用DeepSeek搜索与抓取API构建联网研究工具

用 DeepSeek 的 Search 与 Fetch 两个 API 搭一个可检查、有成本边界的研究命令行工具。
DeepSeek 将自研搜索引擎通过 Search 和 Fetch 两个直接 API 开放,分别负责发现相关网页和把 URL 取回为 Markdown,均按每 1000 次成功请求 0.75 美元计价。文章指出,发现与抽取是带有成本、延迟和策略限制的外部调用,应显式暴露而非藏进不可检查的框架。作者用纯 Python 标准库构建了一个小型命令行工具:带认证和超时的 JSON 客户端、校验结构的搜索函数、逐个抓取并保留标题与来源 URL 的 fetch、对单页和整体设限的研究包组装,最后串联成完整流程。文中还给出成本估算、每秒 5 次的限流、429 与抓取失败的处理建议,并强调抓取文本属于不可信输入,需防范提示词注入、保留来源以便核验。
大模型的答案可能听起来很新,实际上却依赖着昨天的知识。当用户问出「这个库这周改了什么」这类问题时,模型需要两种独立能力:发现相关页面,以及读取这些页面。过去,团队往往要拼凑多个搜索与抽取服务,或者把控制权交给一个行为难以检查的框架。
DeepSeek广告 将自家搜索引擎通过两个直接 API 开放出来:Search 返回结构化网页结果,Fetch 把一个 URL 取回为 Markdown。每个 API 每 1000 次成功请求标价 0.75 美元。这是一个新的积木,而不是完整的研究智能体。本文把它组装成一个体积小、过程可检查的 Python 命令行工具。

这个工具要做什么
程序会依次完成几件事:针对一个问题执行搜索,并打印数量有限的候选结果;让你选择抓取哪些结果页,而不是自动读完所有结果;把每个抓取页面的标题和 URL 与 Markdown 内容保留在一起;在把任何内容交给模型之前限制响应大小;当密钥缺失、服务返回 HTTP 错误或响应结构异常时,明确报错退出。
这种拆分很重要。发现和抽取都是外部调用,带有成本、延迟和策略限制。把它们显式暴露出来,才可能在 prompt 之外加入审批、缓存、日志和来源审查,而不是把这些决策藏在提示词里。
研究智能体(research agent) 与单纯的检索 API 是两个层面的东西。检索 API 只负责把外部信息取回来,而研究智能体是一个会自行规划问题、决定搜什么、选哪些页面、如何整合答案的循环系统。把这两者混为一谈是常见误区:直接调用 Search 和 Fetch 并不等于拥有了一个智能体,你还缺少决策逻辑、来源审查、错误恢复和成本控制。相反,一个完整的智能体框架(如各类 Harness)虽然开箱即用,但内部行为往往不透明,调试时难以定位问题出在规划、检索还是抽取环节。本文选择的做法是「用小积木自己搭」:先用两个原子 API 构成最小闭环,把发现、选择、抽取、组装每一步都摊开在代码里,等这个流程被验证有效之后,再考虑是否引入更高层的自动化。
API 就两个调用
官方指南给出的端点是 POST https://api.deepseek.com/v1/tools/search 和 POST https://api.deepseek.com/v1/tools/fetch,两者都使用 Bearer API key 和 JSON 请求体。Search 接受必填的 query、1 到 50 之间的可选结果数量,以及 need_content;Fetch 接受一个 URL,返回标题加 Markdown 内容。Search 的 count 控制结果条数,而不是请求价格。
计费细节容易读错:Search 和 Fetch 各自按每 1000 次成功请求 0.75 美元计价。一次 Search 在返回 HTTP 200 且结果非空时才算可计费;一次 Fetch 在返回页面内容时才算可计费。根据该指南,空结果、失败请求和超时不收费。因此,一次搜索后再抓取五个页面,是六次 API 调用,而不是一次。
另有一条发布说明需要区分:通过 DeepSeek Harness、Claude Code 或 Codex 发起的服务端 Search 和 Fetch 调用目前免费,计费计划于 2026 年 10 月 31 日 16:00 UTC 开始。本文直接调用独立端点,因此不能假定这项豁免适用于它。
发布说明称,这次开放的端点由 DeepSeek 自研搜索引擎驱动。这描述的是提供方的公告,而不是对索引覆盖范围或结果质量的独立评估。对于应用真正关心的查询,索引质量需要自行测量。
成功请求计费(billable on success) 是一种与「按调用次数计费」不同的定价语义,理解它对成本估算很关键。按调用计费时,只要请求发出就产生费用;而按成功请求计费时,只有真正返回了有效结果的请求才计入账单。具体到这两个端点:Search 只有在返回 HTTP 200 且 results 非空时才计费,Fetch 只有在真正取回页面内容时才计费;空结果、HTTP 错误(如 401、429)以及超时都不计费。这意味着重试失败请求本身通常不会直接烧钱,但会消耗时间和触发限流。做预算时应当按「预期成功的调用数」而非「发出的请求数」估算,同时把重试带来的延迟和配额占用纳入容量规划。
第 1 步:准备 Python 环境
示例只使用 Python 标准库,不需要安装任何包。创建一个名为 research.py 的文件,通过环境变量提供 API key。不要把真实密钥粘贴进源码或提交到版本库。
export DEEPSEEK_API_KEY="your-key"
python3 research.py "What changed in Python's asyncio this month?"
在 Windows PowerShell 中,用 $env:DEEPSEEK_API_KEY="your-key" 为当前 shell 设置变量。环境变量只是本地示例的便利做法,不是部署服务的完整密钥管理策略。
第 2 步:写一个带认证的 JSON 客户端
下面的 HTTP 辅助函数集中处理认证、超时、JSON 编码和错误处理。超时是必需的:外部搜索不应无限期挂起工作进程。
import json
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
BASE_URL = "https://api.deepseek.com/v1/tools"
API_KEY = os.environ.get("DEEPSEEK_API_KEY")
def post_json(path: str, payload: dict) -> dict:
if not API_KEY:
raise RuntimeError("Set DEEPSEEK_API_KEY before running this script")
request = Request(
f"{BASE_URL}/{path}",
data=json.dumps(payload).encode("utf-8"),
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urlopen(request, timeout=20) as response:
body = response.read(2_000_000)
except HTTPError as exc:
detail = exc.read(4_000).decode("utf-8", errors="replace")
raise RuntimeError(f"DeepSeek returned HTTP {exc.code}: {detail}") from exc
except URLError as exc:
raise RuntimeError(f"Could not reach DeepSeek: {exc.reason}") from exc
try:
return json.loads(body)
except json.JSONDecodeError as exc:
raise RuntimeError("DeepSeek returned invalid JSON") from exc
响应读取上限设为两兆字节,以避免无界内存使用。这个上限是本地安全选择,不是 DeepSeek 的 API 限制。错误响应体同样设上限,因为诊断信息不应变成另一个不受控的输入来源。
第 3 步:搜索并检查结果列表
增加一个搜索函数,它请求少量结果,并在使用前校验响应结构:
def search(query: str, count: int = 5) -> list[dict]:
query = query.strip()
if not query or len(query) > 128:
raise ValueError("Query must contain 1 to 128 characters")
if not 1 <= count <= 50:
raise ValueError("count must be between 1 and 50")
data = post_json("search", {
"query": query,
"count": count,
"need_content": False,
})
results = data.get("results")
if not isinstance(results, list):
raise RuntimeError("Search response did not contain a results list")
return results
API 还支持 site:、-site:、引号短语、lang:、after:、before: 等搜索运算符。例如查询里可以写 site:docs.python.org asyncio TaskGroup after:2026-01-01。运算符可用于收窄信息需求,但不要把过滤后的结果集当作「不存在其他相关页面」的证据。
把结果连同序号、标题和 URL 打印出来,由人来决定哪些页面值得抓取。下面是展示函数:
def show_results(results: list[dict]) -> None:
for index, item in enumerate(results):
print(f"[{index}] {item.get('title', '(untitled)')}")
print(f" {item.get('url', '')}")
print(f" {item.get('snippet', '')}\n")
生产级 agent 可以让模型自己完成选择,但仍应把选中的 URL 和选择理由暴露出来。搜索结果摘要适合排序和初筛,当准确性重要时,它不是阅读原文的替代品。
第 4 步:抓取选定页面并保留来源
一次只抓一个页面。content 值就是 Markdown,API 文档说明其可用性取决于目标站点的访问策略。有些 URL 不会返回可用内容。把这些失败当作单个来源的失败处理,而不是让整个研究任务失败。
def fetch(url: str) -> dict:
if not url.startswith(("https://", "http://")):
raise ValueError("Only HTTP and HTTPS URLs are accepted")
data = post_json("fetch", {"url": url})
title = data.get("title", "")
content = data.get("content", "")
if not isinstance(content, str) or not content.strip():
raise RuntimeError(f"No readable Markdown returned for {url}")
return {
"title": title,
"url": url,
"content": content,
}
在这个简单的命令行版本里,URL 校验刻意保持基础。如果应用接受来自不受信任用户的 URL,需要更强的防御措施,防范服务端请求伪造、重定向到内网地址以及不安全的协议。不要在未审查威胁模型的情况下,把裸 fetch 端点暴露给任意客户端。
关键的设计选择是每份文档都携带来源 URL。不要把页面拼接成一个匿名的大块。一旦来源丢失,模型可能给出看似合理的陈述,而你再也无法分辨哪个来源支持它。
第 5 步:组装有大小上限的研究包
一次搜索最多返回 50 条结果,抓取的页面也可能很长。在把内容发给模型之前,要对单页大小和整个数据包的大小同时设限:
def make_packet(documents: list[dict], per_page_chars: int = 8_000) -> str:
sections = []
for doc in documents:
body = doc["content"][:per_page_chars]
sections.append(
f"## {doc['title']}\n"
f"Source: {doc['url']}\n\n"
f"{body}"
)
return "\n\n---\n\n".join(sections)
字符上限不等于 token 上限。如果你的模型有严格的上下文预算,请用该模型的 tokenizer 对数据包做 token 化,并为指令和答案预留空间。也可以考虑去掉导航栏和模板文本,但要保留原始 URL 和足以验证关键论断的原文。
一个负责任的研究提示词可以要求模型只根据提供的文档作答,为每条事实性论断附上来源 URL,并在来源无法回答问题时明确说明。提示词有帮助,但不能保证真实。应用仍需展示底层证据,并允许用户核验。
字符上限与 token 上限并不等价,这是构造研究包时最容易被忽视的细节。同一个字符数,在不同模型、不同语言、不同内容(代码、自然语言、URL)下对应的 token 数差异很大,中文通常比英文占用更多 token。因此,用 per_page_chars 这样的字符阈值只能作为粗略的粗筛,不能当作精确的上下文预算。正确做法是:确定目标模型的 tokenizer,对拼装后的数据包实际做 token 计数,并从中为系统指令、用户问题和模型输出预留固定额度。此外,截断要讲究策略——优先保留页面前部与正文区域,去掉导航、页脚、cookie 提示等模板文本,但务必保留原始 URL 和足以支撑关键论断的上下文,否则截断后的片段可能失去可验证性。
第 6 步:把整个流程串起来
入口函数先搜索、显示候选,然后抓取前两个结果。为了让示例能用一条命令跑通,它自动选择前两个;在交互式应用中,应在这里插入审核或相关性选择步骤。
def main() -> int:
if len(sys.argv) < 2:
print('Usage: python3 research.py "your question"', file=sys.stderr)
return 2
question = " ".join(sys.argv[1:])
try:
results = search(question, count=5)
if not results:
print("No search results.")
return 0
show_results(results)
documents = []
for item in results[:2]:
url = item.get("url", "")
if not url:
continue
try:
documents.append(fetch(url))
except (RuntimeError, ValueError) as exc:
print(f"Skipping {url}: {exc}", file=sys.stderr)
if not documents:
print("No pages could be fetched.", file=sys.stderr)
return 1
print("\n=== Research packet ===\n")
print(make_packet(documents))
return 0
except (RuntimeError, ValueError) as exc:
print(str(exc), file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())
在真实的 agent 中,不要盲目抓取前两条结果。用透明规则对候选排序,对敏感来源要求审批,在策略允许时缓存重复 URL。如果提供方未来返回请求 ID 或时间戳,记录下来;并保留足够日志以解释答案来自哪里。
成本与运行限制
按当前标价,一次成功的 Search 加两次成功的 Fetch,总成本是 0.00225 美元。这是根据公开的按请求单价算出的结果,不是对完整 AI 答案总成本的承诺。模型推理、重试以及其他服务都另算。反复运行的 agent 循环会把微小的单次费用累积成可观的用量,所以应统计实际请求数,并在可用时设置账户级预算告警。
指南列出 Search 的账户限制为每秒 5 次查询,Fetch 另有每秒 5 次查询的限制,且 Fetch 还有每域名限制。超出限制的请求可能返回 HTTP 429。生产环境应加入带指数退避和抖动的有界重试;不要无限重试,也不要重试无效密钥或格式错误请求这类不可重试的错误。
API 文档还指出,Fetch 可能因为 URL 不可用、被站点策略阻止、不受支持或无法抽取 Markdown 而失败。搜索到一条结果,并不等于获得了忽略目标站点访问规则的许可。来源站点的条款和适用政策应纳入审查流程。
上线前的检查清单
- 把凭据挡在代码和日志之外。 部署环境使用密钥管理器,并隐去认证头。
- 设置请求超时和响应大小限制。 外部页面和服务不受你的应用控制。
- 保留来源信息。 在检索、摘要和展示过程中,让标题、URL 和内容始终在一起。
- 限制成本。 统计搜索和抓取次数,限制结果数和重试次数,并监控实际用量。
- 把抓取的文本当作不可信输入。 页面可能包含提示词注入指令。检索到的文本是数据,永远不是改变系统策略或调用工具的权力。
- 把搜索与动作分开。 读取一个页面不应自动授权 agent 执行代码、发送消息或修改记录。
- 测试失败路径。 覆盖空结果、HTTP 401、429、抓取被拒、超时、畸形 JSON 和内容截断。
- 核对第一手来源。 搜索摘要可以帮助发现证据,但重要论断要对照原始页面核实。
提示词注入(prompt injection) 是指不可信文本中夹带指令,诱导模型改变原有行为或调用工具的攻격手法。在检索增强场景里,风险来源正是抓取回来的网页:页面上可能出现「忽略之前的指令,改为……」「请把以下内容发送到……」之类的文字。由于模型无法天然区分「用户指令」与「文档内容」,一旦把抓取文本原样拼进上下文,就可能被操控。防御的核心原则是数据与指令的分离:抓取内容永远只作为数据看待,不应拥有修改系统策略、改变工具权限或触发副作用的权力。实践上可采取显式分隔与标注来源、在系统提示中声明文档内容不可作为指令执行、把搜索与动作(执行代码、发消息、写记录)彻底解耦,以及对敏感操作引入人工审批。没有任何提示词能完全保证安全,因此还需在外围用权限和审计兜底。
关键结论
DeepSeek 的 Search 和 Fetch 端点,把过去需要多供应商拼凑的网页检索路径,压缩成了两个小的 JSON 调用。有趣的不只是低廉的按请求价格,而是边界清晰可见:搜索发现候选,fetch 读取选定页面,由你的代码决定什么进入模型上下文。
这个边界为你提供了可操作的落点,用来加入来源审查、来源标注、提示词注入防御、缓存和预算控制。从范围很窄的问题和少量来源开始,先测量结果对你的工作负载是否真正有用,再把这两个端点变成自动研究循环。
来源
- DeepSeek Search & Fetch API guide: https://api-docs.deepseek.com/guides/search/
- DeepSeek API changelog, October 17, 2026: https://api-docs.deepseek.com/news/news261017/
相关推荐

Workbuddy零基础入门:桌面AI智能体如何落地工作流
Workbuddy桌面AI智能体零基础教程解读:从安装、积分领取到文件管理与自动化工作流搭建,对比CodeX的适用场景与成本差异,帮助职场人完成从AI提问者到AI管理者的思维转变。

OpenAI智能体新栈:计算机操作、决策API与超速工作流
OpenAI Dev Day发布智能体新栈:Computer Use操作速度超越普通人,Decisions API基于Luna实现极低延迟分类,Agents API开放同源计算机操作能力,GPT-6.1 Sol成本仅为Astra七分之一。本文梳理核心更新、实测数据与开发者落地要点。

梁文峰开源推理框架DeepInfo,硬刚美国禁令
DeepSeek在开源模型后再开源推理框架DeepInfo,包含GPU/CPU交叉流水线、小模型预判大模型复核、华为昇腾与英伟达芯片全面适配等技术,直面美国联邦禁令。本文解析其对AI产业落地的意义与开源策略。