换了住宅 IP,验证码怎么还在弹?因为代理和识别是两层:Bright Data(原 Luminati,7200 万+住宅 IP)负责换 IP,真弹出 reCAPTCHA v2 或 Turnstile 时还是要 CaptchaAI 拿 token。跨境电商团队抓美区、欧区商品数据常用这个组合:定向换 IP,命中验证码就转交 CaptchaAI。
代理和验证码识别,谁负责什么
Your Script ──▶ Bright Data Proxy ──▶ Target Site
│
CAPTCHA appears
│
CaptchaAI API ──▶ Solved token
│
Inject token ◀─┘
CaptchaAI 不经过你的代理,用自己的服务器识别。代理管抓取,CaptchaAI 管识别:
- 代理层:换 IP、做地区定向,降低触发率。
- 识别层:验证码弹出后,把 sitekey 和页面 URL 交给 CaptchaAI,拿回 token。
Python 集成:从抓取到自动识别验证码
Requests + Bright Data + CaptchaAI
import requests
import time
CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"
# Bright Data proxy credentials
BRIGHT_DATA_PROXY = {
"http": "http://brd-customer-CUSTOMER_ID-zone-ZONE:[email protected]:22225",
"https": "http://brd-customer-CUSTOMER_ID-zone-ZONE:[email protected]:22225",
}
def fetch_with_proxy(url):
"""Fetch a page through Bright Data proxy."""
resp = requests.get(
url,
proxies=BRIGHT_DATA_PROXY,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/126.0.0.0 Safari/537.36"
},
timeout=30,
)
return resp
def solve_recaptcha(site_url, sitekey):
"""Solve reCAPTCHA v2 via CaptchaAI."""
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
"key": CAPTCHAAI_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": site_url,
"json": 1,
})
data = resp.json()
if data["status"] != 1:
raise Exception(f"Submit: {data['request']}")
task_id = data["request"]
for _ in range(60):
time.sleep(5)
resp = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY,
"action": "get",
"id": task_id,
"json": 1,
})
data = resp.json()
if data["request"] == "CAPCHA_NOT_READY":
continue
if data["status"] == 1:
return data["request"]
raise Exception(f"Solve: {data['request']}")
raise TimeoutError("Solve timeout")
def solve_turnstile(site_url, sitekey):
"""Solve Cloudflare Turnstile via CaptchaAI."""
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
"key": CAPTCHAAI_KEY,
"method": "turnstile",
"sitekey": sitekey,
"pageurl": site_url,
"json": 1,
})
data = resp.json()
task_id = data["request"]
for _ in range(60):
time.sleep(5)
resp = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY,
"action": "get",
"id": task_id,
"json": 1,
})
data = resp.json()
if data["request"] != "CAPCHA_NOT_READY":
return data["request"]
raise TimeoutError("Solve timeout")
Selenium + Bright Data + CaptchaAI
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
BRIGHT_DATA_HOST = "brd.superproxy.io"
BRIGHT_DATA_PORT = 22225
BRIGHT_DATA_USER = "brd-customer-CUSTOMER_ID-zone-residential"
BRIGHT_DATA_PASS = "PASSWORD"
def create_driver_with_proxy():
options = webdriver.ChromeOptions()
options.add_argument(
f"--proxy-server=http://{BRIGHT_DATA_HOST}:{BRIGHT_DATA_PORT}"
)
options.add_argument("--no-sandbox")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
return driver
def scrape_with_captcha_solving(url, sitekey=None):
driver = create_driver_with_proxy()
try:
driver.get(url)
time.sleep(3)
# Auto-detect sitekey if not provided
if not sitekey:
sitekey = driver.execute_script(
"return document.querySelector('[data-sitekey]')"
"?.getAttribute('data-sitekey')"
)
if sitekey:
token = solve_recaptcha(url, sitekey)
driver.execute_script(f"""
document.querySelector('#g-recaptcha-response').value = '{token}';
document.querySelectorAll('[name="g-recaptcha-response"]')
.forEach(el => {{ el.value = '{token}'; }});
""")
# Trigger callback
driver.execute_script("""
if (typeof ___grecaptcha_cfg !== 'undefined') {
const clients = ___grecaptcha_cfg.clients;
for (const key in clients) {
for (const prop in clients[key]) {
const val = clients[key][prop];
if (val && typeof val === 'object') {
for (const p in val) {
if (typeof val[p]?.callback === 'function') {
val[p].callback(arguments[0]);
}
}
}
}
}
}
""")
return driver.page_source
finally:
driver.quit()
两段代码走的是同一套流程:
- 拉取目标页面;
- 提取
sitekey; - 转交 CaptchaAI 识别,拿到 token;
- 把 token 注入表单字段并提交。
按国家/地区定向抓取,降低验证码触发率
Bright Data 支持按国家、州、城市三级定向:
# Country targeting
proxy_us = "http://brd-customer-ID-zone-residential-country-us:[email protected]:22225"
proxy_uk = "http://brd-customer-ID-zone-residential-country-gb:[email protected]:22225"
proxy_de = "http://brd-customer-ID-zone-residential-country-de:[email protected]:22225"
# City targeting
proxy_nyc = "http://brd-customer-ID-zone-residential-country-us-city-newyork:[email protected]:22225"
# Use the geo-matched proxy for lower CAPTCHA rates
def scrape_localized(url, country="us"):
proxy = f"http://brd-customer-ID-zone-residential-country-{country}:[email protected]:22225"
resp = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
return resp
抓美区用美区 IP、抓欧区用欧区 IP,归属地对上号,比换更贵的代理类型划算。
会话保持:粘性会话还是轮换 IP?
# Sticky session (same IP for entire session)
proxy_sticky = (
"http://brd-customer-ID-zone-residential"
"-session-abc123:[email protected]:22225"
)
# Rotating (new IP each request)
proxy_rotating = (
"http://brd-customer-ID-zone-residential:[email protected]:22225"
)
验证码流程用粘性会话,同一 IP 从加载用到提交——token 和 IP 绑定,中途换 IP 会被拒绝。纯翻页任务才适合轮换 IP。
Node.js 集成
const axios = require("axios");
const https = require("https");
const CAPTCHAAI_KEY = "YOUR_API_KEY";
const CAPTCHAAI_URL = "https://ocr.captchaai.com";
const proxyAgent = new (require("https-proxy-agent"))(
"http://brd-customer-ID-zone-residential:[email protected]:22225"
);
async function fetchWithProxy(url) {
return axios.get(url, {
httpsAgent: proxyAgent,
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0.0.0",
},
});
}
async function solveCaptcha(siteUrl, sitekey) {
const submit = await axios.post(`${CAPTCHAAI_URL}/in.php`, null, {
params: {
key: CAPTCHAAI_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: siteUrl,
json: 1,
},
});
const taskId = submit.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get(`${CAPTCHAAI_URL}/res.php`, {
params: {
key: CAPTCHAAI_KEY,
action: "get",
id: taskId,
json: 1,
},
});
if (result.data.request === "CAPCHA_NOT_READY") continue;
if (result.data.status === 1) return result.data.request;
}
throw new Error("Timeout");
}
Node.js 版本调用逻辑跟 Python 一致:先取 sitekey,再交给 CaptchaAI 识别,拿到 token 后注入表单。
该选哪种代理类型,区域参数怎么配
先定类型,再配区域参数:
| 类型 | IP 数量 | 速度 | 成本 | 触发率 |
|---|---|---|---|---|
| 数据中心 | 77 万+ | 快 | 低 | 较高 |
| 住宅 | 7200 万+ | 中等 | 中等 | 较低 |
| ISP | 70 万+ | 快 | 高 | 最低 |
| 移动网络 | 700 万+ | 慢 | 高 | 很低 |
验证码较多的站点优先选住宅或 ISP 类型;数据中心 IP 便宜快,但更容易被标记。类型定好后,区域参数照下表配置:
| 配置项 | 推荐值 | 原因 |
|---|---|---|
| 代理类型 | 住宅 | 触发率最低 |
| 地区定位 | 匹配目标站点所在地 | IP 与内容地区一致 |
| 会话类型 | 粘性会话 | token 需与 IP 匹配 |
| IP 质量过滤 | 高质量 | 避开已标记 IP |
| 最大并发数 | 100+ | 支持并行识别任务 |
常见故障怎么排查
代理返回 407
凭证错误。检查 customer ID、zone、密码。
每次请求都弹验证码
命中了数据中心 IP。切换到住宅或 ISP 区域。
token 被拒绝
识别和提交之间 IP 变了。改用粘性会话。
响应变慢
出口节点拥塞。定向访问量较小的地区。
连接被拒绝
触碰带宽上限。登录控制台查看用量。
常见问题
明明用了住宅 IP,验证码为什么还是弹出来?
住宅 IP 只降触发率,不是完全不触发。稳定做法是两层都做:代理降触发率,CaptchaAI 兜底识别。
国内团队抓国外站点,reCAPTCHA 要注意什么?
reCAPTCHA 加载 Google 托管脚本,国内网络访问不稳定,常表现为组件不渲染。先判断是网络问题还是真触发验证码,Turnstile 没有这层依赖,排查更简单。
该用粘性会话还是轮换 IP?
验证码请求用粘性会话,token 和 IP 绑定,中途换 IP 会被拒绝。只有翻页任务适合轮换 IP。
多线程并发抓取,代理会话怎么分配?
建议一个线程对应一个独立粘性会话(用不同 session 参数区分),避免多线程共用同一 IP,也方便按线程排查问题。
相关阅读
- 轮换自有服务器基础设施解决验证码
- 代理质量如何影响识别成功率
- ISP 代理与数据中心代理怎么选
把 Bright Data 的代理网络和自动验证码识别接在一起——获取你的 CaptchaAI API Key,把抓取流程跑稳定。