Jevcrawl:让 Agent 爬网页只读「答得上问题」的那一段
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人工智能时代,转载请注明出处。