一. 前言
近期一直在尝试实现一个功能 ,做了很多尝试后 ,有一些零零散散的记录 ,这里拿出来给大家分享一下。
二. 数据收集的几种维度
2.1 Python 脚本爬取网页新闻数据 (常规方式)
在 AI 还没发展出来 ,一般可以通过新闻聚合提供的接口 或者 爬虫 来拉取消息数据。
其中新闻聚合在 AI 出现之后 ,逐渐转变成支持组件 ,为 AI 智能体提供 MCP 服务。 比如博查/聚合数据这类。
而爬虫这东西, 具有一定的风险 ,可能涉及到违法,这里 Demo 就很简单的说说 :
- 常用的组件 :
requests
:发送HTTP请求抓取网页内容beautifulsoup4
:解析网页HTML,提取新闻信息fake_useragent
(可选):动态伪装请求头,避免被服务器拒绝
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import time
from urllib.parse import urljoin
def log(msg):
print(f"[LOG] {msg}")
def fetch_sina_finance_news():
# 某财经网站作为案例
base_url = "https://xxx.xxx.xxx.cn/stock/"
log(f"开始访问 {base_url}")
headers = {
"User-Agent": UserAgent().random
}
try:
response = requests.get(base_url, headers=headers, timeout=10)
response.raise_for_status()
log("网页请求成功")
except Exception as e:
log(f"请求失败: {e}")
return
soup = BeautifulSoup(response.content, "html.parser")
# 找到第一个<div class="tabs-cont sto_cont0">对应要闻板块
news_container = soup.find("div", class_="tabs-cont sto_cont0")
if not news_container:
log("未找到要闻内容区,网页结构可能已变更")
return
# 找到里面第一个<ul class="list01" data-client="scroll">就是要闻新闻列表
news_list = news_container.find("ul", class_="list01", attrs={"data-client": "scroll"})
if not news_list:
log("未找到要闻新闻列表")
return
news_items = news_list.find_all("li", recursive=False) # 只查一级li,避免抓取嵌套的
if not news_items:
log("新闻列表为空")
return
log(f"共找到 {len(news_items)} 条新闻,开始提取标题和链接")
for index, li in enumerate(news_items, 1):
a_tag = li.find("a")
if not a_tag:
log(f"第{index}条新闻无链接,跳过")
continue
title = a_tag.get_text(strip=True)
href = a_tag.get("href")
full_url = urljoin(base_url, href) # 完整url拼接
print(f"{index}. {title}\n 链接: {full_url}")
log("新闻提取完成")
if __name__ == "__main__":
fetch_sina_finance_news()
阶段总结 :
- 使用爬虫可以达到目的 ,但是 开发成本比较高 ,高度 依赖目标网站 的特性
- 爬虫获取的数据比较原始 ,需要 进行比较复杂的二次分析 ,才能获取最终效果
- 爬虫具有一定的 法律风险 ,可能会带来不必要的麻烦,使用要慎重
2.2 聚合平台获取数据 / 供 AI 使用的基础数据 (组件依赖)
如果规模比较大 ,一般就会考虑通过聚合平台来获取数据了 ,聚合平台会把各平台的数据进行聚合 ,只需要调用接口就能获取对应的新闻信息 : ( 案例平台 : 博查AI开放平台 | Search API, Reranker API)
from typing import List, Optional, Dict, Any
from dataclasses import asdict, dataclass, field
import requests
@dataclass
class WebPageItem:
id: Optional[str] = None
name: Optional[str] = None
url: Optional[str] = None
displayUrl: Optional[str] = None # 修改为匹配 JSON 中的键名
snippet: Optional[str] = None
siteName: Optional[str] = None # 修改为匹配 JSON 中的键名
siteIcon: Optional[str] = None # 修改为匹配 JSON 中的键名
datePublished: Optional[str] = None # 修改为匹配 JSON 中的键名
dateLastCrawled: Optional[str] = None # 修改为匹配 JSON 中的键名
cachedPageUrl: Optional[str] = None # 修改为匹配 JSON 中的键名
language: Optional[str] = None
isFamilyFriendly: Optional[bool] = None
isNavigational: Optional[bool] = None
@dataclass
class ImageItem:
webSearchUrl: Optional[str] = None # 修改为匹配 JSON 中的键名
name: Optional[str] = None
thumbnailUrl: Optional[str] = None # 修改为匹配 JSON 中的键名
datePublished: Optional[str] = None
contentUrl: Optional[str] = None
hostPageUrl: Optional[str] = None
contentSize: Optional[str] = None
encodingFormat: Optional[str] = None
hostPageDisplayUrl: Optional[str] = None
width: Optional[int] = None
height: Optional[int] = None
thumbnail: Optional[str] = None
@dataclass
class SearchResponse:
code: int
log_id: str
msg: Optional[str] = None
data: Dict[str, Any] = field(default_factory=dict)
web_pages: List[WebPageItem] = field(default_factory=list)
images: List[ImageItem] = field(default_factory=list)
def __post_init__(self):
if self.data and 'webPages' in self.data:
self.web_pages = [WebPageItem(
**page) for page in self.data['webPages'].get('value', [])]
if self.data and 'images' in self.data:
self.images = [ImageItem(**image)
for image in self.data['images'].get('value', [])]
class WebSearchClient:
def __init__(self, api_key):
self.api_key = api_key
self.base_url = "https://api.bochaai.com/v1/web-search"
print(f"初始化 WebSearchClient,API端点: {self.base_url}")
def search(self, query):
"""通过 Web Search API 查询网络内容"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"query": query,
"freshness": "oneDay",
"summary": "false",
"count": 10
}
print(f"发起搜索查询:{query}")
try:
response = requests.post(
self.base_url,
headers=headers,
json=payload
)
response.raise_for_status()
results = response.json()
# 使用 DTO 对象处理结果
search_response = SearchResponse(**results)
print(f"搜索成功,返回结果数量:{len(search_response.web_pages)}")
return search_response
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
def main():
# API 密钥
api_key = "sk-11111"
# 预定义查询关键词
query = "当天和经济金融股票相关的新闻"
web_search = WebSearchClient(api_key)
print(f"开始查询:{query}")
results = web_search.search(query)
print(f"查询结果{results}")
if __name__ == "__main__":
main()
阶段总结 :
- 这类聚合平台使用简单 ,数据来源渠道充足 ,开发成本很低
- 需要一定的支出 , 总体不贵 ,这个是 0.04 一次(
平台很多,大家可以自行斟酌
) - 同样是新闻主体 ,还是需要进行二次分析 ,多用于 MCP 外部支援部分 (后面来看)
- 这里只是演示了最基础的 WebSearch ,通常这种还自带
AI Search 及 Agent Search
2.3 AI 厂商获取新闻数据 (AI玩法)
部分 AI 厂商本身就集成了 WebSearch 的功能 ,只需要直接调用接口 ,就可以快速搜索, 这里就算入门了 ,不再需要额外的分析操作,交给大模型进行处理 :
import os
import json
from typing import Dict, Any, List, Optional
from openai import OpenAI
from openai.types.chat.chat_completion import Choice
class SinaFinanceNewsSearcher:
def __init__(self, api_key: Optional[str] = None):
"""
初始化 SinaFinanceNewsSearcher
:param api_key: 可选的 API Key,如果为 None 则尝试从本地或 DataMap 获取
"""
self.api_key = self._get_api_key(api_key)
self.client = OpenAI(
base_url="https://api.moonshot.cn/v1",
api_key=self.api_key
)
self.initial_messages = [
{
"role": "system",
"content": "你是一个金融工作者,现在要收集每天相关的金融信息,你从新浪财经(https://finance.sina.com.cn/)获取数据。数据的返回通过 JSON 返回给我"
}
]
def _get_api_key(self, provided_key: Optional[str] = None) -> str:
"""
获取 API Key 的优先级方法
优先级:
1. 直接提供的 API Key
2. 本地文件 (moonshot_api_key.txt)
3. DataMap 中获取
4. 抛出异常
:param provided_key: 直接提供的 API Key
:return: API Key
"""
# 1. 如果直接提供了 API Key,直接返回
if provided_key:
return provided_key
# 2. 尝试从本地文件获取
local_file_path = os.path.join(
os.path.dirname(__file__), "moonshot_api_key.txt")
if os.path.exists(local_file_path):
with open(local_file_path, "r") as file:
local_key = file.read().strip()
if local_key:
return local_key
def _search_impl(self, arguments: Dict[str, Any]) -> Any:
"""
搜索实现,目前直接返回参数
:param arguments: 搜索参数
:return: 搜索结果
"""
return arguments
def search_finance_news(self, query: Optional[str] = None) -> str:
"""
搜索金融新闻
:param query: 可选的搜索查询,默认为今天的热点新闻
:return: 搜索结果内容
"""
messages = self.initial_messages.copy()
# 如果没有提供查询,使用默认查询
if query is None:
query = "搜索今天有什么热点新闻"
messages.append({
"role": "user",
"content": query
})
finish_reason = None
while finish_reason is None or finish_reason == "tool_calls":
choice = self._chat(messages)
finish_reason = choice.finish_reason
if finish_reason == "tool_calls":
messages.append(choice.message)
for tool_call in choice.message.tool_calls:
tool_call_name = tool_call.function.name
tool_call_arguments = json.loads(
tool_call.function.arguments)
if tool_call_name == "$web_search":
tool_result = self._search_impl(tool_call_arguments)
else:
tool_result = f"Error: unable to find tool by name '{tool_call_name}'"
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_call_name,
"content": json.dumps(tool_result)
})
return choice.message.content
def _chat(self, messages: List[Dict[str, str]]) -> Choice:
"""
调用 Moonshot AI 聊天接口
:param messages: 消息列表
:return: 聊天返回结果
"""
completion = self.client.chat.completions.create(
model="moonshot-v1-128k",
messages=messages,
temperature=0.3,
tools=[
{
"type": "builtin_function",
"function": {
"name": "$web_search",
}
}
]
)
return completion.choices[0]
def get_sina_finance_news(api_key: Optional[str] = None, query: Optional[str] = None) -> str:
"""
外部调用函数,获取新浪财经新闻
:param api_key: 可选的 Moonshot AI 的 API Key
:param query: 可选的搜索查询
:return: 新闻内容
"""
searcher = SinaFinanceNewsSearcher(api_key)
return searcher.search_finance_news(query)
# 使用示例
if __name__ == "__main__":
# 三种使用方式:
# 1. 不提供 API Key,自动从本地或 DataMap 获取
news1 = get_sina_finance_news(None, "搜索最近的科技股新闻")
# # 2. 直接提供 API Key
# news2 = get_sina_finance_news("your-specific-api-key")
# # 3. 提供 API Key 和具体查询
# news3 = get_sina_finance_news("your-specific-api-key", "搜索最近的科技股新闻")
print(news1)
# print(news2)
# print(news3)
阶段总结 :
- 除了 Kimi ,包括 DeepSeek 等都有完善的 API
- 费用和查询的结果数量挂钩 ,价格会比单纯的查消息略贵 ,涉及到大模型的计费
- 整体来说开发难度低 ,适用面广
- 效果依赖于AI厂商,灵活度不高
2.4 AI 综合平台获取新闻数据 (高度定制 AI)
虽然上面的方式已经足够我们了解到日常的新闻需求 ,但是其更依赖于单个厂商 ,如果需要更灵活的整合 ,就需要综合性的 AI 平台处理了 , 这里我使用的是阿里云的 :
Client 端进行查询
import os
from http import HTTPStatus
from dashscope import Application
response = Application.call(
# 若没有配置环境变量,可用百炼API Key将下行替换为:api_key="sk-xxx"。但不建议在生产环境中直接将API Key硬编码到代码中,以减少API Key泄露风险。
api_key="sk-xxxx",
app_id='xxxx',# 替换为实际的应用 ID
prompt='给我当天的热点新闻')
if response.status_code != HTTPStatus.OK:
print(f'request_id={response.request_id}')
print(f'code={response.status_code}')
print(f'message={response.message}')
print(f'请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code')
else:
print(response.output.text)
阶段总结
- 可以更灵活 ,自行集成 MCP / Agent 都很方便
- 价格也比较便宜 ,每个不一样 (想尝试的可以现在去白嫖 Token)
总结
- 这么一看 ,其实使用云厂商的工具是很不错的选择 ,DeepSeek 开源后 ,能力一下就上来了
- 后续得看AI模型商得费用 ,费用低也是很能打的
- 整体的效果还是都不错到的 ,后面就准备用上了
最后的最后 ❤️❤️❤️👇👇👇
- 👈 欢迎关注 ,超200篇优质文章,未来持续高质量输出 🎉🎉
- 🔥🔥🔥 系列文章集合,高并发,源码应有尽有 👍👍
- 走过路过不要错过 ,知识无价还不收钱 ❗❗