数据截至 (上游 commit 7e801521428e)
多页爬取:深爬与自适应
前面讲的都是「爬一页」。本章讲怎么从一页扩到整站,以及更聪明的「爬到信息够了就自动停」。
1. 这一章解决什么
单页 arun 只处理你给的那个 URL。但很多任务是「爬整个文档站」「把某主题的相关页都收集」。两种思路:
| 思路 | 怎么停 | 适合 |
|---|---|---|
| 深度爬取(deep crawl) | 到达 max_depth / max_pages | 「把这站点按结构遍历一遍」 |
| 自适应爬取(adaptive) | 「信息够了」就停 | 「为回答某查询,收集够就行,别浪费」 |
2. 深度爬取
2.1 怎么接入主线
回忆 01-pipeline.md §2:arun 被 DeepCrawlDecorator 包过(deep_crawling/base_strategy.py:10)。一旦 config 里有 deep_crawl_strategy,装饰器就拦截,转去多页遍历(:17-43),用 ContextVar 防止递归。
2.2 三种遍历策略
都继承 DeepCrawlStrategy(deep_crawling/base_strategy.py:45):
| 策略 | 顺序 | 文件 |
|---|---|---|
BFSDeepCrawlStrategy | 广度优先:一层层 | bfs_strategy.py |
DFSDeepCrawlStrategy | 深度优先:钻到底再回 | dfs_strategy.py |
BestFirstCrawlingStrategy | 最佳优先:按 URL 打分挑最相关的先爬 | bff_strategy.py |
2.3 一层 BFS 怎么走
核心是 link_discovery(bfs_strategy.py:133):从一个页面的结果里抽链接,过滤 → 打分 → 排进下一层。
怎么读: 从上到下是处理一个页面产出的链接时的判断链。
link_discovery(result, ...)
│
├─ 抽出 result 里的外链
├─ can_process_url(url, depth)? 格式校验 + 过滤链 :62
│ └─ 不过 → 丢弃
├─ url_scorer.score(url) 打分(没配 scorer 则 0) :177
│ └─ score < score_threshold → 丢弃 :181
├─ 超出本层容量?→ 按分数降序取 top :189
└─ 入队 next_level + 记 depth + 把 score 写进 metadata :199
2.4 两个可插拔件:过滤链 与 打分器
过滤链 FilterChain(deep_crawling/filters.py)——决定「这个 URL 要不要爬」。内置:
| 过滤器 | 作用 |
|---|---|
URLPatternFilter | 按 URL 通配/正则 |
DomainFilter | 限定域名 |
ContentTypeFilter | 按 content-type |
SEOFilter / ContentRelevanceFilter | SEO/相关性 |
打分器 URLScorer(deep_crawling/scorers.py)——决定「先爬哪个」(给 BestFirst 用)。内置:KeywordRelevanceScorer(关键词相关)、FreshnessScorer(新鲜度)、PathDepthScorer(路径深度)、DomainAuthorityScorer 等,可用 CompositeScorer 组合。
这样「爬什么、先爬什么」全是配置出来的,不用改遍历逻辑。
3. 自适应爬取:把「够了吗」算成数学
3.1 直觉
深爬是「爬满 N 页就停」,但 N 多少算够?爬少了信息不全,爬多了浪费。AdaptiveCrawler(adaptive_crawler.py)换个思路:给定一个查询,一边爬一边问「现在的信息足够回答了吗」,够了就停。 这叫「信息觅食(information foraging)」(文件头 docstring,:1-7)。
3.2 三个停止指标
状态存在 CrawlState(adaptive_crawler.py:26):已爬 URL、知识库、词频/文档频、历史等。默认的 StatisticalStrategy.calculate_confidence(:309)用三个指标合成一个 confidence:
| 指标 | 问的问题 | 怎么算 |
|---|---|---|
| coverage | 查询词在已爬内容里覆盖得好吗? | 查询词的文档频/词频(_calculate_coverage,:328) |
| consistency | 各页对查询的回答一致吗? | _calculate_consistency |
| saturation | 新页还在带来新信息吗? | 新词增量历史(new_terms_history)趋于 0 = 饱和 |
合成公式(adaptive_crawler.py:324):
# adaptive_crawler.py:324
confidence = 0.4 * coverage + 0.3 * consistency + 0.3 * saturation
confidence 达到 AdaptiveConfig.confidence_threshold 就停止爬取。
3.3 coverage 怎么算(看一眼真章)
_calculate_coverage(adaptive_crawler.py:328-367)对每个查询词:看它在多少文档里出现(doc coverage = df / 总文档数),再用对数词频做个增益,平均起来,最后开方让分数更直观:
# adaptive_crawler.py:351-367(精简)
doc_coverage = df / state.total_documents # 多少比例的文档含此词
freq_signal = math.log(1+tf) / math.log(1+max_tf) # 归一化词频
term_score = doc_coverage * (1 + 0.5 * freq_signal)
coverage = sqrt(平均(term_scores)) # 开方让区分更明显
注意这是词面统计(TF/DF),不是语义。
3.4 语义版(embedding 策略)
纯统计不懂同义词。AdaptiveConfig 还支持 embedding 策略(:184-220 的 coverage_threshold、embedding_coverage_radius 等):把查询和已爬内容都嵌入向量空间,用几何覆盖(甚至 alpha shape,coverage_shape,:49)判断查询空间是否被覆盖、找「语义空洞」继续补。这需要 embedding 模型,更重但更准。
3.5 状态可持久化
CrawlState.save/load(adaptive_crawler.py:53-90)能把爬取状态存盘/恢复——长时间爬取崩了能续(配合 README 提到的 crash recovery / resume_state)。
4. 怎么选
要「遍历这个站点的结构」?
├─ 是 → 深爬:BFS(全面)/ DFS(钻深)/ BestFirst(挑相关先爬)
要「为回答某查询收集够就停」?
└─ 是 → AdaptiveCrawler:统计策略(免费快)或 embedding 策略(语义准、要模型)
5. 巧妙之处
- 遍历 / 过滤 / 打分三件事解耦:换「先爬哪个」只改 scorer,不动 BFS 逻辑。
- 把「爬 够了吗」量化成 coverage+consistency+saturation,而不是拍脑袋定页数——saturation(新信息趋零)尤其是个好停止信号。
- 状态可存盘续爬,长任务抗崩溃。
6. 边界
- 统计自适应是词面匹配,查询用词和页面不一致时 coverage 失真;要语义得上 embedding(更重)。
- 深爬的过滤/打分配不好容易爬偏(爬进无关分支或漏掉相关页)。
- 自适应的阈值/权重是经验值(
AdaptiveConfig默认),不同站点可能要调。
7. 代码地图
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 深爬装饰器 | crawl4ai/deep_crawling/base_strategy.py | DeepCrawlDecorator、DeepCrawlStrategy |
| BFS 遍历 | crawl4ai/deep_crawling/bfs_strategy.py | BFSDeepCrawlStrategy.link_discovery、can_process_url |
| 最佳优先 | crawl4ai/deep_crawling/bff_strategy.py | BestFirstCrawlingStrategy |
| 过滤链 | crawl4ai/deep_crawling/filters.py | FilterChain、URLPatternFilter、DomainFilter |
| URL 打分 | crawl4ai/deep_crawling/scorers.py | KeywordRelevanceScorer、CompositeScorer |
| 自适应状态 | crawl4ai/adaptive_crawler.py | CrawlState、AdaptiveConfig |
| 置信度计算 | crawl4ai/adaptive_crawler.py | StatisticalStrategy.calculate_confidence、_calculate_coverage |