通过限制并发浏览器worker数量+域级队列+有界重试+快速终止卡死会话,解决CAPTCHA风暴问题,CPU和吞吐量均显著改善。
我以为我需要更好的隐匿策略。
实际上我需要的是减少混乱。
我的爬虫 agent 同时打开了太多 Playwright 会话,触发了 CAPTCHAs,重试过于激进,然后把整个工作流的其余部分也拖累了。CPU 飙升,吞吐量反而变差,下游 LLM 步骤开始处理垃圾数据和重复数据。
修复方案并不高明:
限制活跃的浏览器 worker 数量 按域名控制请求速率 快速 kill 卡住的会话
一个 5 个活跃 + 5 个排队的配置,远胜过我那 50 个标签页的混乱局面。
如果你在 agent、n8n 工作流、Make 场景、Zapier 自动化、OpenClaw 或自定义 Node/Python 管道中运行浏览器自动化,这一点比人们承认的更重要。
真正的问题不是 CAPTCHA 页面
我一直怪 Cloudflare、DataDome 和挑战页面。
它们确实是问题的一部分。
但更大的问题是自找的负载。
一个 worker 被封禁 它立即重试 又有十个 worker 访问同一个域名 旧浏览器会话存留时间超过应有的时长 CPU 和内存被已经注定失败的会话消耗殆尽 其余的 agent 管道在他们后面排队
这就是死亡螺旋。
在日志里看起来像是目标站点的问题。
很多时候,这是你自己的并发模型的问题。
为什么 50 个浏览器 worker 通常会让事情更糟
人们把浏览器并发当作原始吞吐量来处理。
使用 Playwright 或 Puppeteer 时,每个额外的浏览器或上下文都会增加压力:
向同一域名发送更多突发流量 更多重试同时发生
这意味着两件坏事同时发生:
你对目标来说看起来更像机器人了 你自己的浏览器栈变得不稳定
第二点让我更意外。
我料到反机器人系统会不爽。
我没料到自己的基础设施这么快就成为瓶颈。
更低的并发可能更快
这是大多数爬虫教程略过的部分。
如果每个浏览器会话都有足够的 CPU 和内存,页面完成会更可靠。更少的超时。更少的半死会话。更少的重试。更好的吞吐量。
所以,没错,5 个健康的 worker 绝对能打败 50 个吵闹的 worker。
这听起来是反直觉的,直到你见过一台机器花一半时间在照看卡住的 Chromium 进程。
爬虫不是直线加速赛。
它是交通工程。
队列是功能,不是可有可无的
我找到的最好的心智模型很简单:
允许工作达到并发上限
这比假装每个请求都值得立即执行要好得多。
有界的队列把过载变成延迟,而不是崩溃。
没有队列,突发流量会变成:
下游更多重复工作
有了队列,你的系统保持可读。
如果你的第一直觉是 20,试着用 5。
这里是一个 Browserless 示例:
docker run -p 3000:3000 \
-e "TOKEN=my-secure-token" \
-e "CONCURRENT=5" \
-e "QUEUED=5" \
-e "TIMEOUT=300000" \
registry.browserless.io/browserless/browserless/enterprise:latest
一个真正的超时预算给长页面
这一个改动就已经强制了更好的行为。
队列化是你阻止峰值变成重试风暴的方式。
如果你的浏览器服务满了,让请求等待。
如果队列满了,用类似 429 的明确方式拒绝,然后在上游处理。
这比假装系统有无限容量要好得多。
全局速率限制有帮助。
按域名限速帮助更大。
如果十个 worker 同时紧密突发地猛锤同一个主机,你会被发现。
这里是一个合理的 Crawlee 入门配置:
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
maxConcurrency: 5,
minConcurrency: 1,
maxRequestsPerMinute: 60,
sameDomainDelaySecs: 2,
maxRequestRetries: 2,
retryOnBlocked: true,
useSessionPool: true,
proxyConfiguration,
requestHandler: async ({ page, request, log }) => {
log.info(`Processing ${request.url}`);
// scrape here
},
});
这不会神奇地打败强指纹识别。
但它会阻止你表现得像一个穿着风衣的拒绝服务攻击。
无限重试是有额外步骤的预算泄漏。
两次重试通常就够了。
在那之后,你通常只是在花钱确认网站仍然不喜欢你。
async function fetchWithBoundedRetry(task, retries = 2) {
let lastError;
for (let attempt = 0; attempt <= retries; attempt++) {
try {
return await task();
} catch (err) {
lastError = err;
if (attempt < retries) {
await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
}
}
}
throw lastError;
}
不花哨。非常有效。
这一点很无聊但非常重要。
如果页面崩溃了,关闭它。如果上下文用完了,关闭它。如果浏览器卡住了,kill 它。
每个挂起的会话都在从有用工作中偷走 CPU 和内存。
基本的 Playwright 卫生习惯:
const browser = await playwright.chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(url, { timeout: 30000 });
// scrape
} finally {
await page.close().catch(() => {});
await context.close().catch(() => {});
await browser.close().catch(() => {});
}
很多"反机器人不稳定"实际上是清理债务。
真正重要的设置
如果你在调优浏览器自动化,这些控制比另一个泛泛的"使用隐匿策略"更有用。
这些设置直接映射到真实的故障模式。
maxConcurrency 保持 worker 数量有界 sameDomainDelaySecs 减少对单一主机的突发性 maxRequestsPerMinute 塑造全局压力 maxRequestRetries 防止 thrash retryOnBlocked 让你不同地处理反机器人响应 useSessionPool 帮助身份轮换而不完全随机
这如何破坏 AI agent 工作流
这是我最关心的部分。
一个不稳定的浏览器步骤不会保持隔离。
在 n8n、Make、Zapier、OpenClaw 或自定义 agent 中,浏览器失败会扩散到整个管道。
一个典型流程是这样的:
Playwright 收集页面 提取步骤清理内容 GPT-5.4、Claude Opus 4.6 或 Grok 4.20 对其进行分类或丰富 结果写入数据库或发送到另一个系统
现在想象浏览器层运行着 50 个会话并被封禁。
你不仅仅是得到更多 CAPTCHA 页面。
下游重复的提取作业 半截页面被总结 整个工作流中队列深度增长 运维人员将问题归咎于模型层,而实际上这是三层之前的浏览器层问题
我见过团队追逐"LLM 不稳定",而根本原因是三步之前失控的浏览器并发。
这就是为什么稳定的吞吐量胜过虚假的峰值吞吐量。
当你在为模型使用付费时,这为什么更重要
如果你的管道把浏览器重试扇出到更多 LLM 调用,成本问题会很快变得丑陋。
一个糟糕的浏览器并发设置会产生:
重复的分类 在封禁页面上重复丰富
这就是一个爬虫错误如何变成 AI 计费错误。
这是我喜欢 Standard Compute 的扁平费率模式的原因之一,用于 agent 工作流。如果你正在跨 GPT-5.4、Claude Opus 4.6 和 Grok 4.20 路由工作,最后一件事你想要的是按 token 定价在你遇到重试风暴和吵闹的自动化时惩罚你。
Standard Compute 提供 OpenAI 兼容 API 与可预测的月度定价,这对于长时间运行的 agent 比不断盯着每次工作流变得混乱时的 token 消耗要好。
显然,你仍然想修复浏览器层。
但可预测的 AI 成本让整个系统在维护时压力小得多。
一个实用的起点
如果你的爬虫现在不稳定,我会从这里开始:
active browser sessions: 5
queued sessions: 5
same-domain delay: 2s
max requests per minute: 60
max retries: 2
session cleanup: aggressive
跟踪:
每个成功页面的 LLM 调用数
最后一指标在 agent 管道中非常重要。
如果每个成功页面的 LLM 调用数在上升,你的浏览器层可能在制造垃圾工作。
什么时候一次一个浏览器会话是正确的答案
有时候正确的并发是 1。
如果你在处理:
激进的信誉评分 高摩擦的反机器人系统
那么一次一个页面可能是明智的选择。
教训不是"总是并行化"。
教训是:永远不要让并发变得隐式。
选择一个上限。测量它。调整它。
有界并行不能解决什么
这不是一个神奇的绕过。
如果一个站点有强指纹识别、挑战系统或可靠的信誉评分,更低的并发不会让它们消失。
你需要:
更强的会话管理 挑战页面处理 人类验证的备用路径
但有界并行给了你非常宝贵的东西:
一个优雅失败而不是灾难性崩溃的系统。
无聊的修复才是真正的修复
我寻找更聪明的规避方法。
最先有帮助的远不那么令人兴奋:
更少的活跃浏览器 明确的队列限制 在需要的地方使用会话池和代理轮换
一旦我停止同时淹没目标和我自己的基础设施,CAPTCHAs 没有消失。
它们只是停止增殖了。
这就改变了整个工作流的经济学。
浏览器层变得更平静了。队列更理智了。下游 LLM 步骤停止处理那么多垃圾。agent 开始表现得像一个服务而不是恐慌发作。
如果你只记住一件事,那就是:
敌人不仅仅是 CAPTCHA 页面。它是反馈循环,被阻止的会话触发更多吵闹的会话,进而触发更多下游自动化工作。
用有界的 worker 和队列打破那个循环,你整个 agent 栈的运行就会容易得多。