返回博客
网络爬虫技术
Raluca PenciucLast updated on May 13, 20263 min read

HTTP 标头网络抓取:停止受阻

HTTP 标头网络抓取:停止受阻
简而言之:当你的浏览器能正常加载某个URL,而你的爬虫却收到403错误时,通常是HTTP头部导致的。本指南将说明反机器人系统实际会检查哪些头部,如何通过开发者工具捕获真实浏览器的头部集,如何在Python和Node.js中正确发送并轮换这些头部,以及在何种情况下手动调整不再奏效,转而使用托管爬虫API才是更明智的选择。

大多数被拦截的爬虫并非因 IP 地址被封,而是因为在请求主体发送之前,其发送的请求本身就被拦截了。在 HTTP 头信息处理中,网络爬虫的工作就是让客户端的元数据看起来像真实浏览器而非默认的 Python 或 Node.js 库,这是对抗反机器人检测最经济且最被低估的手段。

在 HTTP 中,头部(header)是由冒号分隔的名称-值对,承载着关于请求或响应的元数据:客户端身份、支持的语言、编码、Cookie、安全上下文等。 MDN 关于 HTTP 头部的参考文档和 RFC 9110 定义了标准语义。检测系统会将您的爬虫头部集与真实 Chrome 或 Firefox 会话的指纹进行比对,任何在值、存在性、大小写或顺序上的不匹配都可能导致请求被标记。

本指南面向后端、数据和运维工程师,其爬虫返回 403、429 状态码、空请求体,或显示与浏览器所见不同的页面。阅读完本指南,您将了解哪些标头至关重要,如何通过开发者工具读取标头并在 Python 或 Node.js 中重现,如何处理标头顺序和 TLS 指纹,以及何时停止调优并将请求层交由托管服务处理。

首先建立正确的概念模型。请求头是客户端附加到出站请求上的元数据:您的身份、接受的格式、来源等。响应头则是服务器发回的元数据:状态提示、内容类型、缓存规则以及 Set-Cookie 指令。

Cookie 并非独立协议,而是一种带状态的标头。服务器通过 Set-Cookie,而客户端会在后续的每次请求中通过 Cookie 随后的每次请求中通过头部进行回传。根据 RFC 6265,这种往返通信能维持会话有效性、传递认证令牌、锁定地理位置,并存储 A/B 测试分组。

对于基于 HTTP 头信息的网页抓取,这两层都至关重要。若其中任何一层出错,你的客户端在每次请求时都会被视为新用户,而这正是反机器人检测系统重点监控的对象。我们的 HTTP Cookie 解析文章详细介绍了其底层工作原理。

反机器人系统实际监控哪些标头

服务器并非对每个标头都给予同等重视。在真正的指纹识别系统中,只有少数几个标头承担着主要工作,且不同检测供应商使用的标头名称基本一致。下文的 H3 标题大致按对 HTTP 标头网络爬虫的影响程度排序,从几乎所有人都搞错的(User-Agent)到客户端提示和会话 Cookie。

User-Agent:被检查最多且被伪造最多的标头

User-Agent 该标头用于识别客户端软件、操作系统和浏览器引擎,是反机器人系统检测的首个目标。诸如 python-requests/2.x 或 axios 的默认值会立即被拦截,因为真实访客绝不会发送此类信息。现实中最常见的配置是 Windows 版 Chrome,因此这是最安全的模仿目标。

有两种模式必然会失败。第一种是在数百万次请求中重复使用同一个 User-Agent。第二种是手动修改真实的 User-Agent,随意更改一位数字,最终得到一个实际浏览器从未发布的版本。在撰写本文时,建议对照当前的 Chrome 或 Firefox 版本进行验证,而不是从任何教程(包括本文)中复制版本字符串。

Accept、Accept-Language 和 Accept-Encoding

这三项参数表明客户端能够处理哪些内容。 Accept 列出 MIME 类型, Accept-Language 列出语言环境,例如 en-US,en;q=0.9Accept-Encoding 列出压缩算法(gzip, deflate, br)。真正的浏览器会在此处发送丰富且有序的值,而Chrome和Firefox的具体字符串有所不同。

陷阱很微妙。一个美国住宅IP地址若与 Accept-Language: ru 会产生指纹不匹配。发送 br (Brotli) 却无法解码 Brotli 主体,同样会显得像机器人。将 Accept-Language 您的代理地理位置,并仅声明您的HTTP客户端能够透明解压的压缩格式。

Referer 和 Origin

Referer 字段会告知服务器是哪一页将用户引导至此 URL。如果访问的是一个深层产品页面,却完全没有 Referer 的访问看起来很可疑,因为真实访客通常来自搜索引擎、分类列表或内部链接。请设置一个合理的 Referer ,例如 Google 搜索或网站自身的分类页面。

Origin 是其更严格的变体:浏览器会将其附加到跨源的 POST 和 fetch/XHR请求中附加该字段。若您正在重放通过开发者工具观察到的API调用,请原样复制 Origin 标头,否则服务器会将该请求视为伪造。

Sec-Fetch 和客户端提示 (Sec-CH-UA)

现代 Chromium 浏览器会附加一组安全上下文标头,而旧版的抓取指南往往忽略了这些: Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest以及 Sec-Fetch-User。这些标头描述了请求属于顶级导航、嵌入资源还是跨源 XHR。从新标签页直接加载页面时,通常会发送 Sec-Fetch-Site: none, Sec-Fetch-Mode: navigate,以及 Sec-Fetch-Dest: document.

客户端提示 (Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform) 会以结构化的方式重复您的用户代理配置文件。指纹验证很简单:如果您的 User-Agent 声称是 Windows 版 Chrome,但你的 Sec-CH-UA-Platform 却显示 "macOS",则会被标记。请务必引用 User-Agent, Sec-CH-UA,并 Sec-CH-UA-Platform 确保它们均来自同一真实浏览器的截图,以保持内部一致性。

首次请求之后, Cookie 便成为你发送的最具身份识别特征的头部字段。反机器人系统会利用它来追踪会话是否已预热、你是否接受了同意横幅,以及你的 CSRF 令牌是否来自与提交表单相同的渲染环境。若在请求间丢弃 Cookie,你每次都会被视为全新的访客。

自定义标头如 x-csrf-token, x-api-keyAuthorization 等自定义标头会出现在现代单页应用(SPA)背后的 XHR 端点中。请从 HTML 或之前的 JSON 响应中提取这些标头,然后将其附加到实际的数据调用中。若省略此步骤,API 将返回 401 错误或空结果。

通过开发者工具捕获真实浏览器头部

别再凭空猜测头部值了。在常规的 Chrome 或 Firefox 会话中打开目标网站,右键点击并选择“检查”,然后切换到“网络”标签页。刷新页面,点击第一个文档请求(即 HTML),并打开“头部”面板。“请求头部”部分就是你的基准参考。

两个小技巧可节省时间。右键单击请求并选择“复制为 cURL”,将包含头部、Cookie 和正文的完整调用导出为可直接在终端执行的命令。在重放之前,请移除会话特定值(如 Cookie, x-client-data以及任何一次性 CSRF 令牌等会话特定值。

将重放请求指向 httpbin.org/headers 进行验证,该地址会精确回显客户端发送的内容。如果回显内容与开发者工具捕获的信息不符,说明您的 HTTP 库正在重写数据。我们的 cURL 响应头指南涵盖了更深入的检查技巧。

在 Python 和 Node.js 中发送自定义头部

所有 HTTP 客户端的模式都是一样的:构建一个包含标头名称和值的字典,将其附加到请求中,并复用会话以确保 Cookie 持久化。下文的两个语言特定章节展示了在大规模场景下容易引发问题的陷阱。

Python(requests 和 httpx)

使用 requests,传递一个 headers 字典 get,并使用 Session ,这样cookie就能在不同请求间保留:

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/<current> 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",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Dest": "document",
}

with requests.Session() as s:
    s.headers.update(headers)
    r = s.get("https://httpbin.org/headers")
    print(r.json())

注意事项: requests 无法可靠地保持标头顺序,这已被证实是导致指纹识别漏洞的弱点。对于大规模的 HTTP 标头网页抓取,建议优先使用 httpx,它能完全保留字典中的插入顺序,并支持 HTTP/2。按开发者工具显示的顺序构建字典,网络数据包的结构将保持一致。

Node.js(axios 和 fetch)

在 Node.js 中, axios 和原生 fetch 都接受 headers 对象。复用 axios 实例以共享默认值,并使用 Cookie 存储器(axios-cookiejar-support 或类似工具)来确保会话在跨请求中得以保留:

import axios from "axios";

const client = axios.create({
  headers: {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
                  "AppleWebKit/537.36 (KHTML, like Gecko) " +
                  "Chrome/<current> 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",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Dest": "document",
  },
});

const { data } = await client.get("https://httpbin.org/headers");
console.log(data);

注意 axios 的默认设置,例如 X-Requested-With: XMLHttpRequest,这些会泄露库信息。请显式覆盖或删除它们。我们的 axios 头部深度解析涵盖了请求层面的检测规避模式。

标头顺序、大小写与 TLS 指纹

根据 RFC 规范,标头名称不区分大小写,但其大小写和排序却构成了显著的指纹特征。真实的 Chrome 浏览器会在 User-Agent ,其确切大小写格式位于相对于 AcceptSec-Fetch-*。Python requests 历来会将名称转为小写,且不保证顺序; httpx 则会保留您提供的任何内容;axios通常保持原样,但如果您未移除默认值,它会添加自己的默认值。

但这只是问题的一半。即使拥有完美的标头集,您的 TLS 握手本身也具有独特的指纹。JA3 和更新的 JA4 会对客户端提供的密码套件、扩展和椭圆曲线进行哈希计算。一个声称是 Chrome 却提供 OpenSSL 密码套件顺序的 Python TLS 堆栈显然是谎言;请访问 tlsfingerprint.io 了解其可检测性。

缓解措施:使用支持 HTTP/2 且 TLS 配置符合实际的客户端(curl_cffi, tls-client 在 Python 中; undici 在 Node 中使用自定义 TLS),或者升级到隐形代理或托管 API,由其为你管理 TLS 层。

大规模轮换和刷新标头集

在规模化场景下,HTTP 头部轮换应在会话级别而非请求级别进行。在 Cookie 有效期内保持一组合理的头部配置,仅在创建新身份时进行切换。 User-Agent 会话中途的切换本身就是一种被检测的信号。

从当前的 Chrome、Firefox、Edge 和 Safari 版本中构建包含数百组真实标头集的池。随着浏览器更新及时刷新该池,否则您将因使用去年的用户代理(UA)而显得异常。将标头轮换与代理轮换结合使用,确保 IP 地址或标头本身都不成为可预测的固定特征。我们的《避免 IP 封禁指南》以及《Python requests 代理操作指南》涵盖了代理方面的内容。

发送前需移除的默认库头部

在任何请求发出之前,请审核您的库在未经请求时发送的内容。常见的暴露信息包括:

  • User-Agent: python-requests/<version> 或 axios 的默认 UA
  • X-Requested-With: XMLHttpRequest 来自 axios
  • Accept: */* 而非真实的 MIME 列表
  • Connection: close 而真实浏览器会保持连接
  • 代理注入 Via, X-Forwarded-For,以及 Forwarded

将前四个替换为实际值,并测试您的代理 httpbin.org/headers 以捕获最后一个组。

基于标头的阻塞调试:检查清单

针对 HTTP 头部网络爬虫的调试循环很短。当遇到 403、429 状态码或正文为空但浏览器仍能正常渲染时,请按顺序执行以下步骤:

  1. 比较头部信息。将开发者工具捕获的数据与 httpbin.org/headers 逐行对比。
  2. 将 User-Agent 切换为当前 Chrome 或 Firefox 的其他字符串并重试。
  3. 检查 Accept-Encoding。若声明了 br,请确认客户端能对其进行解压;否则请将其移除。
  4. 验证 Cookie。确认 Cookie 是否与您预热过的会话匹配。
  5. 使用 cURLCopy as cURL。如果 cURL 成功而您的代码失败,问题出在您的客户端。如果 cURL 也失败,则是 IP 或 TLS 问题,而非头部信息。

何时应从手动设置头部切换至托管式爬取 API

手动设置 HTTP 头部进行网页抓取存在性能瓶颈。当遇到以下任一情况时,请升级方案:

  • TLS 或 JA3/JA4 阻断,即使使用完美的头部信息也无法突破。解决方法见下文 HTTP 部分。
  • 并发量超过几百个会话时,此时维护新鲜的 UA 池和 Cookie 池已演变为独立的服务。
  • 轮换所需的工程师工时成本超过托管端点按成功请求计费的费用。
  • 受企业级机器人管理保护的硬性目标,其会将会话与完整的浏览器指纹绑定。

托管式爬虫 API 或隐形代理可在单一端点后统一管理头部、TLS、IP 地址及重试机制,从而让您的代码专注于解析逻辑。我们关于 2026 年“如何避免被封锁的网页爬取”指南涵盖了完整的升级路径。

关键要点

  • 默认库的请求头是最明显的暴露点。请替换 python-requests/x.y 用户代理、axios 默认设置,并 X-Requested-With
  • 捕获现有数据,而非自行生成。从开发者工具中提取标头集,或 Copy as cURL,剔除会话特有的值,并参照 httpbin.org/headers.
  • 内部一致性胜过纯粹的现实主义。 User-Agent, Sec-CH-UA, Sec-CH-UA-Platform,且 Accept-Language 必须全部描述同一浏览器、操作系统和地理位置。
  • 顺序和 TLS 与数值同样重要。优先使用 httpx 而非 requests ,并在使用 JA3/JA4 指纹识别时采用符合 TLS 规范的客户端(或托管 API)。
  • 按会话轮换,而非按请求轮换。在 Cookie 有效期内保持一组标头,随着浏览器更新刷新池,并配合代理轮换使用。

常见问题

为什么我复制了Chrome中的确切头部信息后,请求仍然被拦截?

通常是因为头部信息并非唯一的识别信号。 您的 TLS 握手(JA3/JA4)、HTTP/2 帧顺序、IP 信誉以及缺失的会话 Cookie 都会独立地对您的客户端进行指纹识别。即使字节完全一致的头部也会失败,因为底层的 TLS 堆栈显示的是“Python”而非“Chrome”。请使用 cURL 重放请求:如果 cURL 成功而您的代码失败,那么问题出在头部层之下。

我应该多久轮换一次跨请求的 User-Agent 字符串和完整头部集?

应按会话轮换,而非按请求轮换。真实访客在整个访问过程中会使用同一浏览器,因此会话中途更换 User-Agent 的行为本身就值得怀疑。在 Cookie 有效期内保持同一组标头,然后为下一个会话选择新的一组。随着 Chrome 和 Firefox 发布新的稳定版本,每隔几周刷新底层标头池。

如果使用 Playwright 或 Puppeteer 这样的无头浏览器,是否仍需设置 HTTP 头部?

是的,部分需要。无头浏览器会自动发送真实的标头,包括 Sec-CH-UASec-Fetch-*,因此您可以省去大部分手动操作。但您仍需覆盖 headless-mode User-Agent (通常包含 HeadlessChrome),设置一个合理的 Accept-Language ,并禁用 navigator.webdriver 标志。

托管式爬取 API 或智能代理能否自动为我处理 HTTP 头部?

可以。托管式抓取API和隐身代理网络会针对每个目标选择一套真实的头部信息,将其与住宅IP和TLS配置文件进行匹配,并同步轮换所有设置。您只需发送目标URL,它们就会返回HTML或JSON。其权衡在于每次请求的成本,与构建和维护您自己的头部、代理及指纹识别栈所需的工程时间之间。

如何判断封禁是源于头部信息、IP 地址还是 TLS 指纹?

请逐一排查变量。首先,使用 curl --resolve ;如果 cURL 成功,问题出在你的 HTTP 客户端的头部或顺序上。接下来,切换到另一个家庭 IP 地址并保持相同的头部;如果阻塞消失,说明是 IP 被标记了。如果这两种方法都不奏效,TLS 握手最可能是罪魁祸首。

总结

HTTP 头部处理虽不是构建爬虫时最引人注目的部分,但却是投入少量工作却能获得最大回报的部分。捕获真实的浏览器头部信息,按浏览器使用的顺序发送,并保持 User-Agent, Sec-CH-UAAccept-Language 保持内部一致性,按会话而非按请求轮换,并去除那些昭示自动化身份的库默认设置。养成这一套习惯,在您甚至还没动用代理之前,就能解决大部分简单的 403 和 429 错误。

除此之外,手动调试终将遇到瓶颈。JA3/JA4指纹识别、企业级机器人管理以及大规模并发等挑战,都将工作推向了头部层之下,而这正是停止手动开发、转而让托管服务接管处理的恰当时机。 如果您的技术栈已发展到这一阶段,WebScrapingAPI 的 Scraper API 可在单一端点后统一管理头部、TLS 配置、住宅 IP 及重试机制,让您保留解析逻辑的同时,摆脱指纹识别的军备竞赛。当手动配置头部不再是最经济的方案时,不妨从这里开始。

关于作者
Raluca Penciuc, 全栈开发工程师 @ WebScrapingAPI
Raluca Penciuc全栈开发工程师

Raluca Penciuc 是 WebScrapingAPI 的全栈开发工程师,主要负责开发爬虫、优化规避机制,并探索可靠的方法以降低在目标网站上的被检测概率。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。