跳到主要内容

多页爬取:深爬与自适应

前面讲的都是「爬一页」。本章讲怎么从一页扩到整站,以及更聪明的「爬到信息够了就自动停」。

1. 这一章解决什么

单页 arun 只处理你给的那个 URL。但很多任务是「爬整个文档站」「把某主题的相关页都收集」。两种思路:

思路怎么停适合
深度爬取(deep crawl)到达 max_depth / max_pages「把这站点按结构遍历一遍」
自适应爬取(adaptive)「信息够了」就停「为回答某查询,收集够就行,别浪费」

2. 深度爬取

2.1 怎么接入主线

回忆 01-pipeline.md §2:arunDeepCrawlDecorator 包过(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 / ContentRelevanceFilterSEO/相关性

打分器 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-220coverage_thresholdembedding_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.pyDeepCrawlDecoratorDeepCrawlStrategy
BFS 遍历crawl4ai/deep_crawling/bfs_strategy.pyBFSDeepCrawlStrategy.link_discoverycan_process_url
最佳优先crawl4ai/deep_crawling/bff_strategy.pyBestFirstCrawlingStrategy
过滤链crawl4ai/deep_crawling/filters.pyFilterChainURLPatternFilterDomainFilter
URL 打分crawl4ai/deep_crawling/scorers.pyKeywordRelevanceScorerCompositeScorer
自适应状态crawl4ai/adaptive_crawler.pyCrawlStateAdaptiveConfig
置信度计算crawl4ai/adaptive_crawler.pyStatisticalStrategy.calculate_confidence_calculate_coverage