在 Scrapy 下载器中间件中使用 solvethree

作者:solvethree阅读需 1 分钟

使用异步下载器中间件,仅为爬虫明确标记的请求解题。在工作线程中运行同步 solvethree SDK,然后返回包含令牌、代理和返回的浏览器身份的 FormRequest。

该示例处理一个已配置的 reCAPTCHA Enterprise 表单端点。它不会检测验证挑战,也不会探查任意网站期望如何接收令牌。

本页内容

启用中间件前配置目标

  • 使用 Scrapy 2.19+、Python 3.10+ 和官方 solvethree Python SDK。该 SDK 是同步的,没有原生异步客户端。
  • 在控制台中为工作区创建并启用站点配置。发送主机名和操作不会创建站点配置。请参阅站点配置设置。
  • 将 TARGET_URL 设置为确切的表单提交 URL,将 SOLVETHREE_PROXY 设置为此应用请求使用的代理,并将 SOLVETHREE_API_KEY 设置为工作区密钥。请将密钥保留在服务端。
  • 如下所示,启用 AsyncioSelectorReactor,并以优先级 740 注册中间件。这会将其置于优先级为 750 的 HttpProxyMiddleware 之前。

仅为明确标记的请求解题

Scrapy · 仅对明确标记的表单请求求解
# middlewares.py; Scrapy 2.19+, Python 3.10+, official solvethree SDK.
import asyncio
import os
from urllib.parse import urlsplit
from scrapy.http import FormRequest
from solvethree import Solvethree

class CaptchaMiddleware:
    async def process_request(self, request):
        if not request.meta.get("solvethree_form") or request.meta.get("solvethree_applied"):
            return None
        target = os.environ["TARGET_URL"]
        if request.url != target:
            raise ValueError("Only the configured form target may use this middleware")
        proxy = os.environ["SOLVETHREE_PROXY"]
        def solve():
            return Solvethree(os.environ["SOLVETHREE_API_KEY"]).solve({
                "site": urlsplit(target).hostname, "action": "login", "proxy": proxy,
                "requestId": request.meta["solvethree_request_id"],
            }, timeout=180)
        result = await asyncio.to_thread(solve)
        solution = result["solution"]
        fields = dict(request.meta["solvethree_fields"])
        fields["g-recaptcha-response"] = solution["token"]
        meta = {**request.meta, "proxy": proxy, "solvethree_applied": True,
                "dont_retry": True, "dont_redirect": True}
        return FormRequest(target, formdata=fields, callback=request.callback,
            errback=request.errback, headers={"User-Agent": solution["userAgent"],
                "sec-ch-ua": solution["secChUa"]}, meta=meta, dont_filter=True)

# settings.py:
# TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
# DOWNLOADER_MIDDLEWARES = {"myproject.middlewares.CaptchaMiddleware": 740}
# The spider first loads its form/CSRF data with the same proxy and cookiejar.
# Then yield a Request(TARGET_URL, callback=your_result_handler, meta={
#     "solvethree_form": True, "solvethree_request_id": one_saved_logical_job_id,
#     "solvethree_fields": {"csrf": token_from_the_form, "email": "your-test-value"},
#     "cookiejar": your_cookiejar_id,
# })
# Inspect the final response in your_result_handler; a ready task is not that response.

中间件的 process_request 方法仅接收 request,不使用已弃用的 spider 参数。未标记的请求会原样通过。已标记的请求必须与 TARGET_URL 完全匹配;不匹配时会抛出错误,而不是将令牌发送到其他端点。

asyncio.to_thread 让同步 SDK 的网络调用和轮询在事件循环线程之外执行。仅将 process_request 声明为 async 并不能做到这一点。SDK 会创建任务并等待结果就绪,或抛出异常;随后中间件构建表单提交请求。

匹配应用实际提交的表单

  1. 1.

    设置正确的操作和令牌字段

    示例使用 login 操作,并将 solution.token 放入 g-recaptcha-response。请根据目标的集成方式修改这些值。任务的 site 取自 TARGET_URL 的主机名;reCAPTCHA 任务不接受 type 或 url 字段。省略 recaptchaType 时会选择 enterprise-v3。

  2. 2.

    提供现有表单字段

    爬虫必须在 solvethree_fields 中填入端点要求的字段,包括当前的 CSRF 值。中间件会复制这些字段并添加令牌;它不会加载表单或提取隐藏输入字段。

  3. 3.

    保持会话一致

    使用应用要求的同一个代理和浏览器身份加载初始表单。在已标记请求的元数据中传入其 cookiejar ID。返回的 FormRequest 会保留该 cookiejar,并使用 solution.userAgent 和 solution.secChUa,但示例不会准备或修复之前的会话。

  4. 4.

    处理应用响应

    提供用于检查表单提交是否成功的 callback,以及处理请求失败的 errback。任务就绪表示已生成解决结果,并不表示网站接受了后续请求。

本示例特意使用同一个代理进行解题和提交。不要在解题后将其替换为不相关的代理。还要检查任何会改写 User-Agent 或客户端提示的自定义下载器中间件:它不得覆盖随解决结果返回的身份。

避免重试触发再次解题

在 yield 已标记请求之前保存 solvethree_request_id,并在同一次逻辑解题过程中保留它。requestId 接受 1–64 个 ASCII 字母、数字、下划线或连字符。使用完全相同的任务输入重复使用它,可恢复现有任务;保留该 ID 却更改输入会发生冲突。

返回的 FormRequest 会设置 solvethree_applied,以免再次触发解题。它还会设置 dont_retry 和 dont_redirect,防止该次提交自动重试和重定向。这些标志不会阻止您自己的 callback 或作业运行器发起另一个请求,因此请明确决定是否这样做。

解题调用超时。

确认是否已创建任务。连接超时并不能证明创建失败。

试试这样做: 创建新的逻辑求解请求之前,先获取已知任务,或使用保存的 requestId 和完全相同的任务输入恢复任务。

求解时抛出异常,而不是返回结果。

区分 HTTP 请求错误和已进入终态的任务失败。对于失败的任务,检查 errorCode 和 error。

试试这样做: 处理失败原因,不要反复 yield 同一个已标记的请求。重试前先修正无效输入;任务失败后必须停止轮询。

网站拒绝了提交的表单。

先检查发出请求中的令牌字段、CSRF 字段、Cookie、代理和浏览器身份。

试试这样做: 再次付费求解之前,先修正应用请求。该字段必须包含 solution.token,而不是任务 ID 或过期令牌。

运行您的第一个任务。

使用 $5 工作区额度开始体验。

创建工作区
在 Scrapy 下载器中间件中使用 solvethree · solvethree