如何越过验证码抓取搜索结果(Python 指南,2026)
搜索结果页是爬虫最先遇到验证码的地方之一。网站看到同一个地址发出的一连串请求,于是返回一个挑战,在挑战通过之前不给出任何结果。要越过验证码抓取搜索结果,你的脚本需要三个步骤:在解析之前先检测挑战,用一次 API 调用识别它,然后提交 token,让网站返回真正的结果页。
本指南用 requests 和 BeautifulSoup 在 Python 中演示这一流程。它涵盖显示 PopularCaptcha 控件的页面,这是 Scarnox 目前支持的类型(无代理)。请先注册账户,免费试用额度,无需信用卡,并先在你自己的页面上运行这段代码。
验证码是什么样子的
搜索页通常把控件放在一个表单中。控件容器带有 data-sitekey 属性,表单会把数据回传给网站:
<form action="/search/verify" method="post">
<input type="hidden" name="q" value="web scraping">
<input type="hidden" name="lang" value="en">
<div class="h-captcha" data-sitekey="YOUR-SITEKEY-UUID"></div>
<button type="submit">Continue</button>
</form>
你的代码需要从这段标记中获取三样东西:sitekey、表单的 action URL,以及提交 token 时必须一并回传的隐藏字段。下面的辅助函数会读取这三项,所以无需硬编码它们。
如果你的目标控件是另一种验证码类型,请参阅验证码类型。Cloudflare Turnstile 和 reCAPTCHA 的支持即将推出,因此这里的代码只涵盖 PopularCaptcha。
识别请求
识别请求发送到 Scarnox API,并在 Bearer 请求头中带上你的密钥。调用会阻塞,直到 token 准备好,因此不需要轮询循环:
import time
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
API_KEY = "scarnox_YOURAPIKEY"
SEARCH_URL = "https://search.example.com/search"
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
def solve(sitekey: str, page_url: str) -> str:
r = requests.post(
"https://api.scarnox.com/api/tasks/create",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185, # 调用会在 token 准备好后返回
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
识别失败时,errorId 不为 0,而且不会向你收费。重试相关内容见验证码识别错误处理。
获取页面并通过验证码
抓取函数会先检查控件是否存在。如果不存在,就原样返回 HTML。如果存在,就识别挑战,用相同的隐藏字段重建表单,然后提交:
def fetch_results(query: str, page: int = 1) -> str:
resp = session.get(SEARCH_URL, params={"q": query, "page": page}, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
widget = soup.select_one("[data-sitekey]")
if widget is None:
return resp.text # 此页面没有验证码
form = widget.find_parent("form")
if form is None:
raise RuntimeError("Captcha widget is not inside a form")
token = solve(widget["data-sitekey"], resp.url)
payload = {
field["name"]: field.get("value", "")
for field in form.select("input[name]")
}
payload["h-captcha-response"] = token
payload["g-recaptcha-response"] = token
action = urljoin(resp.url, form.get("action") or resp.url)
verified = session.post(action, data=payload, timeout=30)
if "data-sitekey" in verified.text:
raise RuntimeError("Challenge still shown after submit; token may be rejected")
return verified.text
这里有两个细节很关键。site_url 是验证码实际提供的 URL(即 resp.url,包含所有重定向),而不是裸的搜索端点。另外,会话会在 GET 和 POST 之间保留 Cookie,许多网站需要这些 Cookie 才会接受 token。
Token 大约两分钟后过期,所以识别应当在读取表单之后立即进行,而不是提前。关于这个时间窗口的更多信息,请参阅验证码 token 过期。
解析结果
当返回真正的结果页之后,解析就只是普通的 HTML 工作了。下面的选择器只是占位符,请在开发者工具中打开页面,并替换为真实的选择器:
def parse_results(html: str) -> list[dict]:
soup = BeautifulSoup(html, "html.parser")
rows = []
for item in soup.select("div.result"):
link = item.select_one("a[href]")
heading = item.select_one("h3")
if link and heading:
rows.append({
"title": heading.get_text(strip=True),
"url": link["href"],
})
return rows
if __name__ == "__main__":
for page in range(1, 4):
html = fetch_results("web scraping", page)
for row in parse_results(html):
print(row)
time.sleep(5) # 翻页之间控制请求间隔
请把每一页的输出分开保存。如果某页返回零条结果,先检查是否存在验证码控件,再假定查询确实没有结果。
负责任地抓取
验证码是一个信号,而不是漏洞。在对任何搜索网站运行这段代码之前:
- 阅读 robots.txt 和服务条款。 许多搜索网站禁止自动化查询。如果条款禁止,就不要运行这段脚本。
- 限制速率。 上面的
time.sleep(5)只是下限,不是目标。如果你看到更多的挑战,请进一步放慢速度。 - 限制保存的内容。 搜索结果可能包含姓名、邮箱等个人数据。只保存你需要的字段,并删除其余部分。
- 网站提供官方 API 时优先使用它。 它比抓取 HTML 更可靠,通常也更便宜。
关于法律方面,请参阅网页抓取合法吗?。关于如何从根本上避免被封,请参阅抓取时如何不被封禁。
定价
Scarnox 按成功的识别收费,识别失败不收费。费率请查看当前定价。免费试用额度,无需信用卡,立即注册。