如何在 Crawlee 中使用 CapSolver 绕过 reCAPTCHA 和 Turnstile
Source: Dev.to
请提供您希望翻译的具体文本内容(文章正文、代码块说明等),我将按照要求保留原始的 Markdown 格式和技术术语,仅翻译正文部分为简体中文。谢谢!
TL;DR
现代网页抓取使用 Crawlee 时,经常会被激进的 CAPTCHA 挑战阻断。通过集成 CapSolver,可以编程方式绕过 reCAPTCHA、Turnstile 以及其他反机器人机制,使抓取工作流保持稳定并实现全自动。
在使用 Crawlee 等库开发强大的网络爬虫时,遇到 CAPTCHA(完全自动化公共图灵测试,用于区分计算机和人类)是不可避免的。激进的防机器人服务——包括 Google 的 reCAPTCHA 和 Cloudflare 的 Turnstile——旨在阻止自动化访问,常常让即使是最先进的 Playwright 或 Puppeteer 爬虫也陷入停滞。
本指南提供了一种实用、代码为中心的方法,将 CapSolver 与 Crawlee 集成,以自动检测并绕过这些常见的 CAPTCHA 类型。我们将在页面上下文中直接注入解决方案令牌,使爬虫能够像人类完成挑战一样继续执行。
Crawlee 是一个功能强大的开源网页抓取和浏览器自动化库,适用于 Node.js。它旨在创建可靠、可投入生产的爬虫,能够模拟人类行为并规避基础的机器人检测。
功能
| 功能 | 描述 |
|---|---|
| 统一 API | 为快速基于 HTTP 的爬取(Cheerio)和完整浏览器自动化(Playwright/Puppeteer)提供单一接口。 |
| 反机器人隐身 | 内置自动浏览器指纹生成和会话管理功能,使行为看起来更像真人。 |
| 智能队列 | 持久化请求队列管理,支持广度优先或深度优先爬取。 |
| 代理轮换 | 与代理提供商无缝集成,实现 IP 轮换,避免被封锁。 |
Crawlee 的优势在于能够处理复杂的导航,但当遇到硬性的 CAPTCHA 障碍时,需要使用外部服务。
CapSolver 是一家领先的 CAPTCHA 绕过服务,利用 AI 快速、准确地破解各种挑战。它提供简洁的 REST API,非常适合集成到像 Crawlee 这样的自动化工作流中。
CapSolver 支持多种挑战
- reCAPTCHA v2(复选框和隐形)
- reCAPTCHA v3(基于评分)
- Cloudflare Turnstile
- AWS WAF
3️⃣ 核心集成:设置 CapSolver 服务
1️⃣ 安装所需的包
npm install crawlee playwright axios
# or
yarn add crawlee playwright axios
2️⃣ 创建服务类(capsolver-service.ts)
// capsolver-service.ts
import axios from 'axios';
const CAPSOLVER_API_KEY = 'YOUR_CAPSOLVER_API_KEY';
interface TaskResult {
status: string;
solution?: {
gRecaptchaResponse?: string;
token?: string;
};
errorDescription?: string;
}
class CapSolverService {
private apiKey: string;
private baseUrl = 'https://api.capsolver.com';
constructor(apiKey: string = CAPSOLVER_API_KEY) {
this.apiKey = apiKey;
}
/** 1️⃣ Create a new CAPTCHA task and return the task ID */
async createTask(taskData: object): Promise {
const response = await axios.post(`${this.baseUrl}/createTask`, {
clientKey: this.apiKey,
task: taskData,
});
if (response.data.errorId !== 0) {
throw new Error(`CapSolver error: ${response.data.errorDescription}`);
}
return response.data.taskId;
}
/** 2️⃣ Poll the API until the task is ready or fails */
async getTaskResult(taskId: string, maxAttempts = 60): Promise {
for (let i = 0; i {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/** 3️⃣ Bypass reCAPTCHA v2 */
async bypassReCaptchaV2(websiteUrl: string, websiteKey: string): Promise {
const taskId = await this.createTask({
type: 'ReCaptchaV2TaskProxyLess',
websiteURL: websiteUrl,
websiteKey,
});
const result = await this.getTaskResult(taskId);
return result.solution?.gRecaptchaResponse ?? '';
}
/** 4️⃣ Bypass Cloudflare Turnstile */
async bypassTurnstile(websiteUrl: string, websiteKey: string): Promise {
const taskId = await this.createTask({
type: 'AntiTurnstileTaskProxyLess',
websiteURL: websiteUrl,
websiteKey,
});
const result = await this.getTaskResult(taskId);
return result.solution?.token ?? '';
}
/** 5️⃣ Bypass reCAPTCHA v3 */
async bypassReCaptchaV3(
websiteUrl: string,
websiteKey: string,
pageAction = 'submit'
): Promise {
const taskId = await this.createTask({
type: 'ReCaptchaV3TaskProxyLess',
websiteURL: websiteUrl,
websiteKey,
pageAction,
});
const result = await this.getTaskResult(taskId);
return result.solution?.gRecaptchaResponse ?? '';
}
}
export const capSolver = new CapSolverService();
Source: …
核心逻辑概述
- 检测 页面上的 CAPTCHA 元素。
- 提取
data-sitekey和页面 URL。 - 调用 相应的
capSolver.bypass…方法获取 token。 - 注入 返回的 token 到隐藏的表单字段中。
- 提交 表单以继续爬取过程。
注意:
- reCAPTCHA v2 通常以复选框形式出现。token 必须注入到隐藏的 “ 中。
- Cloudflare Turnstile 使用不同的隐藏输入字段(
cf-turnstile-response)。
reCAPTCHA v2 示例
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Processing ${request.url}`);
// 1️⃣ Detect reCAPTCHA v2 element
const hasRecaptcha = await page.$('.g-recaptcha');
if (hasRecaptcha) {
log.info('reCAPTCHA v2 detected, initiating bypass...');
// 2️⃣ Extract the site key
const siteKey = await page.$eval(
'.g-recaptcha',
el => el.getAttribute('data-sitekey')
);
if (siteKey) {
// 3️⃣ Get the bypass token from CapSolver
const token = await capSolver.bypassReCaptchaV2(request.url, siteKey);
// 4️⃣ Inject the token into the hidden textarea
await page.$eval(
'#g-recaptcha-response',
(el: HTMLTextAreaElement, t: string) => {
// Optional: make it visible for debugging
el.style.display = 'block';
el.value = t;
},
token
);
// 5️⃣ Submit the form
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
log.info('reCAPTCHA v2 successfully bypassed!');
}
}
// Continue with data extraction…
const title = await page.title();
await Dataset.pushData({ title, url: request.url });
},
});
await crawler.run(['https://example.com/protected-page']);
Cloudflare Turnstile 示例
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Processing ${request.url}`);
// 1️⃣ Detect Turnstile widget
const hasTurnstile = await page.$('.cf-turnstile');
if (hasTurnstile) {
log.info('Cloudflare Turnstile detected, initiating bypass...');
// 2️⃣ Extract the site key
const siteKey = await page.$eval(
'.cf-turnstile',
el => el.getAttribute('data-sitekey')
);
if (siteKey) {
// 3️⃣ Get the bypass token
const token = await capSolver.bypassTurnstile(request.url, siteKey);
// 4️⃣ Inject token into the hidden input
await page.$eval(
'input[name="cf-turnstile-response"]',
(el: HTMLInputElement, t: string) => {
el.value = t;
},
token
);
// 5️⃣ Submit the form
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
log.info('Turnstile successfully bypassed!');
}
}
// Continue with data extraction…
const title = await page.title();
await Dataset.pushData({ title, url: request.url });
},
});
await crawler.run(['https://example.com/turnstile-protected']);
动态 CAPTCHA 检测与绕过(生产级)
与其为每种 CAPTCHA 类型编写单独的处理器,不如创建一个实用工具,自动检测挑战并调用相应的绕过方法。
// ------------------------------------------------
// Types & Helper
// ------------------------------------------------
interface CaptchaInfo {
type: 'recaptcha-v2' | 'recaptcha-v3' | 'turnstile' | 'none';
siteKey: string | null;
}
// ------------------------------------------------
// Detection
// ------------------------------------------------
async function detectCaptcha(page: any): Promise {
// reCAPTCHA v2
const recaptchaV2 = await page.$('.g-recaptcha');
if (recaptchaV2) {
const siteKey = await page.$eval(
'.g-recaptcha',
(el: Element) => el.getAttribute('data-sitekey')
);
return { type: 'recaptcha-v2', siteKey };
}
// Turnstile
const turnstile = await page.$('.cf-turnstile');
if (turnstile) {
const siteKey = await page.$eval(
'.cf-turnstile',
(el: Element) => el.getAttribute('data-sitekey')
);
return { type: 'turnstile', siteKey };
}
// reCAPTCHA v3 (identified by script tag)
const recaptchaV3Script = await page.$('script[src*="recaptcha/api.js?render="]');
if (recaptchaV3Script) {
const scriptSrc = await recaptchaV3Script.getAttribute('src') ?? '';
const match = scriptSrc.match(/render=([^&]+)/);
const siteKey = match ? match[1] : null;
return { type: 'recaptcha-v3', siteKey };
}
// No known CAPTCHA found
return { type: 'none', siteKey: null };
}
// ------------------------------------------------
// Bypass & Injection
// ------------------------------------------------
async function bypassAndInject(
page: any,
url: string,
captchaInfo: CaptchaInfo
): Promise {
if (!captchaInfo.siteKey || captchaInfo.type === 'none') return;
let token: string;
switch (captchaInfo.type) {
case 'recaptcha-v2':
token = await capSolver.bypassReCaptchaV2(url, captchaInfo.siteKey);
await page.$eval(
'#g-recaptcha-response',
(el: HTMLTextAreaElement, t: string) => {
el.style.display = 'block';
el.value = t;
},
token
);
break;
case 'recaptcha-v3':
token = await capSolver.bypassReCaptchaV3(url, captchaInfo.siteKey);
await page.$eval(
'input[name="g-recaptcha-response"]',
(el: HTMLInputElement, t: string) => {
el.value = t;
},
token
);
break;
case 'turnstile':
token = await capSolver.bypassTurnstile(url, captchaInfo.siteKey);
await page.$eval(
'input[name="cf-turnstile-response"]',
(el: HTMLInputElement, t: string) => {
el.value = t;
},
token
);
break;
}
// Submit the form (generic selector – adjust if needed)
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
在爬虫中使用这些助手
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Processing ${request.url}`);
// Detect any supported CAPTCHA
const captchaInfo = await detectCaptcha(page);
if (captchaInfo.type !== 'none') {
log.info(`${captchaInfo.type} detected, bypassing...`);
await bypassAndInject(page, request.url, captchaInfo);
log.info(`${captchaInfo.type} successfully bypassed!`);
}
// Continue wi
> **Source:** ...
```javascript
// 常规数据提取
const title = await page.title();
await Dataset.pushData({ title, url: request.url });
},
});
await crawler.run(['https://example.com/mixed-protected']);
生产就绪爬虫的要点
- 错误处理 – 将绕过调用包装在
try/catch中并实现重试。 - 会话管理 – 重用已认证的会话以避免重复的挑战。
- 动态检测 –
detectCaptcha辅助函数让您只需最少的代码更改即可支持新的 CAPTCHA 类型。 - 日志与监控 – 保持详细日志(检测到的类型、成功/失败),便于调试。
使用这些模式,您可以稳健地处理 reCAPTCHA v2、reCAPTCHA v3、Cloudflare Turnstile,并在出现新挑战时进行扩展。
使用 CapSolver 与 Crawlee 绕过 CAPTCHA
以下是一份简明指南,介绍如何在基于 Crawlee(使用 Playwright)的爬虫中集成 CapSolver。代码片段可直接复制粘贴,说明文字已重新排版以提升清晰度,同时保留了原始信息。
1. 注入 Turnstile 令牌
// ... inside your request handler
case 'turnstile':
// Get the token from CapSolver
const token = await capSolver.bypassTurnstile(url, captchaInfo.siteKey);
// Inject the token into the hidden input field
await page.$eval(
'input[name="cf-turnstile-response"]',
(el: HTMLInputElement, t: string) => { el.value = t; },
token
);
break;
}
// Submit the form after the token has been injected
const submitBtn = await page.$('button[type="submit"], input[type="submit"]');
if (submitBtn) {
await submitBtn.click