Use Cases

保险报价比较自动化与验证码处理

车险报价页提交完信息,最后一步弹出 reCAPTCHA;健康险比价网站选完方案,又冒出图片验证码——这是测试保险比价系统的 QA 团队天天会遇到的场景。CaptchaAI 把这一步变成可预期的环节:识别验证码、拿到 token,继续跑完流程。


常见故障速查

  • 报价表单中途重置——IP 变化,改用持久 QA 会话。
  • 提示"无法提供报价"——必填字段没填全。
  • 验证码连续出现两次——token 已过期,重新识别并提交。
  • 报价结果和手动测试不一致——会话状态不同,换新会话重测。
  • 触发限流——请求过密,放慢节奏、轮换会话。

保险站点常见的验证码分布

险种 验证码类型 触发环节 报价流程复杂度
车险 reCAPTCHA v2 报价表单提交 多步骤(车辆 + 司机信息)
健康险 reCAPTCHA v2 方案比较页 多步骤(人口统计信息)
家财险 图片验证码 地址查询 中等
人寿险 reCAPTCHA v2 费率计算器 单页表单
旅游险 隐形 reCAPTCHA v2 报价请求 单页表单
商业险 reCAPTCHA v3 联系/报价表单 长表单,字段多

多承运商报价采集脚本

依次提交车辆、司机信息,在报价页解一次 reCAPTCHA,按月费排序:

import requests
import time
import re
import base64

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"


def solve_recaptcha(sitekey, pageurl):
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "userrecaptcha",
        "googlekey": sitekey, "pageurl": pageurl, "json": 1,
    })
    task_id = resp.json()["request"]
    for _ in range(60):
        time.sleep(5)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


def solve_image_captcha(image_bytes):
    img_b64 = base64.b64encode(image_bytes).decode()
    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
        "key": CAPTCHAAI_KEY, "method": "base64",
        "body": img_b64, "json": 1,
    })
    task_id = resp.json()["request"]
    for _ in range(20):
        time.sleep(3)
        result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY, "action": "get",
            "id": task_id, "json": 1,
        })
        data = result.json()
        if data["request"] != "CAPCHA_NOT_READY":
            return data["request"]
    raise TimeoutError("Timeout")


class InsuranceQuoteCollector:
    def __init__(self, proxy=None):
        self.session = requests.Session()
        if proxy:
            self.session.proxies = {"http": proxy, "https": proxy}
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
        })

    def get_auto_quote(self, carrier_url, vehicle_data, driver_data, sitekey):
        """Get auto insurance quote from a single carrier."""
        # Step 1: Load quote page
        self.session.get(carrier_url)

        # Step 2: Submit vehicle info
        resp = self.session.post(f"{carrier_url}/vehicle", data=vehicle_data)

        # Step 3: Submit driver info
        resp = self.session.post(f"{carrier_url}/driver", data=driver_data)

        # Step 4: Solve CAPTCHA on quote page
        token = solve_recaptcha(sitekey, f"{carrier_url}/quote")

        # Step 5: Get quote
        resp = self.session.post(f"{carrier_url}/quote", data={
            "g-recaptcha-response": token,
        })

        if resp.status_code == 200:
            return self._parse_quote(resp.text)
        return None

    def compare_carriers(self, carriers, vehicle_data, driver_data):
        """Compare quotes across multiple carriers."""
        quotes = []

        for carrier in carriers:
            try:
                quote = self.get_auto_quote(
                    carrier_url=carrier["url"],
                    vehicle_data=vehicle_data,
                    driver_data=driver_data,
                    sitekey=carrier["sitekey"],
                )
                quotes.append({
                    "carrier": carrier["name"],
                    "status": "success",
                    "quote": quote,
                })
            except Exception as e:
                quotes.append({
                    "carrier": carrier["name"],
                    "status": "failed",
                    "error": str(e),
                })
            time.sleep(5)  # Delay between carriers

        # Sort by price
        successful = [q for q in quotes if q["status"] == "success" and q["quote"]]
        successful.sort(key=lambda x: x["quote"].get("monthly_premium", float("inf")))

        return {
            "quotes": quotes,
            "best_rate": successful[0] if successful else None,
            "total_compared": len(carriers),
            "successful": len(successful),
        }

    def _parse_quote(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        premium_el = soup.select_one(".premium, .monthly-rate, .quote-amount")
        coverage_el = soup.select_one(".coverage-summary, .plan-details")

        return {
            "monthly_premium": premium_el.get_text(strip=True) if premium_el else "",
            "coverage": coverage_el.get_text(strip=True) if coverage_el else "",
        }


# Usage
collector = InsuranceQuoteCollector(
    proxy="http://user-session-abc:pass@residential.proxy.com:5000"
)

vehicle = {
    "year": "2022",
    "make": "Toyota",
    "model": "Camry",
    "vin": "",
    "mileage": "15000",
}

driver = {
    "age": "35",
    "gender": "M",
    "zip": "90210",
    "driving_record": "clean",
}

carriers = [
    {"name": "Carrier A", "url": "https://carrier-a.example.com/auto", "sitekey": "6Lc_xxx1"},
    {"name": "Carrier B", "url": "https://carrier-b.example.com/auto", "sitekey": "6Lc_xxx2"},
    {"name": "Carrier C", "url": "https://carrier-c.example.com/auto", "sitekey": "6Lc_xxx3"},
]

comparison = collector.compare_carriers(carriers, vehicle, driver)
print(f"Best rate: {comparison['best_rate']}")

健康险多方案比价脚本

验证码卡在提交人口统计信息之后、展示方案之前:

def compare_health_plans(marketplace_url, demographics, sitekey):
    """Compare health insurance plans on marketplace sites."""
    collector = InsuranceQuoteCollector(
        proxy="http://user-session-xyz:pass@residential.proxy.com:5000"
    )

    # Load marketplace
    collector.session.get(marketplace_url)

    # Submit demographics
    collector.session.post(f"{marketplace_url}/demographics", data=demographics)

    # Solve CAPTCHA for plan results
    token = solve_recaptcha(sitekey, f"{marketplace_url}/plans")

    resp = collector.session.post(f"{marketplace_url}/plans", data={
        "g-recaptcha-response": token,
    })

    if resp.status_code == 200:
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(resp.text, "html.parser")
        plans = []
        for card in soup.select(".plan-card, .insurance-plan"):
            plans.append({
                "name": card.select_one(".plan-name").get_text(strip=True) if card.select_one(".plan-name") else "",
                "premium": card.select_one(".premium").get_text(strip=True) if card.select_one(".premium") else "",
                "deductible": card.select_one(".deductible").get_text(strip=True) if card.select_one(".deductible") else "",
            })
        return plans

    return []

会话保持要求:为什么报价流程离不开同一个 IP

承运商服务器把每一步都绑定到同一个会话和 IP,中途换 IP 会话就失效:

  1. 个人信息——姓名、出生日期、邮编。
  2. 车辆/房产信息——年份、品牌、型号。
  3. 保障范围选择——限额、免赔额。
  4. 验证码识别——拿到 token。
  5. 报价结果展示——无额外数据。

用 TTL 10-15 分钟的持久 QA 会话覆盖这五步。

本地化场景:出境旅游险比价测试

留学中介、出境游平台对接多家国际承运商的旅游险接口,多用隐形 reCAPTCHA v2,换个表单字段即可复用同一套逻辑。


常见问题

每个承运商每天能测试多少次报价请求?

延迟合理的前提下,通常每天 20-50 次较安全,超限后限流更频繁。

reCAPTCHA v3 或隐形 reCAPTCHA 页面看不到验证码框,还需要处理吗?

需要。页面仍在后台生成 sitekey 并等待 token,提交 sitekey 和 pageurl 即可,不用等页面显示验证码框。

出境旅游险这类多承运商比价,测试节奏要注意什么?

先摸清每家承运商的触发点和步骤数,再用持久会话统一测试,承运商之间错开请求节奏。

会话中途换出口 IP,为什么整条链路都要重新提交?

因为每步都绑定同一会话和 IP,换出口 IP 会话即失效,得从第 1 步重提交。


相关指南


大规模测试保险报价比价流程——获取你的 CaptchaAI API Key,把报价采集流程变成可重复的自动化测试。

该文章已禁用评论。