Use Cases

用于验证码抓取的代理轮换

抓取脚本验证码越来越频繁,往往不是验证码本身的问题,而是同一个 IP 短时间内请求太多。把请求分散到多个 IP,能从源头降低触发概率;剩下的验证码交给 CaptchaAI 处理,两者配合才稳定。

验证码为什么会随抓取量增多

站点的风控系统主要看单个 IP 的请求模式:

  • 请求频率:单个 IP 每分钟超过 10 次就容易触发验证码,分散到多个 IP 能摊开压力。
  • IP 信誉:用得越久越容易被标记,多 IP 池能持续换新。
  • 会话模式:请求节奏都集中在一个 IP 上,行为特征更容易被识别。
  • 地理一致性:长期同一地点不如自然分布的地理位置可信。

该选哪种 IP:住宅、移动、ISP 静态还是数据中心

类型 最适合场景 验证码触发率 成本
住宅 IP 高价值目标(如 Google、亚马逊) 最低 $$$
移动网络 IP 对检测极敏感的场景 最低 $$$$
ISP 静态 IP 需要长时间保持同一会话 较低 $$
数据中心 IP 高并发、风控宽松的站点 较高 $

风控严格的站点优先用住宅或 ISP 静态 IP;风控宽松的站点,数据中心 IP 性价比更高。

例如杭州一家跨境电商团队把数据中心 IP 换成香港、新加坡住宅 IP 后,验证码拦截率从近 30% 降到个位数。抓取前建议先确认目标数据属于《网络安全法》《数据安全法》与 PIPL 下你有权采集的公开数据。

实战经验清单

做法 说明
IP 地理位置匹配目标站点 抓美区站点用美区 IP
一个 IP 对应一个会话 不要跨会话复用
控制单 IP 请求频率 每分钟 5-10 次较安全
持续监控触发率 及时剔除高触发率的 IP
多步骤流程用固定会话 登录、提交表单全程同一 IP
连接失败自动重试 超时就换下一个 IP

常见故障排查

  • 所有 IP 都触发验证码:换成住宅或 ISP 静态 IP,同时降低请求频率。
  • IP 请求超时:移出响应慢的 IP,适当调高超时时间。
  • 不同 IP 返回内容不一样:部分站点按地区返回内容,需要做归一化处理。
  • token 换 IP 后不生效:确认 token 与提交请求用的是同一个 IP。

基础轮换实现(Python)

从 IP 池里随机取一个,出现验证码就调用 CaptchaAI 解决:

import requests
import random
import time

PROXIES = [
    "http://user:[email protected]:8080",
    "http://user:[email protected]:8080",
    "http://user:[email protected]:8080",
]

API_KEY = "YOUR_API_KEY"

def get_random_proxy():
    proxy = random.choice(PROXIES)
    return {"http": proxy, "https": proxy}

def scrape_with_rotation(url):
    proxy = get_random_proxy()
    session = requests.Session()
    session.proxies = proxy
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    })

    resp = session.get(url)

    # If CAPTCHA appears, solve it
    if "g-recaptcha" in resp.text or "captcha" in resp.text.lower():
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(resp.text, "html.parser")
        rc = soup.find("div", class_="g-recaptcha")
        if rc:
            site_key = rc["data-sitekey"]
            token = solve_captcha(site_key, url)
            resp = session.post(url, data={"g-recaptcha-response": token})

    return resp.text

def solve_captcha(site_key, page_url):
    resp = requests.get("https://ocr.captchaai.com/in.php", params={
        "key": API_KEY, "method": "userrecaptcha",
        "googlekey": site_key, "pageurl": page_url
    })
    task_id = resp.text.split("|")[1]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id
        })
        if result.text == "CAPCHA_NOT_READY": continue
        if result.text.startswith("OK|"): return result.text.split("|")[1]
        raise Exception(result.text)
    raise TimeoutError()

进阶:按验证码触发率打分,自动避开高风险 IP

固定顺序轮换效果有限——更好的做法是记录每个 IP 的历史表现,优先选表现好的:

from collections import defaultdict
import random

class SmartProxyRotator:
    def __init__(self, proxies):
        self.proxies = proxies
        self.captcha_count = defaultdict(int)
        self.success_count = defaultdict(int)

    def get_proxy(self):
        # Prefer proxies with lower CAPTCHA rates
        scored = []
        for proxy in self.proxies:
            total = self.captcha_count[proxy] + self.success_count[proxy]
            if total == 0:
                score = 0.5  # Unknown proxy, neutral score
            else:
                score = self.success_count[proxy] / total
            scored.append((proxy, score))

        # Weight selection by score
        scored.sort(key=lambda x: x[1], reverse=True)
        top_proxies = scored[:max(len(scored) // 2, 1)]
        proxy = random.choice(top_proxies)[0]
        return proxy

    def report_success(self, proxy):
        self.success_count[proxy] += 1

    def report_captcha(self, proxy):
        self.captcha_count[proxy] += 1

# Usage
rotator = SmartProxyRotator(PROXIES)

def scrape(url):
    proxy = rotator.get_proxy()
    resp = requests.get(url, proxies={"http": proxy, "https": proxy})

    if "captcha" in resp.text.lower():
        rotator.report_captcha(proxy)
        # Solve CAPTCHA...
    else:
        rotator.report_success(proxy)

    return resp.text

Selenium 场景下的 IP 切换

Selenium 抓取同样可以按会话切换 IP:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def create_driver_with_proxy(proxy_url):
    options = Options()
    options.add_argument(f"--proxy-server={proxy_url}")
    options.add_argument("--no-sandbox")
    return webdriver.Chrome(options=options)

# Rotate proxy per session
proxy = random.choice(PROXIES)
driver = create_driver_with_proxy(proxy)
driver.get("https://example.com")

Cloudflare 验证码:为什么必须锁定同一个 IP

Cloudflare 验证码是个例外——签发的 qa_session_cookie 与请求 IP 绑定,中途换 IP 会导致验证失效:

proxy = "http://user:[email protected]:8080"

resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": API_KEY,
    "method": "cloudflare_challenge",
    "pageurl": "https://example.com",
    "proxy": proxy,
    "proxytype": "HTTP"
})
task_id = resp.text.split("|")[1]

# Poll for qa_session_cookie cookie
# Use the same proxy for subsequent requests

常见问题

免费代理能用来做验证码抓取吗?

不建议,免费 IP 信誉普遍偏低,更容易触发验证码,还常在请求中途掉线。

IP 应该多久轮换一次?

没有固定答案,可先按每 20-50 次请求轮换一次,再按触发率调整。

解决验证码用的 IP,必须和抓取用的 IP 一致吗?

多数类型(reCAPTCHA、Turnstile、GeeTest v3 等)token 与 IP 无关。但 Cloudflare 验证码例外——qa_session_cookie 与 IP 绑定,必须同一个 IP。

所有 IP 都频繁触发验证码,说明什么?

通常是站点收紧了风控,或 IP 段信誉较差。优先换住宅或 ISP 静态 IP,并降低请求频率。

相关阅读

该文章已禁用评论。