验证
托管浏览器会自行处理滑块和复选框验证。验证求解服务默认关闭,需要你主动开启,用来处理它们处理不了的验证。
滑块验证
有些网站会返回“滑动验证”:把滑块拖到条的末端,或把拼图块拖进缺口。托管浏览器会替你处理这些验证,任何标签页都可以, 框架(frame)内也可以。默认开启;如果你的脚本自己处理,请传入 solveSliders: false。
- 它做什么。普通滑块会被拖到末端。拼图会一直拖到拼图块落进缺口:浏览器先在拼图图片中找到缺口, 再检查拼图块实际移动到了哪里并进行修正。拖动是按住鼠标、以人类速度进行的移动,而不是瞬间跳过去。
- 何时不出手。如果无法有把握地找到缺口,它会保持拼图原样而不是去猜,因为一次错误的拖动可能对会话不利。 每个页面最多尝试三次;有人通过实时画面控制会话时,或在交给真人处理期间,它会等待。
- 你能看到什么。每次尝试都会作为一个
slider事件记录在会话的事件时间线中, 说明验证之后是否消失(passed)或没有消失(failed),或者拼图为何保持原样(skipped)。 - 只处理验证:当页面或滑块周围的组件表明这是验证时(验证码或安全验证),滑块才被视为验证。范围输入框、轮播和价格筛选器不会被触碰。 你的脚本自身的鼠标输入可能与正在进行的拖动交织在一起,所以验证出现在屏幕上时请暂停点击,或者关闭这个选项。
复选框验证
有些网站会先要求勾选“验证你是真人”的复选框。托管浏览器会替你点击它,任何标签页都可以,框架内也可以,包括那些对页面脚本隐藏复选框的组件。 这是独立于滑块的选项,同样默认开启;如果要自己处理这些复选框,请传入 solveCheckboxes: false。
- 哪些复选框。只有自身文字表明是真人验证的复选框(真人、机器人、验证码、验证你是……)。 “记住我”或“我同意条款”之类的复选框永远不会被点击,无论页面其他地方写了什么。
- 如何点击。以人类速度把指针移到复选框,稍作停顿,再按下,按住的时长与真人点击相同。 如果复选框随后弹出滑块,则按滑块处理。
- 你能看到什么。每次尝试都会作为一个
checkbox事件记录在会话的事件时间线中: 复选框消失或被勾选时为passed,仍在等待时为failed。每个页面最多尝试三次; 有人通过实时画面控制会话时,或在交给真人处理期间,不会尝试。
验证求解服务
对于在免费的滑块和复选框操作之后仍不断弹出验证的网站,托管浏览器可以把更难的验证交给求解服务处理。除非你主动开启,否则它处于关闭状态:传入 challengeService: true 让它自行选择,或传入一个对象来选择它可以做什么。它使用你自己的求解服务密钥,或使用我们的密钥(按解出的验证计费)。
选择要处理哪些验证
你无需告诉它网站使用的是哪种组件或哪家厂商。托管浏览器会根据页面上每个验证的标记、框架和行为自行识别它,并将其归入五个类别之一。你选择的是类别,而不是组件。
| 类别 | 哪些属于此类 | auto 中包含 |
|---|---|---|
token | 向页面交付令牌(token)的组件:会变成图片验证的“我不是机器人”复选框、弹出了图片验证的隐形组件,以及一直未作答的交互式“请验证你是真人”检查。 | 是 |
clearance | 整页的“正在检查你的浏览器”等待页,且不会自行消失。 | 是 |
block-page | 网站带有谜题或设备检查的拦截页。 | 是 |
image | 文本框旁的扭曲文字验证码,以及浏览器自己无法确定缺口位置的拼图。 | 是 |
score | 页面自行运行的隐形评分检查。 | 否:需自行列出 |
- 自动选择。
challengeService: true,或不含categories的对象(或设为categories: "auto"):在所有网站上,它识别出的每个属于token、clearance、block-page和image类别的验证都会被处理。score不包含在内,因为浏览器本身已经能获得自己的评分令牌:改由外部作答,每次调用都要花费一次求解,所以只有在某个网站拒绝你会话的评分时才值得这样做。 - 选择类别。
categories: ["token", "image"]只处理所列类别。其他类别的验证仍会在时间线中报告(记为category_off),但从不收费。 - 选择网站。
sites: ["shop.example"]把它限制在这些主机及其子域名上;在其他网站上既不报告,也不请求。与categories结合使用,即可实现“在那个网站上处理这一类验证”。 - 不确定网站用的是什么?先用
mode: "report"运行一次:不请求、不付费,时间线会把页面出现过的每个验证列为challenge.detected,并附带它的category以及能否求解。然后只为该网站开启这些类别。
// In the body of POST /api/v1/browsers, or in the SDK's launch options (cloud: true)
// Auto-select: every challenge it recognises (all categories but score), on every site
challengeService: true
// Only widgets that hand out a token, only on one site, at most EUR 0.20 per session
challengeService: { categories: ["token"], sites: ["shop.example"], maxSpendEur: 0.2 }
// Only full-page waits and block pages, with your own solving-service key
challengeService: { categories: ["clearance", "block-page"], key: "own" }
// Auto-select plus the score check, on one site
challengeService: { categories: ["token", "clearance", "block-page", "image", "score"], sites: ["shop.example"] }
// First find out what a site uses: report only, nothing asked or paid for
challengeService: { mode: "report" }所有字段
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
categories | "auto" 或由 token、score、clearance、block-page、image 组成的列表 | "auto" | 可以作答哪些类型的验证。auto 即除 score 以外的全部类别。 |
sites | 主机名列表 | 所有网站 | 仅限这些主机及其子域名(最多 50 个)。 |
key | "own" 或 "managed" | 保存过密钥就用你的,否则用我们的 | 使用谁的求解服务密钥:你的,或我们的(按解出的验证计费)。 |
apiKey | 字符串 | 无 | 仅用于本会话的你的求解服务密钥(加密存储,之后不会再显示)。隐含 key: "own"。 |
mode | "solve" 或 "report" | "solve" | report 只识别并报告;不请求、不付费,也不需要密钥。 |
maxSolves | 整数,1 到 100 | 10 | 每个会话的请求次数;达到该次数即停止请求。 |
maxSpendEur | 数字,0.01 到 100 | 0.50 | 一个会话可在求解上花费的金额(使用我们的密钥时即你支付的金额);达到后即停止请求。 |
工作方式
- 先用免费操作。滑块和复选框操作总是先执行。只有在验证已经有过充分机会后仍然存在时,才会请求服务:出现了图片验证、隐形组件弹出了谜题、组件约 15 到 25 秒仍未作答、整页等待约 20 秒仍未消失,或者页面是拦截页或文字验证码。
- 答案如何填入。按页面期望的方式填入:令牌写入组件的响应字段并触发其回调,为网站设置 cookie 并重新加载,把验证码输入到对应的输入框,把拼图拖到位。之后会像免费操作一样检查验证是否已消失。
- 同一个 IP。整页等待和拦截页必须从使用答案的那个 IP 求解:服务会通过你会话自己的出口 IP、借助一条短时且一次性的连接来求解。网站拒绝的令牌会以同样的方式再请求一次。
- 你的密钥或我们的。
key: "own"使用你自己的求解服务密钥:在仪表盘(Settings)中保存一次,或在请求中传入apiKey。密钥加密存储,之后不会再显示,也不会进入浏览器;费用由你直接支付给服务。key: "managed"使用我们的密钥:每个解出的验证按服务自身的价格 × 1.5 从你的托管浏览器余额中扣费,失败的尝试免费。省略key时,如果你保存过密钥就用你的,否则用我们的。 - 限制。每个页面上的每个验证最多请求 2 次。每个会话有
maxSolves(默认 10,最多 100)和maxSpendEur(默认 €0.50):达到任一上限即停止请求。使用我们的密钥时,每个账户还有月度上限(在仪表盘中设置,最高 €25)。密钥错误或服务端余额为空时,该会话将停止请求。 - 你能看到什么。事件时间线中,每个页面上的每个验证记录一次
challenge.detected,每次请求记录一次challenge.service(solved、failed或skipped,并附带原因和服务费用);会话上有challenges(用的是谁的密钥、请求了多少次、我们密钥的求解花费)。在智能体任务中,服务处理页面期间智能体会等待。 - 有些组件能被识别但不会被求解:它们的答案无法以通用方式写回页面。这些组件会以
solvable: false报告,且从不收费。页面地址和验证的详细信息会发送给求解服务。
每种验证会怎样处理
| 页面上的情况 | 类别 | 向服务请求的内容 | 答案如何填入 |
|---|---|---|---|
| 点击后打开了图片验证的“我不是机器人”复选框 | token | 该组件的令牌 | 写入组件的响应字段,并用它调用页面的回调 |
| 弹出了图片验证的隐形组件 | token | 该组件的令牌 | 同上 |
| 一直未作答的交互式“请验证你是真人”检查 | token | 该组件的令牌 | 同上 |
| 文本框旁的扭曲文字验证码 | image | 图片中的文字 | 先点击输入框,再逐键输入 |
| 浏览器自己无法放准的拼图滑块(例如带有诱饵缺口的那种) | image | 缺口的位置 | 浏览器把拼图块拖到那里,并检查它实际落在了哪里 |
| 迟迟不消失的整页“正在检查你的浏览器”等待页 | clearance | clearance cookie,通过你会话的出口 IP 求解 | 为网站设置该 cookie,然后重新加载页面 |
| 带有谜题或设备检查的拦截页 | block-page | cookie,通过你会话的出口 IP 求解 | 为网站设置该 cookie,然后重新加载页面 |
页面自行运行的隐形评分检查(仅在列出 score 时) | score | 针对页面自身操作的评分令牌 | 交给页面自己获取令牌的调用 |
免费操作仍然优先:普通滑块直接拖动,浏览器能找到缺口的拼图直接放好,点击即可勾选的复选框直接点击,这些都不会请求服务。在组件中,填入令牌后复选框本身可能仍未勾选:页面读取的是响应字段和回调,而不是复选框。
通过 SDK 使用
SDK 0.38.0 及更高版本支持在云端启动时传入此选项(Python 中为 challenge_service,它还接受 max_solves、max_spend_eur 和 api_key)。时间线会显示发生了什么;最后几条事件会在会话结束几秒后到达。
import { Cloud, launch } from "clearcote";
const browser = await launch({
cloud: true,
challengeService: { categories: ["token", "image"], sites: ["shop.example"], maxSpendEur: 0.2 },
});
const page = await browser.newPage();
await page.goto("https://shop.example/signup");
// ... your script: challenges the free actions cannot clear are answered for you
const id = browser.cloudSession.id;
await browser.close();
const { events } = await new Cloud().browsers.events(id);
for (const e of events) if (e.type.startsWith("challenge.")) console.log(e.type, e.data);
// challenge.detected { category: "token", solvable: true }
// challenge.service { category: "token", outcome: "solved", applied: "callback", attempt: 1, ms: 12725, costUsd: 0.00013 }from clearcote import Cloud, launch
browser = launch(cloud=True, challenge_service={"categories": ["token", "image"], "sites": ["shop.example"], "max_spend_eur": 0.2})
page = browser.new_page()
page.goto("https://shop.example/signup")
session_id = browser.cloud_session["id"]
browser.close()
for e in Cloud().browsers.events(session_id)["events"]:
if e["type"].startswith("challenge."):
print(e["type"], e["data"])