Key Takeaways
面向生产环境的 Web Scraping 系统设计指南,重点拆分任务所有权、HTTP/浏览器抓取边界、代理与会话策略、有限重试、数据验证、证据留存和可观测性。
抓取系统真正需要设计的是“失败边界”
一个生产级 Web Scraping 系统,不应该只是“拿到 URL → 发请求 → 解析 HTML”的循环。
当任务规模扩大后,最容易失控的通常不是 HTTP 客户端本身,而是这些问题:
- 谁拥有当前任务?Worker 崩溃后谁接手?
- 哪些失败允许重试,哪些应该立即停止?
- HTTP 抓取和浏览器渲染在哪里分流?
- Cookie、浏览器 Context 和 Sticky 代理会话如何绑定?
200 OK但拿到登录页、挑战页、错误地区页面时,算成功还是失败?- 重复投递是否会产生重复数据?
- 原始页面、截图、HAR、日志要保留多久,如何避免泄露敏感信息?
所以架构目标不是“服务越多越专业”,而是让任务所有权、状态、重试、数据质量和停止条件都能被明确解释。
本文只讨论获得授权的数据采集、监控和测试场景。代理改变的是网络出口,不会自动获得访问权限,也不会消除 Cookie、账户状态、浏览器指纹、隐私要求或站点策略。
一套可落地的参考架构
这里真正重要的是边界,不是具体选 Redis、SQS、Kafka 还是某个爬虫框架。
Scrapy 的官方架构同样把 Scheduler、Downloader、Spider 和 Item Pipeline 分成不同职责;Item Pipeline 常用于清洗、验证、去重和持久化。这种分层思想比“所有逻辑塞进一个 spider”更容易测试和演进。
参考:Scrapy Architecture↗ · Item Pipeline↗
1. 先定义 Job Contract,再谈 Worker 数量
每个入队任务都应该包含足够的信息,让任何合格 Worker 都可以独立执行和审计,而不是依赖某个进程里的隐藏状态。
这里的 3 次尝试只是示例,不是推荐默认值。真正重要的是:
- 有稳定任务 ID
- 有明确 target
- 有 fetch policy
- 有 attempt budget
- 有 deadline
- 有输出 schema version
如果同一任务可能被重复消费,写入端要有幂等键。例如:
AWS SQS 的官方文档明确说明标准队列采用至少一次投递语义,因此消费者仍需要处理重复消息。即使你不用 SQS,这也是设计任务队列时应主动考虑的故障模型。
参考:Amazon SQS Visibility Timeout↗
2. 队列不只是 Buffer,而是任务所有权协议
队列至少要回答四个问题:
- 当前谁拥有这个任务?
- Worker 死掉后,所有权何时失效?
- 什么错误可以重新入队?
- 什么错误必须进入 dead-letter / quarantine?
常见模型是:
Worker 在 ack 前崩溃,lease 到期后任务可以被重新领取。
建议监控的不只是 queue depth,还包括:
- oldest job age
- in-flight jobs
- lease expiration count
- retry count by reason
- dead-letter count
- 每个 target 的排队年龄
“队列只有 20 条”并不代表健康。如果最老任务已经卡了很久,可能是某个 target、partition 或依赖持续失败。
3. HTTP 与 Browser Worker 要明确分流
不要默认把所有 URL 都扔给 Playwright。
优先用 HTTP Client 的典型场景:
- 数据直接存在于响应正文
- 有公开且允许使用的接口
- 不依赖前端 JS 状态
- 不需要浏览器 Cookie / LocalStorage / DOM 交互
只有任务确实需要客户端渲染、浏览器状态或授权交互时,再升级到 Browser Worker。
一个实用流程:
关键点是先验证再升级。
200 可能是:
- 登录页
- Challenge 页面
- 空壳 HTML
- 错误地区/语言
- 已经变更的页面模板
同样,浏览器也不是 403 的自动解决方案。
4. BrowserContext 是会话隔离边界
Playwright 将 BrowserContext 定义为隔离的浏览器会话。不同 Context 可以隔离 Cookie 等浏览器状态。
如果任务需要认证态,建议把这些状态绑定到同一个逻辑 session_key:
认证状态要按 secret 管理。Playwright 官方也提醒,保存的 storage state 可能包含能够代表账户完成操作的敏感 Cookie 和 Header。
5. 把代理选择做成 Policy Layer
代理逻辑不要散落在 spider、parser 和业务代码中。
一个独立的 routing policy 应该能决定:
- direct / proxy
- country / region / city 要求
- rotating / sticky
- session key
- 当前 route 是否健康或被 quarantine
- 某类失败是否允许换 route 重试
例如:
不要默认“住宅代理一定更好”。真正应该比较的是该 target 上的有效结果率、错误类型、地区准确性、字节成本和延迟。
更深入的路由层设计可参考:Web Scraping Proxy Architecture。
6. 先分类失败,再决定是否重试
| 信号 | 主要故障层 | 默认处理 |
|---|---|---|
407 Proxy Authentication Required | 代理认证 | 检查凭据和代理配置,不盲目换 IP |
429 Too Many Requests | 目标站限流 | 降低速率;存在 Retry-After 时按其语义处理 |
403 Forbidden | 授权 / 策略 / 安全 | 检查响应和授权边界,不把换 IP 当默认方案 |
| Connect timeout | 网络 / 代理 / 目标站 | 先做分层诊断,再进入有限重试 |
| Schema validation failure | 解析 / 页面变更 | 进入 quarantine 或更新 extractor,不用网络重试掩盖 |
| Wrong locale / market | 路由或应用状态 | 同时验证出口地区与页面级证据 |
RFC 6585 对 429 的定义也说明,服务端不一定按 IP 识别用户,还可以依据 credentials、cookie 或其他状态。因此“429 → 马上换 IP”不是可靠的通用策略。
参考:RFC 6585 §4↗
7. Retry 必须同时受次数和 Deadline 限制
只设置 max_attempts 不够,只设置 timeout 也不够。
更完整的预算是:
这样目标站持续退化时,不会出现大量旧任务永久占用 Worker。
示例:
具体次数和时间必须根据目标延迟、任务 SLA、请求成本以及重复执行风险来确定,不应该复制某篇文章里的固定数字。
8. Validation 是写入 Canonical Data 前的门禁
抓到了 HTML,不等于拿到了正确数据。
验证层至少可以检查:
- 必填字段是否存在
- 类型、币种、单位是否正确
- 时间是否可解析和标准化
- 是否命中登录/挑战/空页特征
- 当前 market 是否符合请求
- schema version 是否一致
- 是否是重复记录
一个结果可以明确拆为:
这种表达比一个全局 success=true/false 更容易排错。
9. Raw Evidence 和业务数据分开保存
推荐拆成四类:
- Raw response / browser evidence → 有生命周期策略的对象存储
- Canonical extracted data → 数据库 / Warehouse
- Job & attempt state → 运行状态存储
- Metrics → 可观测性系统
不要默认永久保存所有 HTML、Screenshot、HAR、Trace。
它们可能包含:
- Cookie
- Token
- 用户数据
- Query 参数中的签名
- 账户信息
需要明确 retention、访问权限和 secret redaction。
10. 指标要围绕“可用结果”,而不是 HTTP 200
更有价值的指标包括:
- usable-result rate
- validation-failure rate
- challenge/login/empty-page rate
- attempts per usable result
- bytes per usable result
- browser escalation rate
- p50 / p95 job duration
- queue oldest age
- lease expiration rate
- 407 / 403 / 429 / timeout 分类计数
- requested market vs observed market mismatch
如果一个请求返回 200,但产出的 SKU、价格、职位或 SERP 数据无法通过验证,它就不应计入业务成功率。
11. 上线前主动注入失败
在 staging 或明确授权的测试环境里,至少验证这些场景:
- Worker fetch 完成后、ack 前退出
- 队列重复投递
- 目标站
200返回登录页 - DOM / JSON schema 发生变化
- 代理返回
407 - 目标返回
429,分别包含和不包含Retry-After - BrowserContext / browser process 崩溃
- sticky session 中途消失
- 数据库或对象存储短时不可用
- 任务在 retry 中超过 deadline
每一种情况都应有可预测结果:
如果值班人员无法判断系统下一步会怎么做,失败策略就还没有设计完成。
生产检查清单
FAQ
所有生产爬虫都需要分布式队列吗?
不需要。小规模任务可能一个进程内 scheduler 就足够。只有在任务需要持久化、跨 Worker、故障接管或独立扩缩容时,分布式队列才值得引入。
Playwright 是否应该成为默认抓取方式?
通常不应该。只有当正确数据依赖浏览器渲染或浏览器状态时才升级到浏览器;否则 HTTP 路径更容易调试和控制资源成本。
返回 200 是否代表抓取成功?
不代表。还要验证 body、schema、market、页面类型和业务字段。
遇到 403/429 是否应该立即切换住宅代理?
不应该作为默认动作。先确认授权和错误语义,再按失败分类决定是否重试、降速、停止或变更路由。
相关 BytesFlows 工程指南
Alex Vance
核心基础设施架构团队
本文由 BytesFlows 工程团队审查。代码示例适用于合规的公开网页数据采集、QA 自动化、SEO 监测与市场研究工作流。实际基准表现可能因目标站点防爬策略、地理位置、客户端运行环境及请求频率而异。