Crawl4AI digest() 自适应爬取 API 详解:从查询驱动爬取、置信度停爬到状态断点续爬
本文围绕 Crawl4AI 的 digest() 方法展开——它是 AdaptiveCrawler 面向"带查询的目标爬取"的主入口:给定起始 URL 和查询词,爬虫会自动评估信息是否已足够,无需人工指定爬取范围或停止时机。读完本文,你将掌握 digest() 的完整参数语义、CrawlState 返回结构的各字段含义、置信度/覆盖度/饱和度三指标的源码级计算方式,以及断点续爬、自动保存状态、进度监控等可直接复制运行的实战模式。
方法签名与定位
digest() 是自适应爬取(Adaptive Crawling)的核心接口。它从给定 URL 出发,在查询(query)引导下智能地遍历网站,并自动判断何时已收集到足够信息。在 adaptive_crawler.py 中,其真实签名为:
async def digest(
start_url: str,
query: str,
resume_from: Optional[str] = None
) -> CrawlState
AdaptiveCrawler、AdaptiveConfig、CrawlState 均已在 init.py 中作为顶层符号导出,因此可以直接 from crawl4ai import AsyncWebCrawler, AdaptiveCrawler, AdaptiveConfig, CrawlState 使用。
参数详解
start_url
- 类型:
str - 必填:是
- 说明:爬取起始 URL,应为可正常返回内容的 HTTP/HTTPS 地址,作为信息收集的入口。源码中
digest()会先检查start_url not in self.state.crawled_urls:若断点恢复时起始页已爬过,则直接跳过初始爬取,进入自适应扩展循环(见 digest 实现)。
query
- 类型:
str - 必填:是
- 说明:驱动整个爬取过程的查询语句,应包含你期望收集信息的关键词。它在底层至少承担三个角色:
- 链接预览打分:
digest()内部通过_crawl_with_preview()构造CrawlerRunConfig,把 query 传给LinkPreviewConfig(query=query)做 BM25 上下文化打分,并开启score_links=True进行内禀打分(见_crawl_with_preview); - 相关性评估:
StatisticalStrategy._calculate_relevance()优先使用链接在抓取阶段算好的contextual_score(BM25 分数),否则退化为 query 词与链接文本(链接文字 + title + meta title/description/keywords)的词集重叠率(见 相关性计算); - 覆盖度评估:查询词被分词后,逐个统计其词频(tf)与文档频率(df),构成 Coverage 指标(见 覆盖度计算)。
- 链接预览打分:
因此 query 的质量直接决定爬取的走向——查询词越接近目标页面正文实际出现的术语,BM25 打分与覆盖度提升越快,爬取收敛得越早。
resume_from
- 类型:
Optional[str] - 默认值:
None - 说明:先前保存的爬取状态文件路径。提供时,
digest()通过CrawlState.load(resume_from)从磁盘恢复完整状态(已爬 URL、知识库、待爬链接、词频统计、新增词历史等),并把本次传入的 query 覆盖写入状态(self.state.query = query),从而支持"先小范围试爬、中断后再扩爬"的工作流。状态文件的序列化格式是 JSON,由CrawlState.save()写出、CrawlState.load()读回,其中CrawlResult会被转换为包含 url / content(raw markdown)/ links / metadata 的字典。
返回值:CrawlState 结构
digest() 返回一个 CrawlState 数据类实例,文档中列出的字段与源码完全对应:
| 字段 | 类型 | 含义 |
|---|---|---|
crawled_urls |
Set[str] |
已成功爬取的全部 URL,用于去重 |
knowledge_base |
List[CrawlResult] |
每页的爬取结果(含 markdown 正文与链接) |
pending_links |
List[Link] |
已发现但尚未爬取的内部链接 |
metrics |
Dict[str, float] |
性能与质量指标,运行过程中持续写入 confidence、coverage、consistency、saturation、pages_crawled、depth_reached 等 |
query |
str |
原始查询 |
此外源码还维护了若干"用于打分的附加统计信息",这也是文档中 "Additional statistical information for scoring" 的具体所指:
term_frequencies/document_frequencies/documents_with_terms/total_documents:词频、文档频率等倒排统计,由StatisticalStrategy.update_state()在每页入库时增量更新;new_terms_history:每爬一页新增了多少个"新词",是饱和度(递减收益)计算的原始序列;crawl_order:页面爬取顺序,方便复盘爬虫实际走了哪条路径;kb_embeddings/query_embeddings/expanded_queries/semantic_gaps等:仅在strategy="embedding"时使用的语义空间状态。
工作原理:五步智能爬取算法
文档将 digest() 描述为五步智能爬取流程,与源码主循环一一对应:
# 主循环骨架(摘自 adaptive_crawler.py 的 digest())
if start_url not in self.state.crawled_urls:
result = await self._crawl_with_preview(start_url, query) # 1. 初始爬取
...
while depth < self.config.max_depth:
confidence = await self.strategy.calculate_confidence(state) # 2/3. 计算置信度
if await self.strategy.should_stop(state, self.config): # 5. 停止判断
break
ranked_links = await self.strategy.rank_links(state, cfg) # 4. 链接排序
to_crawl = ranked_links[:self.config.top_k_links] # 自适应选择 Top-K
new_results = await self._crawl_batch(to_crawl, query) # 并行抓取
await self.strategy.update_state(state, new_results)
depth += 1
- 初始爬取:先对
start_url执行一次"带链接预览"的爬取。_crawl_with_preview()内部使用LinkPreviewConfig(include_internal=True, include_external=False, query=query, concurrency=5, timeout=link_preview_timeout, max_links=50),即只保留同域链接、用 query 做 BM25 打分,并过滤掉没有 head 元数据的死链接(见 async_configs.py 中的 LinkPreviewConfig)。 - 链接分析:所有新发现的同域内链进入
state.pending_links;排序时跳过已爬 URL,并对每个候选链接计算"相关性 + 新颖度 + 权威性"加权分。 - 三指标评分:默认统计策略(
StatisticalStrategy)以固定权重0.4*coverage + 0.3*consistency + 0.3*saturation合成置信度: - 自适应选择:
rank_links()按0.5*relevance + 0.3*novelty + 0.2*authority加权(权重来自AdaptiveConfig,默认值恰好如此),只取前top_k_links个且未爬过的链接,交由_crawl_batch()用asyncio.gather并发抓取,失败的页面会被静默过滤、不中断流程(见 批量抓取)。 - 停止决策:
should_stop()与主循环共同构成停止闸门,任何一条触发即退出(详见下文"停止条件")。
循环结束后,digest() 还会再算一次最终置信度写入 state.metrics['confidence'],并补充 pages_crawled 与 depth_reached 两项统计;若配置了 save_state=True 且给出 state_path,每个 depth 结束后都会落盘一次,结束时再做最终保存。
实战示例(可直接运行)
以下示例完整继承官方文档 digest.md 的四个场景,并补充了必要的导入说明。
基础用法
import asyncio
from crawl4ai import AsyncWebCrawler, AdaptiveCrawler
async def main():
async with AsyncWebCrawler() as crawler:
adaptive = AdaptiveCrawler(crawler)
state = await adaptive.digest(
start_url="https://docs.python.org/3/",
query="async await context managers"
)
print(f"Crawled {len(state.crawled_urls)} pages")
print(f"Confidence: {adaptive.confidence:.0%}")
asyncio.run(main())
带配置
AdaptiveConfig 的全部字段与默认值定义见 AdaptiveConfig。示例中用到的三项核心参数:
from crawl4ai import AsyncWebCrawler, AdaptiveCrawler, AdaptiveConfig
config = AdaptiveConfig(
confidence_threshold=0.9, # 置信度阈值,默认 0.7,要求更高把握
max_pages=30, # 最大爬取页数,默认 20
top_k_links=3 # 每个 depth 跟随的最高分链接数,默认 3
)
async with AsyncWebCrawler() as crawler:
adaptive = AdaptiveCrawler(crawler, config=config)
state = await adaptive.digest(
start_url="https://api.example.com/docs",
query="authentication endpoints rate limits"
)
常用配置参数速查(默认值均取自源码):
| 参数 | 默认值 | 作用 |
|---|---|---|
confidence_threshold |
0.7 |
置信度达到该值即判定信息充分、停止爬取 |
max_depth |
5 |
自适应扩展循环的最大轮数(每轮跟随一批链接) |
max_pages |
20 |
累计爬取页数硬上限 |
top_k_links |
3 |
每轮选取评分最高的 K 个链接 |
min_gain_threshold |
0.1 |
最高分链接的预期增益低于该值则停止 |
strategy |
"statistical" |
置信度策略:statistical(默认,无 LLM)或 embedding |
saturation_threshold |
0.8 |
饱和度达到该值即停止 |
coverage_weight / consistency_weight / saturation_weight |
0.4 / 0.3 / 0.3 |
三指标合成权重,构造后必须求和为 1(validate() 会断言) |
relevance_weight / novelty_weight / authority_weight |
0.5 / 0.3 / 0.2 |
链接排序权重,同样要求求和为 1 |
save_state / state_path |
False / None |
开启后每轮 depth 自动把状态 JSON 落盘到 state_path |
link_preview_timeout |
5.0 |
链接 head 预览(BM25 打分)的超时秒数 |
注意:
AdaptiveCrawler.__init__会对 config 调用config.validate(),非法参数(如阈值越界、权重和不为 1)会在构造阶段直接抛断言错误,而不是爬取中途才暴露。
断点续爬(Resume)
# 第一次爬取——可能中途被中断
state1 = await adaptive.digest(
start_url="https://example.com",
query="machine learning algorithms"
)
# 手动保存状态(若配置了 save_state=True 则会自动保存)
state1.save("ml_crawl_state.json")
# 之后,从保存的状态恢复继续爬
state2 = await adaptive.digest(
start_url="https://example.com",
query="machine learning algorithms",
resume_from="ml_crawl_state.json"
)
tests/adaptive/test_adaptive_crawler.py 中的 test_with_persistence 演示了另一种更完整的用法:第一次爬取用 AdaptiveConfig(..., save_state=True, state_path=state_path, max_pages=5) 做小规模试爬,随后把 config.max_pages 调大到 10,再以 resume_from=state_path 恢复续爬——即"先试跑、再扩爬"的典型工程模式。
进度监控
state = await adaptive.digest(
start_url="https://docs.example.com",
query="api reference"
)
# 监控进度
print(f"Pages crawled: {len(state.crawled_urls)}")
print(f"New terms discovered: {state.new_terms_history}")
print(f"Final confidence: {adaptive.confidence:.2%}")
# 查看详细统计(含每个查询词的 tf/df、三指标分解)
adaptive.print_stats(detailed=True)
print_stats(detailed=True) 在统计策略下会额外打印 Query Coverage 明细:逐个查询词展示"出现在 x/y 页、共出现 n 次",未命中的词会标记 not found,非常直观地暴露"哪些查询词没被覆盖到"(实现见 print_stats)。另外,adaptive.get_relevant_content(top_k=5) 可按得分返回知识库中最相关的 Top-K 页面,adaptive.export_knowledge_base("kb.jsonl") 可将知识库导出为 JSONL 供下游 RAG/LLM 使用(测试脚本中均有实际调用)。
查询词最佳实践
官方文档给出的三条建议,结合源码实现可以更精确地理解其原理:
-
要具体(Be Specific):使用会真实出现在目标内容中的描述性术语
# Good query = "python async context managers implementation" # Too broad query = "python programming"原因:覆盖度是按查询词逐个计算的,过宽的查询词(如 "python")会在起始页即被覆盖,无法驱动爬虫继续深入;而 "context managers implementation" 这类词需要多页积累才能覆盖,才能引导爬取走向正确的子树。
-
包含关键术语(Include Key Terms):加入你预期会找到的技术名词
query = "oauth2 jwt refresh tokens authorization"这些词同时喂给 BM25 链接打分与覆盖度统计,是链接排序的"燃料"。
-
组合多个相关概念(Multiple Concepts):
query = "rest api pagination sorting filtering"多概念组合会让覆盖度必须跨页累积才能达到阈值,天然引导爬虫覆盖更全的子页面集合。
文档同时建议查询词长度在 3~8 个词 效果最佳——分词器(_tokenize)会剔除标点与长度 ≤2 的 token,3~8 词恰好能留下足够多的有效评估维度而不至于过于发散。
性能考量
- 起始 URL 选择:选导航良好的页面(如文档索引页)。起始页的内链数量与质量直接决定第一轮候选池的规模;
_crawl_with_preview默认只预览前 50 个同域链接(max_links=50、concurrency=5)。 - 查询词长度:3~8 个词通常最佳(理由见上)。
- 链接密度:导航清晰的站点爬取效率更高——
pending_links池子越大、打分区分度越高;链接稀疏的站点容易在 1~2 轮 depth 后因"无候选链接"提前结束。 - 缓存:对同一域名的重复爬取可开启 Crawl4AI 的缓存机制(
CrawlerRunConfig层面的 cache 选项),避免重复抓取相同页面。
错误处理与自动保存
try:
state = await adaptive.digest(
start_url="https://example.com",
query="search terms"
)
except Exception as e:
print(f"Crawl failed: {e}")
# 若配置中 save_state=True,则状态已按 state_path 自动保存,
# 可用 resume_from=state_path 恢复现场
从源码结构看,digest() 本身对单页失败相当宽容:_crawl_batch 使用 asyncio.gather(..., return_exceptions=True) 收集结果,异常的页面只打印日志并被过滤,不会向上抛错;真正可能抛出的异常来自配置校验(validate() 断言)与 CrawlState.load(状态文件损坏/缺失字段)。配合 save_state=True + state_path,即使进程被 kill,也能在下一个进程用 resume_from 继续,且恢复后会跳过已爬 URL、不重复消耗请求。
停止条件(源码级确认)
文档列出四条停止条件,源码中实际的停止闸门比这更完整。在 digest() 主循环与 StatisticalStrategy.should_stop() 中,任一条件触发即停止:
- 置信度阈值:
confidence >= confidence_threshold(默认 0.7); - 页数上限:
len(crawled_urls) >= max_pages(默认 20); - 收益递减:两个层面——饱和度
saturation >= saturation_threshold(默认 0.8),或最高分候选链接的增益ranked_links[0][1] < min_gain_threshold(默认 0.1); - 无可用链接:
pending_links为空,或评分后的候选全部已爬过; - 深度上限(源码额外保证):
depth >= max_depth(默认 5),即无论指标如何,扩展循环最多跑 5 轮。
循环正常退出后,最终置信度、pages_crawled、depth_reached 都会写入 state.metrics,可通过 state.metrics.get("confidence") 或 adaptive.confidence 属性读取,adaptive.is_sufficient 则直接给出"是否达到阈值"的布尔结论。
延伸阅读
- AdaptiveCrawler 类参考:构造函数、
confidence/coverage_stats/is_sufficient属性与知识导入导出的完整说明 - 自适应爬取指南:统计策略与 Embedding 策略的对比、
strategy="embedding"的完整配置 - 配置选项章节:进阶参数与 LLM 嵌入配置
- 基础爬取示例:仓库内可运行的官方测试脚本,覆盖基本爬取、持久化恢复、停止条件验证与爬取路径分析
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python08
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00