如何在 Crawlee 中使用 CapSolver 绕过 reCAPTCHA 和 Turnstile

发布: (2025年12月24日 GMT+8 16:47)
10 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容(文章正文、代码块说明等),我将按照要求保留原始的 Markdown 格式和技术术语,仅翻译正文部分为简体中文。谢谢!

TL;DR

现代网页抓取使用 Crawlee 时,经常会被激进的 CAPTCHA 挑战阻断。通过集成 CapSolver,可以编程方式绕过 reCAPTCHA、Turnstile 以及其他反机器人机制,使抓取工作流保持稳定并实现全自动。

在使用 Crawlee 等库开发强大的网络爬虫时,遇到 CAPTCHA(完全自动化公共图灵测试,用于区分计算机和人类)是不可避免的。激进的防机器人服务——包括 Google 的 reCAPTCHA 和 Cloudflare 的 Turnstile——旨在阻止自动化访问,常常让即使是最先进的 Playwright 或 Puppeteer 爬虫也陷入停滞。

本指南提供了一种实用、代码为中心的方法,将 CapSolverCrawlee 集成,以自动检测并绕过这些常见的 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:

核心逻辑概述

  1. 检测 页面上的 CAPTCHA 元素。
  2. 提取 data-sitekey 和页面 URL。
  3. 调用 相应的 capSolver.bypass… 方法获取 token。
  4. 注入 返回的 token 到隐藏的表单字段中。
  5. 提交 表单以继续爬取过程。

注意:

  • 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
Back to Blog

相关文章

阅读更多 »

仅依赖静态代码审查的代价

什么是Static code review?Static code review 是在不执行代码的情况下分析 source code 的过程。其目标是通过检查 source code 来识别问题。