Scarnox · Blog

如何在爬虫中避免验证码(不违反规则)

爬虫过程中遇到的大多数验证码,都是网站对看起来像自动化的流量做出的反应。最省事的做法是不去触发它们。这意味着先寻找合法的数据来源,查阅网站的规则确认你可以做什么,减少请求数量并放慢节奏,同时避免重复抓取同一个页面。求解验证码是应对无法避免的情况时的备选方案,而不是首选方案。

先检查是否有官方 API 或数据源

许多经常被爬取的网站,都提供了无需验证码即可获取相同数据的途径:公开 API、数据导出、RSS 或站点地图,或者合作伙伴计划。官方数据源更可靠,不太会在没有通知的情况下变更,而且网站所有者可以对你的使用方式提出意见。可以留意以下几处:

如果其中某个途径能满足你的需求,就直接使用它。不请求 HTML 页面本身,就能避开大多数验证码。

遵守 robots.txt 及其要求的抓取频率

robots.txt 会告诉你哪些路径允许自动化客户端访问,有时还会说明两次请求之间应等待多久。Python 标准库可以替你读取它:

import time
from urllib.robotparser import RobotFileParser
import requests

USER_AGENT = "ExampleBot/1.0 (+https://example.com/bot)"

parser = RobotFileParser()
parser.set_url("https://example.com/robots.txt")
parser.read()

delay = parser.crawl_delay(USER_AGENT) or 5  # 若未设置则回退为保守的 5 秒
urls = [
    "https://example.com/products?page=1",
    "https://example.com/products?page=2",
]

last_request = 0.0
for url in urls:
    if not parser.can_fetch(USER_AGENT, url):
        print("skipped by robots.txt:", url)
        continue
    wait = delay - (time.monotonic() - last_request)
    if wait > 0:
        time.sleep(wait)
    resp = requests.get(url, headers={"User-Agent": USER_AGENT}, timeout=30)
    last_request = time.monotonic()
    print(url, resp.status_code)

如果 robots.txt 禁止访问某个路径,诚实的做法是不去访问它,或者向网站所有者申请许可。改用浏览器绕过这一限制,并不会改变网站已经提出的要求。

放慢节奏,分散请求

稳定且适中的节奏很少触发挑战,而突发的大量请求几乎总会触发。以下几个习惯能带来明显的改善:

缓存,避免重复抓取

你没有发出的每一个请求,都是网站不必处理的请求。将你抓取的页面连同日期和正文的哈希值一起保存,只在内容可能已经变化时才重新抓取。如果服务器支持,使用带有 If-Modified-Since 或 ETag 头的条件请求,可以让服务器以低成本回复“未修改”。

复用会话,发送常规请求头

在两次请求之间保留 cookie 的会话,比每次都从头开始的脚本更像一个正常访客。为每个网站使用 requests.Session(),并发送一个清楚、诚实的 User-Agent,注明你的机器人名称和联系方式。不要为了越过规则而假冒浏览器。如果某个网站需要 JavaScript 才能显示内容,请对这些页面使用浏览器工具,并遵守同样的频率限制。

验证码仍然出现时

有些网站无论你多么谨慎地操作,都会显示验证码。请在代码中检测到验证码后停止该任务,而不是持续刷新页面。检测以及针对 PopularCaptcha 的可选求解,在 网页抓取验证码 一文中有详细说明。

开始前先规划抓取计划

每天抓取全部内容的爬虫,是最容易触发验证码的方式。请先规划好工作:

这样的计划可以减少请求数量,而请求数量正是你最有效的控制手段。

把计划写成一个简短的配置文件或表格:包括站点、板块、刷新间隔,以及每个站点的每日请求预算。预算用完后,任务就停止,直到下一个时间窗口。把预算当作硬性上限而不是参考建议,是防止爬虫偏离、最终形成会触发验证码的流量模式的关键。每隔几周重新审视一次预算,因为一个不断增长的网站,其流量也可能随之增长,上个季度看似礼貌的频率,现在未必还礼貌。

负责任地抓取

下一步