返回博客列表

Jevcrawl:让 Agent 爬网页只读「答得上问题」的那一段

2026-10-14T14:00:00+08:00
JevcrawlMCPAgent爬虫开源项目

Jevcrawl:让 Agent 爬网页只读「答得上问题」的那一段

Agent 要回答一个问题,它得先上网找资料。但"上网找资料"这件事,做起来一直很别扭:一个文档站可能有几千页,答案只藏在其中一页的一小段里。爬回来一大坨 HTML,导航栏、页脚、广告、目录混在一起,真正有用的那几十个字被埋在中间,白白烧掉上下文。

项目的作者把这个问题讲得很直白:浏览器型 Agent 只有两个坏选择——要么把抓到的内容全部读一遍(答案被淹没在模板里,token 白白烧掉),要么让 LLM 一页一页挑下一个链接(慢,而且是每题按美元计费)。

jevcrawl 两个都不选。它的定位是一句话:找到答案本身,而不只是找到网页。

目录

一、Agent 爬网页,只有两个坏选择

先说清楚它要替代的两条路。

路一:全读。 爬虫把页面都抓回来丢给 LLM。问题在于网页本身是为人类浏览设计的——大量篇幅是导航、页脚、cookie 提示、相关链接。真正的答案可能只占其中 2%,但你要为剩下 98% 的 token 付费,而且冗长的上下文还会稀释模型的注意力。

路二:让 LLM 挑链接。 每爬一页就调一次大模型,让它判断"接下来点哪个链接"。准是准一些,代价是:每一页都要一次 LLM 往返,慢,且按 token 计费,一个问题问下来就是几毛钱。

jevcrawl 的做法介于两者之间:用一个小型决策模型去给"链接"和"段落"逐条打分,只把高分的内容交给你的 Agent。爬取的决策成本被压到"每题约一分钱"。

二、核心思路:让模型只回答"是 / 否"

这是整个项目最值得琢磨的设计。

一般的"LLM 爬虫",是让模型读页面、然后生成下一个动作或摘出答案。只要模型能"写",它就能"编"——把不存在的话写进结果,也可能被页面里的注入文本带偏。

jevcrawl 用的决策模型叫 Jev,它只返回一个问题为"是"的概率,不生成任何文本。这带来三个直接后果:

  • 结果不可能是编的。既然模型只能说"这段相关度是 0.97",那它就没法凭空造出一句话。你拿到的每一个段落,都是从页面上原样复制下来的,附带 URL、标题路径和字符偏移量(char_start / char_end),随时可以回去核对原文。
  • 天生抗提示注入。页面文字只会作为"数据字段"被评分,永远不会被当成指令执行。页面上写一句"忽略之前的所有指令",最多只能让某个分数偏高,改变不了流程。
  • 便宜且快。每个页面只发一次决策请求,而不是每个链接一次往返。

用一个只会打分的模型来做决策,这个取舍换来了可验证性——对需要"引用来源"的 Agent 场景来说,这点比聪明更重要。

三、三个工具

它通过 MCP 暴露三个工具,粒度从"回答一个问题"到"只给候选链接":

工具 什么时候用
crawl(goal, start_url, max_pages=30, max_seconds=120, return_tokens=4000, max_decision_usd=0.05) 答案可能在站内好几页深处,返回相关段落
scrape(url, goal=None) 已经知道是哪一个页面。给 goal 只返回相关段落;不给则返回全文且不调用 Jev
map(start_url, goal, limit=50, max_pages=10) 只想要排好序的 URL 列表,用来决定之后抓哪个

命令行同样能跑,三个子命令一一对应:

jevcrawl crawl https://docs.python.org/3/ --goal "What does the walrus operator do?" [--json]
jevcrawl scrape https://example.com/help/returns [--goal "..."] [--json]
jevcrawl map https://example.com --goal "..." [--limit 20]
jevcrawl doctor

它通过 MCP 接入 Claude Code、Claude Desktop、Cursor 等客户端,一行命令就能装:

claude mcp add jevcrawl -e TYPESAFE_API_KEY= -- uvx jevcrawl

四、基准:贵不贵,准不准

作者跑了 WebWalkerQA 基准(阿里巴巴通义实验室,ACL 2025)——问题答案往往藏在真实网站的几层点击之后,中英文都有。三种方案用同一个回答模型、同一个裁判,唯一变量是爬虫。

方案 答对率 命中答案页 每题成本
jevcrawl 40% 54% $0.025
Claude 逐链接挑选 30% 33% $0.194
广度优先爬取 8% 11% $0.228

换算成"每答对一题的成本",差距更明显:jevcrawl $0.06,Claude 挑链接 $0.65,广度优先 $2.85——大约 10 倍的差距。

和另一个知名开源爬虫 Crawl4AI 相比(用它的 BestFirstCrawlingStrategy 做对比,同样不带 LLM):20 个任务里,jevcrawl 找到答案页 10 次、Crawl4AI 5 次;中位抓取耗时 29 秒对 43 秒;Crawl4AI 有 3 个站点没加载出来,jevcrawl 是 0。

作者自己标注了需要留意的地方:样本只有 50 和 20 个,成本差距很大而且稳定,但准确率差距没那么稳;另外 Crawl4AI 会驱动真实浏览器,jevcrawl 不跑 JavaScript,遇到必须执行 JS 的页面会把它们列进 stats.needs_js_urls。

五、一次真实抓取长什么样

拿 docs.python.org 试问"海象运算符 := 是干什么的、哪个 Python 版本引入的",返回的结构大致是这样:

{
  "blocks": [{
    "url": "https://docs.python.org/3/whatsnew/3.8.html",
    "heading_path": "What's New In Python 3.8 > New Features > Assignment expressions",
    "text": "There is new syntax := that assigns values to variables as part of a larger expression. ...",
    "char_start": 307,
    "char_end": 580,
    "relevance": 0.97
  }],
  "uncertain": [],
  "stats": {
    "pages_fetched": 6,
    "tokens_raw": 80034,
    "tokens_returned": 1873,
    "jev_calls": 11,
    "jev_cost_usd": 0.0069,
    "elapsed_seconds": 8.3,
    "stopped_because": "answered"
  }
}

一句话概括这组数字:6 个页面的 80,034 个 token,被压缩成 1,873 个 token 的答案,花了 $0.0069,用了 8.3 秒。

其中几个字段值得注意:blocks 是按相关度排序的相关段落;uncertain 是那些"擦边"的段落,在你断定"确实没找到"之前值得再看一眼;stopped_because 会告诉你是正常答完了(answered),还是撞到了页数、时间或预算上限(max_pages / max_seconds / budget)。

六、六步流水线

一次 crawl 大致这样走完:

graph LR
    A["Seed from sitemap"] --> B["Fetch best 4 pages"]
    B --> C["Segment into passages"]
    C --> D["Filter boilerplate, free"]
    D --> E["Jev scores passages and links"]
    E --> F["Answer the goal?"]
    F -->|no| B
    F -->|yes| G["Return cited passages"]

几个容易忽略但很关键的细节:

  • 低分链接是被"保留",不是被丢弃。排到优先线以下的链接进入后备队列,这样只要页面预算还没用完,爬取就不会提前停手。
  • 失败会降级,但不会假装没事。连续三次 Jev 请求失败后,它会退化成广度优先继续爬,把所有未评分的段落放进 uncertain,并在 stats.degraded 里如实标注。
  • 重复是免费的。Jev 的打分答案缓存在本地(~/.jevcrawl/cache),同一个目标、同一批页面再来一次,成本为零。

七、边界与成本

用之前得清楚它的边界,作者写得很坦率:

  • 不读 robots.txt,也不额外限速,除了一次并发抓 4 个页面之外没有别的节流。哪些站能爬、遵守什么条款,责任在调用方。
  • 不登录、不绕过付费墙、不处理 CAPTCHA、不执行 JavaScript。需要 JS 的页面会被列出来,由你自己决定怎么办。
  • 成本只按输入 token 计,jev-1.13.0 是每百万 token $0.042,基准里平均一次爬取约 $0.011。max_decision_usd 是硬上限——可能超预算的请求根本不会发出,爬取会以 budget 状态停下。
  • 页面文字会发给 TypeSafe(段落、链接标签和 URL)用于打分,API key 只从环境变量读,不落盘、不入日志。

配置项少得可怜,基本都是环境变量:

环境变量 作用 默认值
TYPESAFE_API_KEY Jev API key(必填) 无
JEVCRAWL_BASE_URL 兼容 Jev 的服务端点 https://api.typesafe.ai
JEVCRAWL_MODEL 模型 id jev-1.13.0
JEVCRAWL_CACHE_DIR 答案缓存目录 ~/.jevcrawl/cache

其余阈值、每页链接数、并发度都在 config.py 里有实测过的默认值,Python 调用方也可以传入自己的 Config。

八、上手

需要 Python 3.11+,一个来自 console.typesafe.ai 的 Jev API key,以及 uv。

uvx jevcrawl doctor                                  # 先检查环境
claude mcp add jevcrawl -e TYPESAFE_API_KEY= -- uvx jevcrawl

然后在 Claude Code 里直接说:"用 jevcrawl 从 docs.python.org 找一下海象运算符是哪个 Python 版本引入的。" 对会加载 skills 的 Agent,仓库里还准备了 skills/jevcrawl/SKILL.md。

开发与测试也干净:uv run pytest 默认离线、所有 HTTP 都被 mock,任何会联网或花钱的用例都被单独标记为 live,不会在你的 CI 里悄悄计费。许可证 Apache-2.0。


作者: itech001 来源: 公众号:AI人工智能时代(the-ai-era) 网站: https://www.theaiera.top/ 关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top

本文首发于 AI人工智能时代,转载请注明出处。

分享给朋友