广告验证脚本卡住,十有八九不是解析逻辑写错了,而是发布商页面弹出了验证码。广告验证团队每天要访问成百上千个页面,核对广告位置、品牌安全和地域定向是否达标,而很多发布商站点正好用验证码拦截这类批量访问。用 CaptchaAI 的 API 自动识别这些验证码,验证流水线就不会在半路断掉。
广告验证到底在查什么,验证码又是怎么挡路的
| 检查项 | 具体内容 | 验证码为什么会挡路 |
|---|---|---|
| 广告位置 | 广告是否展示在首屏可见区域? | 自动化访问页面会触发机器人检测 |
| 品牌安全 | 广告旁边不能出现违规或有害内容 | 批量 URL 核查在行为上很像抓取 |
| 可见度 | 广告是不是真的被用户看到? | 无头浏览器容易被 Cloudflare 标记 |
| 地域定向 | 广告投放地区是否正确? | 代理流量本身就容易触发验证码 |
| 竞品监控 | 竞争对手投的是什么广告? | 高频率的广告查询看起来像滥用行为 |
用 Python + CaptchaAI API 自动识别验证码,跑通广告位检测
下面这段脚本会访问发布商页面,遇到 reCAPTCHA 就调用 CaptchaAI 的 in.php / res.php 接口拿到 token,再把识别结果和检测到的广告标签、品牌安全关键词一起写进报告:
import requests
import time
import re
import json
import os
from datetime import datetime
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
def solve_captcha(method, params):
params["key"] = API_KEY
params["method"] = method
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(resp.text)
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)[1]
raise Exception(result.text)
raise TimeoutError()
def verify_ad_placement(url, session):
"""Verify ad placement on a publisher page."""
resp = session.get(url)
# Solve CAPTCHA if present
match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text)
if match:
token = solve_captcha("userrecaptcha", {
"googlekey": match.group(1),
"pageurl": url,
})
resp = session.post(url, data={"g-recaptcha-response": token})
html = resp.text
# Check for ad elements
result = {
"url": url,
"timestamp": datetime.utcnow().isoformat(),
"ads_found": [],
"brand_safety": True,
"captcha_solved": match is not None,
}
# Detect ad tags
ad_patterns = [
(r'googletag\.pubads', "Google Ad Manager"),
(r'doubleclick\.net', "DFP/DoubleClick"),
(r'ad\.doubleclick', "DoubleClick"),
(r'amazon-adsystem', "Amazon Ads"),
(r'criteo\.com/.*\.js', "Criteo"),
]
for pattern, name in ad_patterns:
if re.search(pattern, html):
result["ads_found"].append(name)
# Brand safety check — flag problematic content
safety_keywords = [
"violence", "hate speech", "explicit",
"gambling", "illegal",
]
page_text = re.sub(r'<[^>]+>', '', html).lower()
for keyword in safety_keywords:
if keyword in page_text:
result["brand_safety"] = False
break
return result
def run_verification(urls, output_file="verification_report.json"):
"""Run ad verification across multiple publisher URLs."""
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0"
)
results = []
for i, url in enumerate(urls):
try:
result = verify_ad_placement(url, session)
results.append(result)
ads = ", ".join(result["ads_found"]) or "None"
safe = "SAFE" if result["brand_safety"] else "UNSAFE"
print(f" [{i+1}/{len(urls)}] {url}: {ads} [{safe}]")
except Exception as e:
results.append({
"url": url,
"error": str(e),
"timestamp": datetime.utcnow().isoformat(),
})
print(f" [{i+1}/{len(urls)}] {url}: ERROR - {e}")
time.sleep(2)
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
# Summary
total = len(results)
safe = sum(1 for r in results if r.get("brand_safety"))
captchas = sum(1 for r in results if r.get("captcha_solved"))
errors = sum(1 for r in results if "error" in r)
print(f"\n Total: {total} | Safe: {safe} | CAPTCHAs solved: {captchas} | Errors: {errors}")
return results
# Publisher URLs to verify
publisher_urls = [
"https://publisher1.com/article/tech-news",
"https://publisher2.com/sports/latest",
"https://publisher3.com/finance/markets",
]
run_verification(publisher_urls)
整个流程跑完会生成一份 JSON 报告,标出每个页面命中了哪些广告标签、是否触发了品牌安全关键词、验证码有没有被成功识别。
Cloudflare 防护下的发布商页面怎么处理
不少优质发布商都挂了 Cloudflare,你需要同时处理 Turnstile 和完整的 Cloudflare Challenge:
def handle_cloudflare(url, session):
"""Handle Cloudflare-protected publisher pages."""
resp = session.get(url)
if "cf-turnstile" in resp.text:
match = re.search(r'data-sitekey=["\']([^"\']+)', resp.text)
if match:
token = solve_captcha("turnstile", {
"sitekey": match.group(1),
"pageurl": url,
})
return session.post(url, data={
"cf-turnstile-response": token,
})
if resp.status_code == 403 and "cf-browser-verification" in resp.text:
data = solve_captcha("cloudflare_challenge", {
"pageurl": url,
"proxy": "user:pass@proxy:port",
"proxytype": "HTTP",
})
# Parse qa_session_cookie and use same proxy
return data
return resp
一个真实场景:出海广告主怎么用它做批量核查
国内做海外投放的广告运营团队,经常要抽查投给 Google Ads、DSP 的广告在境外发布商站点上展示成什么样——这些站点大多在美国、欧洲或东南亚,验证码几乎是标配。常见验证码类型分布:
- 境外发布商站点:reCAPTCHA v2/v3、Cloudflare Turnstile
- 国内发布商站点:GeeTest(极验)v3
CaptchaAI 支持 reCAPTCHA v2/v3、Cloudflare Turnstile/Challenge、GeeTest v3 等主流类型,一套 API 覆盖境内外大部分场景,不必为每种验证码单独接一家服务商。
如果一次要跑几千个发布商页面,按线程数计费的套餐比按次计费划算——该选哪一档,看你的页面并发量,而不是总的验证码次数:
| 套餐 | 月费 | 线程数 | 适合场景 |
|---|---|---|---|
| ADVANCE | $90 | 50 | 中等规模出海广告核查 |
| CORPORATE | $240 | 150 | 大规模多地区批量核查 |
每个线程内解决次数不限,量再大可以上更高档位的 VIP 系列。
常见问题
hCaptcha 这类验证码,CaptchaAI 能处理吗?
目前 CaptchaAI 不支持 hCaptcha 和 FunCaptcha(Arkose Labs)。已支持的主流类型包括 reCAPTCHA v2/v3、Cloudflare Turnstile 和 Challenge、GeeTest v3、图片和九宫格验证码等。选型之前先确认目标发布商用的是哪一种。
大批量核查发布商页面,应该选哪个套餐?
按并发线程数选,而不是按验证码数量,具体测算见上一节的出海场景。
视频广告的验证码要怎么处理?
这套方案主要针对展示广告和原生广告。视频广告验证通常需要真实渲染页面,建议配合 Selenium 或 Playwright 打开浏览器后再触发验证码识别。
按地区批量验证广告投放,要注意什么?
换成目标地区的代理再发起请求。CaptchaAI 的接口支持传入代理参数,识别结果会和你的地域定向保持一致,不会因为出口 IP 跟目标地区不符而验证失败。
大规模抓取发布商页面会有合规风险吗?
只核查自己投放或已获授权核查的广告位,遵守目标站点的 robots 协议和使用条款,就不属于违规抓取。国内团队做这类核查时,也要留意《网络安全法》《数据安全法》里关于自动化数据采集的相关要求。
相关指南
- 抓取受保护站点的实战方法
- 代理配置的最佳实践
- 无头浏览器遇到验证码怎么办