[控场AI]
· 6 分钟阅读· 3,431 字

为LLM Agent构建搜索抓取层:缓存、正文提取与成本控制

为LLM Agent构建搜索抓取层:缓存、正文提取与成本控制

为LLM Agent加一层抓取网关,统一缓存、预算、合规与正文提取以控成本。

文章从 DeepSeek 搜索抓取 API 每千次 0.75 美元的定价切入,指出 Agent 单次问答往往触发数十次抓取,成本与合规责任都在累积。作者主张用 Fetch Gateway 替代让 Agent 直接发 HTTP 请求:网关依次校验缓存、当日预算与 robots.txt,通过后抓取并用 trafilatura 提取正文、写入 SQLite 缓存。实测 180KB 原始 HTML 可压缩到约 6KB,是链路中最大的 token 节省;SQLite 加 WAL 配合 TTL 使内部问答缓存命中率达 35%–45%,约 30 行代码削减近半成本。此外强调可识别 UA、防 SSRF、硬截断输出,以及预算超限时返回结构化错误让 LLM 优雅降级。

AI Agent 调用搜索和抓取接口的成本,正在从“几行 requests 代码”变成一笔需要认真核算的账。据 Hacker News 上的讨论,DeepSeek 推出的搜索与抓取 API 定价为每 1000 次成功请求 0.75 美元——单次看起来便宜,但一个 Agent 回答一个问题就要调用 5 到 10 次搜索、每次再抓 3 到 4 个页面,日积月累的数字并不小。同时,Meta scraping 条款的争议也在提醒开发者:抓取不只是技术问题,还牵扯合规责任。

为LLM Agent构建搜索抓取层

一位开发者在复刻内部项目时总结了这套抓取层的做法,核心思路是:不要让 Agent 直接访问互联网,而是在中间加一层网关,把缓存、预算、合规和正文提取四件事收拢到一处。

整体架构:网关模式替代裸调用

最常见的错误是给 LLM 一个 fetch_url(url) 工具,让它直接发起 HTTP 请求。后果很具体:同一个页面在一个会话里被抓取四次,200KB 的原始 HTML(含导航、页脚、追踪脚本)被塞进上下文,而月底没人说得清钱花在了哪里。

正确的做法是插入一层 Fetch Gateway。Agent 发起工具调用后,网关依次检查缓存是否命中、当日预算是否允许、robots.txt 是否放行,全部通过才真正发起 HTTP 请求,抓回页面后提取正文、写入 SQLite 缓存,再把结果返回给 Agent。Agent 拿到的永远是干净文本,并带有长度上限;被拒绝时收到的是结构化的原因说明,而不是异常中断。

这个设计的关键在于职责边界:Agent 只负责决定“抓什么”,网关负责决定“能不能抓、抓多少、花多少钱”。

负责任的抓取:robots.txt、UA 与正文提取

合规部分其实不需要额外依赖,Python 标准库中的 urllib.robotparser 就够用。作者用 lru_cache 缓存每个 origin 的 robots.txt 解析结果,避免重复拉取;无法读取 robots.txt 时的处理策略(放行或拒绝)由业务自行决定。

正文提取选用了 trafilatura 2.0。作者对比过 readability-lxml 和 newspaper4k,认为 trafilatura 在新闻站和越南语博客上的稳定性最好。输出格式建议用 markdown 而非纯文本——LLM 对标题层级和表格的理解明显更好,而 token 消耗几乎不变。

实测数据很能说明问题:一个 VnExpress 页面的原始 HTML 约 180KB,经 trafilatura 提取后只剩约 6KB。如果按 token 付费,这是整个链路中最大的一笔节省,甚至超过抓取 API 本身的费用。

代码中还有几个容易被忽略的细节:

  • User-Agent 必须可识别并附带联系方式。伪装成 Chrome 或许能绕过部分 WAF,但这正是 Meta 条款一类规则针对的行为,商业产品不应如此。
  • 输出硬截断,例如限制在 8000 字符(约 2 到 3k token),足够回答大多数问题。
  • 必须防 SSRF,禁止抓取私有 IP 段(10.x、192.168.x)和云元数据端点 169.254.169.254。Agent 一旦遭遇提示注入,可能被诱导去读取云主机的元数据接口。

robots.txt 是网站根目录下的纯文本协议文件,用 User-agent、Allow、Disallow 等指令声明爬虫可访问的路径范围。它并非法律条文,却是行业公认的抓取礼仪基准,商业爬虫无视它容易引发封禁或合规争议。Python 的 urllib.robotparser.RobotFileParser 可按 origin 解析该文件并提供 can_fetch(ua, url) 判断,配合 lru_cache 缓存解析结果即可避免每次请求都重复拉取。需要注意的是,robots.txt 本身也可能返回 403 或超时,此时放行还是拒绝应作为显式业务策略写入配置,而不是隐式默认。

SSRF(服务端请求伪造) 指攻击者诱导服务端向内部地址发起请求。在 Agent 场景中,风险被放大:页面内容或用户输入中埋入的提示注入指令,可能让 LLM 构造出 http://169.254.169.254/latest/meta-data/ 这类工具调用,从而读取云主机的临时凭证。防御的核心是在网关发起请求前做地址解析与校验:拒绝私有网段(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.0/8)和链路本地地址,并在 DNS 解析后再次校验实际 IP,防止通过域名重绑定绕过。

缓存与预算:真正省钱的部分

按 0.75 美元每千次计算,单次请求确实便宜,但 Agent 的循环调用会让月度请求量很快冲到 5 万次,更不用说 bug 导致的无限重试。作者选择 SQLite 而不是额外部署服务,开启 WAL 模式后足以支撑每分钟数百次请求。

缓存表以 URL 的 SHA256 为键,带 TTL(新闻类 1 到 6 小时,技术文档 24 小时以上)。预算表按天累计计数,只有真正发起外部请求时才计数,缓存命中不计入。在团队内部的问答负载下,缓存命中率稳定在 35% 到 45%——用户当天反复询问同一主题是常态,也就是说大约 30 行代码就消掉近一半抓取成本。

另一个值得借鉴的工程习惯是:预算耗尽时返回 budget exceeded 这样的结构化数据,而不是抛出异常。较新的 LLM 能够理解“我无法继续查询,基于已有信息回答……”并正常收尾,而不是让整个循环崩溃。

SQLite 的 WAL(Write-Ahead Logging)模式 将写操作先追加到独立的日志文件,读操作可同时进行,从而避免默认回滚日志模式下读写互相阻塞。开启方式仅需执行 PRAGMA journal_mode=WAL;,配合 PRAGMA synchronous=NORMAL; 可在保证崩溃安全的前提下显著提升并发吞吐。对每分钟数百次请求的抓取网关来说,单文件数据库加 WAL 已经够用,省去了额外部署 Redis 或 Postgres 的运维成本;但要注意 WAL 文件会随写入增长,需定期执行 checkpoint,且不适合多机共享同一文件的部署形态。

接入 Agent:工具描述也是成本控制手段

在工具描述里写清楚约束,效果比想象中好。作者在 description 中注明:“Returns cleaned markdown, max 8000 chars. May return cached content up to 6h old. Do not fetch the same URL twice.” 仅这一行就让重复抓取次数明显下降。

同时建议把每次工具调用记录到独立表中,包含 url、延迟、是否命中缓存、token 数量,便于定期复盘 Agent 的钱花在了哪里。一条 sqlite3 fetch_cache.db "SELECT day, n FROM usage ORDER BY day DESC LIMIT 7;" 就能看清最近七天的调用趋势。

实践要点

如果只记五件事:不要让 Agent 直接发 HTTP 请求,哪怕网关只有 100 行代码;用 trafilatura 一类工具提取正文并按字符数硬截断;用合理的 TTL 做缓存,SQLite 加 WAL 对多数小团队够用;按日设置预算并以数据形式返回拒绝原因;尊重 robots.txt、使用可识别 UA、封堵 SSRF。

即便改用 DeepSeek广告 这类付费 API,这套网关依然值得保留——只需把 fetch_clean 换成对应厂商的客户端,缓存和预算控制仍然必须掌握在自己手里。抓取层正在成为既有真实成本、又有法律责任的 Agent 基础设施,值得像对待数据库连接池一样认真对待。

#LLM Agent#搜索抓取层#trafilatura#缓存策略#成本控制#robots.txt#AI Agent开发#Python
分享:

相关推荐