5.3 会思考的调度:智能爬取技术


文档摘要

5.3 会思考的调度:智能爬取技术 本节摘要:智能爬取把调度决策从"人写死的规则"升级为"系统按反馈自调"——频率随响应信号自适应换挡、边界队列按主题相关性重排、抽取 schema 借大模型辅助生成。三者共同的工程原则是:机器调参,人守边界。 从固定规则到自适应状态机 前两章的调度参数全是静态的:间隔两秒、深度两层、并发八个。静态规则的问题是环境会变——同一个站点,工作日白天与周末凌晨的承压能力不同,改版前后响应特性不同。凌晨还是两秒间隔,浪费吞吐;高峰仍两秒,可能已经在给对方添堵。自适应频率的思路是把 2.

5.3 会思考的调度:智能爬取技术

本节摘要:智能爬取把调度决策从"人写死的规则"升级为"系统按反馈自调"——频率随响应信号自适应换挡、边界队列按主题相关性重排、抽取 schema 借大模型辅助生成。三者共同的工程原则是:机器调参,人守边界。

从固定规则到自适应状态机

前两章的调度参数全是静态的:间隔两秒、深度两层、并发八个。静态规则的问题是环境会变——同一个站点,工作日白天与周末凌晨的承压能力不同,改版前后响应特性不同。凌晨还是两秒间隔,浪费吞吐;高峰仍两秒,可能已经在给对方添堵。自适应频率的思路是把 2.4 节读到的响应信号接回调度器:正常就升挡、429 或超时就降挡、持续正常再试探回升,一个典型的有限状态机:

状态机的代码骨架不复杂,复杂的是换挡纪律——升挡要慢(连续成功才升)、降挡要快(一次失败就降):

import time from dataclasses import dataclass, field @dataclass class AdaptiveThrottle: """按响应信号自适应的抓取间隔控制器""" intervals: dict = field(default_factory=lambda: {"常速": 2.0, "提速": 1.2, "降速": 6.0}) state: str = "常速" streak: int = 0 floor: float = 1.0 # 纪律下限:再怎么提速也不低于1秒 ceiling: float = 900.0 # 冷静上限:15分钟封顶 def interval(self) -> float: return self.intervals.get(self.state, 2.0) def observe(self, signal: str) -> float: """喂入响应信号,更新状态,返回下一次应等待的秒数""" if signal == "429" or signal == "timeout": self.streak = 0 self.state = {"常速": "降速", "提速": "常速", "降速": "冷静期"}.get(self.state, "冷静期") wait = min(self.interval(), self.ceiling) if self.state == "冷静期" else self.interval() time.sleep(min(wait, 20)) # 演示环境截断等待;生产按真实值睡 return wait self.streak += 1 if self.state == "降速" and self.streak >= 50: self.state, self.streak = "常速", 0 elif self.state == "常速" and self.streak >= 20: nxt = self.intervals["常速"] * 0.6 if nxt >= self.floor: # 纪律下限兜底:不许无限提速 self.intervals["提速"] = nxt self.state, self.streak = "提速", 0 return self.interval() th = AdaptiveThrottle() for sig in ["ok"] * 21 + ["429", "ok", "429", "429"]: pass # 逐条喂入即可 for sig in ["ok"] * 21: th.observe(sig) print(th.state, th.interval()) # 输出:提速 1.2(连续正常后升挡) th.observe("429") print(th.state, th.interval()) # 输出:常速 2.0(提速挡遇429立即回落) for _ in range(3): th.observe("429") print(th.state) # 输出:冷静期(连续失败触发最长退避)

注意 floor 参数的位置:自适应不等于无限制,纪律下限(这里是 1 秒)写死在控制器里,机器能调的只是"下限之上的区间"。这是本节反复出现的模式——机器调参,人守边界

聚焦爬取:让队列学会挑食

第二个智能化的位置是边界队列。2.2 节的优先级调度靠人写规则(列表页 10 分、详情页 3 分),聚焦爬取把打分交给主题相关性:URL 与锚文本对任务关键词的相关度越高,出队越早。这不改变"抓什么"的目标,只改变"先抓什么"的次序,深站归档任务的效率提升常常来自这里:

import math from collections import Counter class FocusedFrontier: """按主题相关性重排的边界队列""" def __init__(self, topic_terms: list[str]): self.topic = set(topic_terms) self.queue: list[tuple[float, int, str]] = [] self.seq = 0 def _score(self, url: str, anchor: str) -> float: text = (url + " " + anchor).lower() terms = [t for t in text.replace("/", " ").replace("-", " ").split() if t] overlap = sum(1 for t in terms if any(k in t for k in self.topic)) # 平滑加位置衰减:路径深的URL略降权,避免钻进无底洞 depth = url.count("/") - 2 return overlap / math.log(len(terms) + 2) - 0.05 * depth def push(self, url: str, anchor: str = ""): import heapq heapq.heappush(self.queue, (-self._score(url, anchor), self.seq, url)) self.seq += 1 def pop_best(self, n: int = 3) -> list[str]: out = [] while self.queue and len(out) < n: out.append(heapq.heappop(self.queue)[2]) return out f = FocusedFrontier(["battery", "固态电池", "电解质"]) f.push("https://news.example/energy/solid-state-battery-breakthrough", "固态电池新突破") f.push("https://news.example/entertainment/celebrity-news", "明星动态") f.push("https://news.example/energy/battery-recycle-policy", "电池回收政策出台") print(f.pop_best(2)) # 输出:['https://news.example/energy/solid-state-battery-breakthrough', # 'https://news.example/energy/battery-recycle-policy'] # 解读:娱乐页相关性得分为负相关,沉底;两条能源页按重叠度排出先后

打分函数刻意保持了朴素:词面重叠加深度衰减,可解释、可调试。升级路径是接向量相似度(把 URL 加锚文本与主题描述都 embed 后算余弦),效果更好但引入了模型依赖——按任务规模决定,几万页的任务词面打分足够。

大模型辅助:两个高杠杆的用法

第三类智能来自大模型本身,两个已经过实践检验的用法。schema 生成:把目标页面的 HTML 片段与字段需求喂给模型,让它起草 JsonCssExtractionStrategy 的 schema,人只做校对——对新站点的冷启动提速明显,特别是字段命名不规则的老网站。页面分类:抓回的页面按"高价值正文、导航壳、垃圾页"分类,分类结果反馈给聚焦爬取的打分器,形成采集质量的闭环。用法边界同样清晰:模型输出只做"草稿与建议",进生产管线的每一行 schema、每一个分类阈值都经人工确认——3.5 节防偏见传导的逻辑在这里完全适用。

智能调度收工。系统会自己调参之后,人盯什么?下一节的值班室回答这个问题:看板上的三类信号、告警的分级、以及值班响应的动作手册。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U