爬虫被封锁,是因为它看起来与目标所期望的流量不符。真实浏览器携带一组一致的信号:可信的 IP 地址、完整的请求头集合、与所声称 User-Agent 匹配的 TLS 指纹,以及不像节拍器一样均匀的请求节奏。去掉其中任何一项,现代反机器人系统就会注意到。在不被封锁的情况下进行抓取,大部分工作就是按正确的顺序将这些信号还原,并且只在目标确实要求时才使用代价高昂的方式。
本文按大致有效的顺序介绍各项技术:轮换 IP、发送可信请求、平滑请求频率、遵守网站声明的规则、在页面需要时渲染 JavaScript,以及在几个请求头已不够用时将整个任务交给托管端点。没有银弹。按正确顺序叠加这些方法,能让你在大多数目标上大多数时候从 403 封锁变为稳定的 200 响应。
优先级最高的解决方案
| 技术 | 应对的封锁类型 | 投入 |
|---|---|---|
| 轮换 IP | 单 IP 速率限制、硬性 IP 封禁 | 低 |
| 发送真实请求头和 User-Agent | 基础机器人指纹识别 | 低 |
| 节流与退让 | 基于请求频率的检测、429 响应 | 低 |
| TLS 指纹与 User-Agent 匹配 | 指纹不匹配检查 | 中 |
| 渲染 JavaScript | 空 HTML、JS 挑战 | 中 |
| 交给托管 API | 同时应对整个技术栈 | 最低(付费) |
从顶部开始,衡量封锁率,只有在目标迫使你时才往下走。用无头浏览器集群来抓取静态价格页面是浪费精力;对着坚固的登录墙用普通 requests 则是浪费请求次数。
轮换 IP
最常见的封锁也是最简单的:来自同一地址的请求过多。网站会统计每个 IP 的请求数,一旦超过阈值就开始返回 429 或封锁页面。将这些请求分散到多个 IP 上,就没有单一地址会触发限制。这就是爬虫基础设施大多是代理基础设施的全部原因, 代理代你发起请求,目标看到的是代理的 IP 而不是你的。
轮换使用的 IP 类型与轮换本身同样重要。数据中心 IP 速度快、成本低,但位于已知的托管地址段,做 ASN 查询的目标会立即标记它们。住宅 IP 从真实的消费者连接出口,看起来像普通访客,但成本较高、速度较慢。完整的权衡分析见数据中心代理与住宅代理的对比,以及静态住宅的折中方案见ISP 代理与住宅代理的主要区别。购买恰好符合目标要求的信任级别,不要超买。
手动轮换 IP 意味着维护一个列表并逐请求循环。轮换代理网关将这一过程隐藏在单个端点后,为你替换出口 IP,可以按请求轮换,也可以在需要保持同一身份时使用粘性会话。
# Rotate exits through a single gateway endpoint. # The gateway picks a fresh IP; your logic stays here. import requests proxies = { "http": "http://_USER_TOKEN_:@smartproxy.crawlbase.com:8012", "https": "http://_USER_TOKEN_:@smartproxy.crawlbase.com:8012", } resp = requests.get("https://example.com/product/123", proxies=proxies, verify=False) print(resp.status_code)
发送真实浏览器会发送的请求
默认的 HTTP 客户端会在第一行就暴露自己。Python 的 requests 库发送 User-Agent: python-requests/2.x 以及几乎没有其他请求头;真实浏览器会以特定顺序发送十几个。仅凭读取这些请求头就会封锁前者而放行后者的网站是存在的。
设置一个当前真实浏览器的 User-Agent,并在一个小池中轮换,而不是一直用同一个字符串。然后发送总是随之而来的请求头:Accept、Accept-Language、Accept-Encoding,以及合理的 Referer。目标不是单一的神奇请求头,而是内部一致性:Chrome User-Agent 配上 Firefox 风格的 Accept 请求头,比完全不做任何伪装更可疑。
import requests headers = { "User-Agent": ( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/124.0 Safari/537.36" ), "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9", "Accept-Encoding": "gzip, deflate, br", "Referer": "https://www.google.com/", } resp = requests.get("https://example.com", headers=headers)
让 TLS 指纹与 User-Agent 匹配
请求头是显而易见的层;TLS 是那些修好了请求头然后就此停步的爬虫的克星。在发送任何 HTTP 字节之前,你的客户端会打开 TLS 握手,该握手的确切形状(加密套件顺序、扩展、支持的组)形成一个通常用 JA3 哈希值概括的指纹。真实的 Chrome 会产生一个众所周知的指纹,Python 的 requests 则会产生完全不同的指纹。当你用 Python TLS 技术栈发送 Chrome User-Agent 时,两者不一致,指纹检查会标记这种不匹配,无论你的请求头多么完美。
解决方法是让握手本身看起来像浏览器。使用能模仿真实浏览器 TLS 配置文件的客户端(在 Python 中,带 impersonate 选项的 curl_cffi 是常见选择),或驱动真实浏览器引擎,它会免费生成真实的握手。这是自建爬虫开始变得昂贵的地方,也是已经处理好指纹的托管端点开始显现吸引力的地方。
反机器人系统很少因为单一的不良信号就封锁;它们封锁的是相互矛盾的信号。数据中心 IP 配完美的浏览器请求头集、Chrome User-Agent 配 Python TLS 指纹、桌面 UA 配移动端 Accept-Language:每种矛盾都是一个标记。目标是让 IP、请求头、TLS 和行为讲述同一个故事的请求。
节流与退让
即使跨越多个 IP,一个比任何人点击速度都快的爬虫看起来也是自动化的。在请求之间添加随机延迟而不是固定延迟(固定 500ms 的间隔本身就是一种指纹),并将并发度控制在目标不会注意到的水平。
比稳态延迟更重要的是你对服务器推回的反应。当服务器返回 429 或 503 时,它在告诉你减速。遵照执行:指数退让,当服务器发送 Retry-After 请求头时遵守它,并将一连串的 429 视为降低整体请求率的信号,而不是加大重试力度的信号。以全速重试被速率限制的端点,是软性限速变为硬性封禁的方式。
import time, random, requests def fetch(url, headers, tries=4): for attempt in range(tries): resp = requests.get(url, headers=headers) if resp.status_code == 200: return resp if resp.status_code in (429, 503): wait = int(resp.headers.get("Retry-After", 2 ** attempt)) time.sleep(wait + random.uniform(0, 1)) continue resp.raise_for_status() raise RuntimeError("exhausted retries")
如果你在解读状态码含义时遇到困难,代理状态错误码会逐一分析常见的状态码及其实际含义。
遵守 robots.txt 并坚守公开数据
在应用规避技术之前,先建立一个让你远离麻烦的纪律:阅读网站的 robots.txt,尊重其爬取延迟和禁止路径,抓取公开页面而不是登录后才能访问的内容。这既是礼貌,也是自我保护。经过身份验证的抓取将每次请求与网站可以一键封禁的账号绑定,而忽视声明的规则既是被标记最快的方式,也是法律问题开始出现的边界。
一个相关的陷阱是蜜罐:通过 CSS 对人类不可见(display:none、零大小、屏幕外定位)但存在于 HTML 中的链接。不加甄别地跟随每个 <a> 的朴素爬虫会直接走进去,暴露自己是机器人。只跟随渲染后的浏览器实际会显示的链接,跳过任何视觉上隐藏的链接。
在页面需要时渲染 JavaScript
许多页面返回几乎空白的 HTML,在加载后通过 JavaScript 构建真正的内容。用普通 HTTP 客户端获取这样的页面,你只能得到一个没有数据的外壳。更糟糕的是,有些网站提供 JavaScript 挑战:一个必须运行并通过的小脚本,然后才会提供真正的页面,而非浏览器客户端永远无法通过这种挑战。
两种情况都需要真实的浏览器引擎。无头浏览器(Playwright、Puppeteer 或驱动 Chrome 的 Selenium)加载页面,运行其脚本,并将用户将看到的 DOM 交给你。它还会产生真实的浏览器 TLS 指纹和真实的 navigator 对象,因此能通过一类原始客户端无法通过的检查。代价是重量:无头浏览器每个页面消耗的 CPU 和内存远多于 HTTP 请求,因此要留给真正需要渲染的页面。更深入的教程请参见使用 Python 和 Selenium 进行网络爬虫。
一个需要注意的地方:默认的无头浏览器是可检测的。navigator.webdriver 标志、缺失或异常的插件列表,以及无头模式特有的怪癖都会泄露信息。隐身插件会掩盖常见的特征,但这是一场军备竞赛,在难度较高的目标上,往往就到了停止维护自己的集群的时机。
何时将任务交给托管 API
上面的每种技术都是你需要构建和维护的一层:代理池、请求头轮换器、模仿 TLS 的客户端、退让策略、带隐身补丁的无头浏览器集群。对于宽容的目标,你可能只需要前两层。对于难度较高的目标,你最终需要组装和维护所有这些,一个 CAPTCHA 或新的 JS 挑战就能在一夜之间破坏整个管道。
爬虫 API 将这个技术栈压缩为一次请求。你发送一个 URL;提供商选择 IP 来源,发送一致的指纹,在需要浏览器时渲染页面,在服务器端对封锁进行重试,并返回完整的 HTML。这个权衡是诚实的:你按请求付费,放弃一些低级别控制,换来不用将反机器人基础设施当成第二份工作来维护。
当目标需要的不仅仅是干净的 IP 时,Crawling API 负责整个技术栈:在超过 1.4 亿个数据中心、住宅和移动 IP 池中轮换,发送可信指纹,在页面需要时渲染 JavaScript,并在服务器端对封锁进行重试。你发送一个 URL 并获得结果。先在免费层用你的真实目标测试。
# Send the URL; rotation, fingerprint, rendering, # and retries are handled server-side. import requests resp = requests.get( "https://api.crawlbase.com/", params={ "token": "_YOUR_TOKEN_", "url": "https://example.com/product/123", "javascript": "true", # render the page in a browser }, ) print(resp.text)
无论你自建还是购买,底层的代理问题都不会消失。如果你还在选择 IP 层,网络爬虫最佳代理会将目标类型与合适的代理类型进行匹配,如何使用轮换代理涵盖了在代码中实现轮换的方法。
核心要点
- 封锁源于不一致。让你的 IP、请求头、TLS 和请求时序讲述同一个故事;一处矛盾就足以被标记。
- 先轮换 IP。大多数封锁是单 IP 速率限制,将请求分散到池中是成本最低、效果最显著的解决方案。
- 同时修复请求头和 TLS。在 Python TLS 技术栈上使用浏览器 User-Agent,比完全不做任何伪装更可疑。
- 尊重目标网站。遵守 robots.txt,在 429 时退让,避开蜜罐,坚守公开数据。
- 按需渲染,困难时外包。仅对 JavaScript 密集的页面使用无头浏览器,当目标在每个层面都开始对抗时,转而使用托管 API。
常见问题
爬虫被封锁最常见的原因是什么?
来自单一 IP 地址的请求过多。网站统计每个 IP 的请求数,一旦超过阈值就开始返回 429 或封锁页面。将请求分散到 IP 池中,让没有单一地址触发限制,是单一效果最显著的解决方案,这也是 IP 轮换通常作为首选技术的原因。
更改 User-Agent 足以避免被封锁吗?
在防御最弱的网站上,有时可以。但对于任何严肃的网站,不行。真实的 User-Agent 必须搭配浏览器发送的完整请求头集、与该浏览器匹配的 TLS 指纹,以及可信的请求频率。在默认 HTTP 客户端 TLS 技术栈上使用伪造的 User-Agent,是指纹检查很容易捕获的矛盾。
我总是需要无头浏览器来抓取吗?
不需要。无头浏览器仅在页面加载后通过 JavaScript 构建内容,或提供非浏览器客户端无法通过的 JavaScript 挑战时才需要。对于静态 HTML,普通 HTTP 请求要快得多,成本也低得多。请将无头浏览器留给真正需要渲染的页面,因为它每个页面消耗的 CPU 和内存要多得多。
如何处理 429 Too Many Requests 响应?
减速而不是加大重试力度。指数退让,当服务器发送 Retry-After 请求头时遵守它,并将一连串的 429 视为降低整体请求率的信号。以全速对被速率限制的端点进行重试,是临时限速变为硬性封禁的方式。
我应该抓取登录后的数据吗?
尽量避免。经过身份验证的请求将每次调用与网站可以立即封禁的账号绑定,并引发公开页面抓取所没有的法律和服务条款问题。阅读网站的 robots.txt,坚守公开数据,跳过对真实用户隐藏的蜜罐链接。
托管爬虫 API 在什么情况下比自建更合适?
当目标同时在多个层面进行对抗时。维护代理池、请求头轮换、模仿 TLS 的客户端、退让逻辑和带隐身补丁的无头浏览器集群是真实的工程负担,一个新的 CAPTCHA 或挑战就能在一夜之间破坏这一切。爬虫 API 将所有这些都隐藏在一次请求后,所以你用按请求付费和放弃部分控制,换来不用自己运行反机器人基础设施。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
