1.5 调度纪律:伦理与法律边界 本节摘要:合规不是教程附录,而是调度室的纪律条令。本节给出三层边界——robots 协议与站点意愿、服务条款与合同义务、法律法规(个人信息保护、数据安全、著作权与反不正当竞争)——并落到两段可直接复用的工程实践:robots 解析与频控自证。 红线在哪里 先看一个反复上演的反例。某团队接了个"聚合比价"的活,目标站点明确写了禁止自动化采集,团队用高匿代理加浏览器伪装绕过了封禁,两周抓了数百万页。结局是对方从日志里还原出采集行为模式,律师函、下线、诉讼接踵而至,项目负责人花在应对纠纷上的时间超过了开发本身。技术上说,绕过手段并不高明;法律上说,"明知禁止而绕过"这个事实本身就是最不利的那份证据。 这类故事的教训不是"爬虫危险",而是红线有位置、有层次。
本节摘要:合规不是教程附录,而是调度室的纪律条令。本节给出三层边界——robots 协议与站点意愿、服务条款与合同义务、法律法规(个人信息保护、数据安全、著作权与反不正当竞争)——并落到两段可直接复用的工程实践:robots 解析与频控自证。
先看一个反复上演的反例。某团队接了个"聚合比价"的活,目标站点明确写了禁止自动化采集,团队用高匿代理加浏览器伪装绕过了封禁,两周抓了数百万页。结局是对方从日志里还原出采集行为模式,律师函、下线、诉讼接踵而至,项目负责人花在应对纠纷上的时间超过了开发本身。技术上说,绕过手段并不高明;法律上说,"明知禁止而绕过"这个事实本身就是最不利的那份证据。
这类故事的教训不是"爬虫危险",而是红线有位置、有层次。调度室的纪律条令可以画成三层,从软到硬:
第一层:robots 协议与站点意愿。 robots.txt 是站点对自动化访问的公开声明,虽不是法律文件,但它是"尊重站点意愿"的技术表达,也是纠纷发生时评判善意的重要参考。同行共识还包括:标明真实 UA、控制请求频率、避开业务高峰。
第二层:服务条款与合同义务。 大多数站点的用户协议包含自动化访问限制,注册账号抓取、绕过付费墙、模拟登录后采集,都可能同时违反合同义务。这一层的违约责任与账号封禁是实打实的。
第三层:法律法规。 个人信息保护相关的法律要求收集个人信息有合法基础、最小必要并脱敏;数据安全相关法律关注重要数据的境内存储与出境;著作权法保护内容的汇编与商用;反不正当竞争法则盯着"实质性替代他人服务"的采集行为。三层叠加,红线就立体了。

把第一关做成代码,是让纪律可执行而不是靠自觉。Python 标准库的 robotparser 足够胜任:
from urllib import robotparser def load_robots(site: str) -> robotparser.RobotFileParser: """读取并解析站点的 robots 协议""" rp = robotparser.RobotFileParser() rp.set_url(site.rstrip("/") + "/robots.txt") rp.read() # 网络读取;站点无 robots 时返回空规则,默认允许 return rp rp = load_robots("https://example-shop.com") print(rp.can_fetch("MyCrawler/1.0", "https://example-shop.com/p/1001")) # 输出:True 或 False,取决于该站 robots 对该路径的约定 print(rp.crawl_delay("MyCrawler/1.0")) # 输出:2(站点建议的抓取间隔秒数;无约定则为 None)
两处工程细节值得较真。其一,UA 要用真实可联系的标识(含团队邮箱或文档页),而不是伪装成浏览器——伪装在第一层就输掉了善意。其二,crawl_delay 的返回值应该直接喂给调度器的节流参数,让"遵守协议"从口号变成配置项,2.2 节的频控实现会接上这条线。
第二段实践是频率与负载的自我约束。判例里常见的认定逻辑是"采集行为对目标站点造成了实质性负担",所以调度室要能自证克制。一段最小的自证记录器:
import time, collections class PolitenessLimiter: """按域名记录最近请求时间,保证同站间隔不低于下限""" def __init__(self, min_interval: float = 2.0): self.min_interval = min_interval self.last_seen: dict[str, float] = collections.defaultdict(float) def wait_for(self, domain: str) -> float: """返回本次应对该域名等待的秒数,并更新记录""" elapsed = time.monotonic() - self.last_seen[domain] wait = max(0.0, self.min_interval - elapsed) time.sleep(wait) self.last_seen[domain] = time.monotonic() return wait limiter = PolitenessLimiter(min_interval=2.0) w1 = limiter.wait_for("example-shop.com") # 首次无需等待 w2 = limiter.wait_for("example-shop.com") # 立刻第二次,触发约 2 秒等待 print(w1, round(w2, 1)) # 输出:0.0 2.0
把每次 wait 的返回值连同时间戳写进日志,就得到了"我们一直保持两秒间隔"的自证材料。加上避开目标站业务高峰(多数站点的低峰在当地时间凌晨)、失败退避、总请求量上限,纪律就从抽象原则落成了工程参数。
第三关过不全时,不是直接放弃,而是加动作。涉及个人昵称、头像、地理位置的,入库前做匿名化——用不可逆哈希替换标识符,保留统计价值、去掉可识别性。涉及文章正文商用授权不明的,优先使用摘要加链接指向原文的形态服务检索,而非全文复制。这两条在 3.3 节的清洗流水线里会再次出现,届时它们是流水线上的固定工位。
第一个坑:把 robots 返回 404 当成"禁止抓取"——恰恰相反,robots 文件缺失按惯例视为不设限,但此时更应主动收紧频率、压低并发;真正要停下的是 robots 里明确 Disallow 的路径。第二个坑:UA 写了真实标识却被浏览器内核的默认值覆盖——开启浏览器模式后,内核自带的 UA 会盖掉配置项,上线前用请求回显服务核对最终发出的 User-Agent,是唯一可靠的检查办法。第三个坑:把"公开可见"等同于"可以商用"——页面无需登录即可见,只说明第一层无障碍,著作权与个人信息两层的动作一项都不能省。三坑的共性是把某一层边界的通过误当成全部放行;拿上面的合规决策路径图逐层走一遍,每层都要有自己的放行证据。
调度纪律讲完了,第一章的任务单要素齐备。第 2 章进入引擎车间:Crawl4AI 怎么安装、一单任务在引擎里怎么走完全程、排班与分拣如何落地。