1. 先把地基打牢:爬虫为什么会卡在 Web 基础这一关
写爬虫的人有个共同的成长路径:第一周用几行代码把某个页面的标题抓下来,觉得自己已经入门;第二周开始批量跑,发现一半请求返回 403,一部分返回的内容是乱码,还有一些请求明明在浏览器里能看到数据,用代码跑就是拿不到。到了这个时候,绝大多数问题都不在你写的代码逻辑本身,而是在请求报文和响应报文里——也就是请求头、响应头、HTTP/HTTPS 协议差异和状态码这四件事上。我这几年带过不少刚接触采集的朋友,几乎所有人第一次真正搞懂响应头、请求头、HTTP 与 HTTPS 的区别、以及状态码分类,都是从一次卡了半天的报错开始的。
这篇内容就是把这四块串起来讲透。它适合两类人:一类是刚学会发请求、但对“为什么这么写”完全没概念的新手;另一类是已经能跑起来脚本、但遇到异常只会复制粘贴报错去搜的中级选手。我会把每一个头字段的作用、为什么服务端会看它、缺了它会怎样、以及实际操作里怎么观察和验证,一条一条拆开。文中所有结论都可以用最简单的工具复现,不需要复杂的环境。文中也会给出一份检索资源清单,标注每项资源应该用什么关键词去找、能解决什么问题,避免你在海量资料里乱翻。
需要先明确一点:了解这些基础不是为了“绕过什么”,而是为了让你发出的请求是规范的、可诊断的、对目标站点友好的。很多所谓“被限制”的情况,本质是请求构造得不合规范,服务端无法判断来源,只能一律拒绝。把规范做对,问题往往自己就消失了。
1.1 一次请求从发出到拿到数据,中间经历了什么
把一次采集请求拆开看,其实是这样一个链条:你的程序按 URL 解析出协议、主机、端口、路径,然后组装出一条 HTTP 报文。报文分三部分——请求行、请求头、请求体。请求行里是方法、路径和协议版本;请求头是一堆键值对,告诉服务端“我是谁、我要什么格式、我带了什么凭证”;请求体则通常只在 POST 这类方法里出现。服务端收到后做业务处理,返回一条响应报文,同样分三部分:状态行、响应头、响应体。状态行里最关键的就是那三个数字的状态码,响应头告诉你内容的类型、长度、编码方式、是否需要重新请求别的位置,响应体才是你真正想要的数据。
问题在于,绝大多数人只关心响应体,把前后两部分当成噪音。可实际排查时,信息密度最高的恰恰是头。举个例子:你拿到一堆乱码,看着像编码问题,其实可能是响应头里声明了压缩编码,而你用的库没有自动解压;你拿到的 HTML 里没有数据,看着像页面结构变了,其实是服务端返回了一个重定向,真正的数据在 Location 指向的地址上;你请求返回 200 但内容是一个验证页,看着像被拦截,其实是 Cookie 没有在两次请求之间保持。这三种情况,只要养成“先看状态码,再看响应头,最后看响应体”的顺序,五秒钟就能定位。
所以我一直建议:调试采集脚本时,第一件事不是打印响应正文,而是把状态码和完整响应头打印出来。这个习惯能省掉你后面 80% 的试错时间。后面第 6 节我会给一段可以直接抄的调试代码,把请求头、响应头、状态码、重定向历史一次性全部打出来。
1.2 基础不牢最容易踩的四个坑
第一个坑是把请求头当成“随便填填”的东西。有些教程会让你复制一整套浏览器请求头塞进去,看着很唬人,但里面很多字段是浏览器自动生成、并且带有强关联性的,比如某些字段的值必须和 Cookie、来源页形成一致,否则反而更容易被判定异常。请求头不是越多越好,是有逻辑一致性的。
第二个坑是分不清 HTTP 和 HTTPS 在报文层面的差异。很多人以为换协议只是改个前缀,实际上报文在传输前会被加密封装,你在抓包里看到的是一串二进制。这直接影响你的调试手段:明文协议可以直接看,加密协议必须借助支持解密的工具或者从应用层打印。
第三个坑是把所有非 200 都当成“失败”。3xx 是重定向,需要跟随;204 是成功但无内容;206 是部分内容,说明服务端支持分段;304 是缓存未过期。把它们一律判为失败,会导致大量本来正常的采集被丢弃。
第四个坑是看到 403 就想着换 IP。403 的成因非常杂,可能是请求头缺少必要字段,可能是 Cookie 过期,可能是访问路径大小写不匹配,也可能是频率触发了保护。不先分辨成因就换出口,通常只是把问题延后。
提示:排查任何采集异常,固定顺序是「状态码 → 响应头 → 请求头回放 → 响应体」。跳过任何一步都可能让你在错误的方向上折腾很久。
2. 请求头:你对服务端说的第一句话
请求头是整条报文里最能体现“你以什么身份、想要什么”的部分。它本质是一组约定好的键值对,但每个字段背后都对应着服务端的一段判断逻辑。理解这些逻辑,你就能预判服务端会怎么处理你的请求,而不是靠试。下面挑几个采集场景里必用的字段逐个说。
2.1 User-Agent:不只是“伪装浏览器”
User-Agent 是服务端最先看到的身份标识,格式大致是“产品名/版本 (平台信息) 渲染引擎/版本”。服务端用它做三件事:区分设备类型返回不同页面、做基础的访问统计、以及做初步的访问控制。采集时最常见的问题是:某些库默认会带一个明显的程序标识,服务端一眼就知道这不是普通浏览器访问,于是直接返回精简页面或验证页。
但这里有个容易被忽略的细节:User-Agent 必须和你的其他请求头、以及你的实际行为保持一致。如果你声明自己是移动端,那么服务端可能返回移动版页面,页面结构和桌面版完全不同,你的解析规则就得跟着改;如果你声明自己是某个较新的浏览器版本,但请求头里缺少这个版本浏览器必然会带的字段,那这种不一致本身就是异常信号。我在实际项目里的做法是:确定一个主流浏览器版本作为基准,把它的完整请求头抓下来,然后只保留必要的部分,其余一律不加。
还有一个常见误解是把 User-Agent 当成一次性配置。同一个采集任务里,如果请求的是同一站点的不同板块,通常不需要变化;但如果跨越了完全不同的站点,就应该按各站点的实际情况分别设定。统一用一套头部去打所有站点,是最容易被识别为批量行为的做法。
2.2 Referer、Origin、Cookie:三个字段各管一段
这三个字段经常被混为一谈,其实分工非常清楚。
Referer 表示“你是从哪个页面跳过来的”,它解决的是来源追溯问题。很多站点的详情页会校验 Referer 是否为本站的列表页或搜索页,因为正常用户确实是这么点进来的。采集时如果不带这个字段,服务端可能直接返回错误页。这里有个实操细节:Referer 的值必须是合法的完整地址,不能是空字符串也不能是随意编造的路径,路径必须真实存在于该站点,否则校验逻辑会判定为伪造。
Origin 用在跨源请求场景,主要出现在 POST 以及部分 GET 请求中,表示发起请求的源(协议+主机+端口)。它和 Referer 的区别是:Referer 可以带完整路径,Origin 只到源为止;Referer 在部分场景可以被省略,Origin 在跨源场景下是强制要求的。做接口采集时如果遇到预检请求或者跨源被拒,先看这个字段。
Cookie 是状态凭证,服务端靠它把多次请求关联成同一个会话。采集里最典型的坑是:只登录一次拿到 Cookie,后面所有请求都用这一份,结果跑一段时间后全部失效。原因是 Cookie 有时效,而且服务端可能在任意一次响应里通过 Set-Cookie 下发新的值,你必须把这个更新循环起来,也就是后面第 3 节要讲的“响应头里的 Set-Cookie 要回写到请求头”。
注意:这三个字段之间存在关联性。Cookie 是哪个会话产生的、Referer 指向哪个页面、Origin 是否匹配,服务端是可以交叉验证的。构造请求时保持它们的内在一致性,比单纯把字段塞满重要得多。
2.3 Accept 系列与 Content-Type:内容协商的完整逻辑
Accept 系列是客户端告诉服务端“我能接受什么格式”,Content-Type 是告诉对方“我发出去的内容是什么格式”。这两个方向不要搞混。
请求方向的常见字段有:Accept(可接受的媒体类型,带权重 q 值)、Accept-Encoding(可接受的压缩方式)、Accept-Language(可接受的语言)、Accept-Charset(可接受的字符集)。其中 Accept-Encoding 对采集影响最大,因为如果服务端返回了压缩内容而你声明支持它,就必须自己负责解压。大多数成熟的请求库会自动处理,但如果你用底层库手写,很容易拿到一堆二进制。
权重 q 值的写法是text/html,application/xhtml+xml;q=0.9,*/*;q=0.8,意思是优先 HTML,其次其他 XML 类,最后接受任意类型。很多采集异常是因为把 Accept 写成了*/*之外的单值,导致服务端返回了非预期的格式。
Content-Type 在请求体存在时才需要,最常见的两种是表单格式application/x-www-form-urlencoded和 JSON 格式application/json。这里有个非常高频的错误:用 JSON 提交却声明成表单格式,服务端解析失败,返回的往往是 400 或 415,而错误信息里未必写清楚原因。反过来,用表单提交却声明 JSON,服务端可能直接读取不到参数,返回业务层错误。这个问题我在早期项目里踩过至少三次,每次都要盯着报文看半天才发现是声明和实际内容不匹配。
2.4 自定义请求头与请求头顺序的取舍
除了标准字段,很多站点会要求自定义头,常见于接口类采集,比如某些内部标识、版本号、设备信息。这类字段通常没有公开文档,只能从浏览器实际的请求里观察。做法是:打开调试工具的请求列表,找到目标接口,把请求头整体复制出来,逐个对比哪些是浏览器自动带的、哪些是页面脚本手动设置的。手动设置的通常就是关键字段。
关于顺序,标准上请求头的顺序不影响语义,但现实中部分站点会做顺序指纹。我不建议在这上面投入太多精力,因为顺序依赖的实现本身很脆弱。更值得投入的是字段值的合理性:长度、字符集、格式是否符合该字段的常规形态。比如时间戳类字段必须是近期的时间,随机串类的字段长度和字符集要匹配,这类细节比顺序重要得多。
3. 响应头:服务端给你的回执单,别只扫一眼就扔
如果说请求头是你说的话,响应头就是服务端的回执,而且这份回执里藏着大量决定性信息。我做采集排查时,看响应头的时间通常比看响应体多。下面按“最常误导新手的程度”排序讲。
3.1 Content-Type 与 Content-Encoding:乱码的真正源头
Content-Type 里包含两部分信息:媒体类型和字符集。形如text/html; charset=utf-8。新手最常见的错误是直接对响应内容做字符串处理,完全忽略声明的字符集。当声明的字符集和实际字节编码不一致时,就会出现乱码。处理原则是:优先信任响应头声明,但如果声明缺失或者与实际明显不符,再回退到从 HTML 的 meta 标签或者字节特征去推断。
这里有个容易混淆的点:响应头里的 Content-Type 优先级高于页面内 meta 声明。有些站点两处写法不一致,浏览器按响应头走,你的脚本如果按 meta 走,结果就会不一样。所以正确的顺序是:响应头 → meta 声明 → 字节探测。
Content-Encoding 表示传输过程中的压缩方式,常见值是 gzip、deflate、br。它和字符集是两个维度:前者是传输压缩,后者是文本编码。乱码问题里,这两者的排查顺序应该是先看有没有压缩没解开(表现为一片二进制),再看字符集对不对(表现为能读出字符但是错字)。成熟的请求库会自动解压并保留原始状态,但如果你用的是自动解压之后又手动读了一遍原始字节的写法,就会出问题。
提示:如果响应体开头出现类似乱码的二进制片段,先检查 Content-Encoding,再检查你是否重复解压。这两个原因占了压缩相关问题的九成。
3.2 Set-Cookie 的正确处理姿势
Set-Cookie 是响应头里唯一需要你主动“回写”的字段。服务端通过它下发会话凭证,你必须把它存下来,并在后续请求的 Cookie 头里带上。这里有几个必须处理的细节。
第一,一个响应可以有多条 Set-Cookie,它们是独立的下发,不能拼接成一个字符串。有些简化实现会把多个值用逗号连接,这是错误的,因为 Cookie 的值本身可能包含逗号。
第二,Set-Cookie 里有多个属性:Domain 限定作用域、Path 限定路径、Expires 和 Max-Age 控制有效期、Secure 表示只在加密连接下发送、HttpOnly 表示脚本不可读、SameSite 控制跨站发送行为。采集时需要特别关注 Path 和 Domain,否则会出现“Cookie 存下来了但请求时没带上”的诡异情况。我遇到过一次排查了两小时的问题,最后发现是 Cookie 的 Path 限定为某个子目录,而我的请求路径在上一层,自然带不上。
第三,Cookie 会滚动更新。服务端可能在任意一次响应里下发新值来续期或者轮换。正确做法是每次响应后合并更新本地存储,而不是初始化时读一次就再也不管。
第四,Secure 属性意味着这条 Cookie 只会在加密连接下发送。如果你用明文协议访问同一个站点,会出现 Cookie 存了但带不上的现象,而这恰好也是理解 HTTP 与 HTTPS 差异的一个切入点。
3.3 Location、Cache-Control、ETag:重定向与缓存里的信息
Location 出现在 3xx 响应里,表示“真正的地址在这里”。采集时最需要警惕的不是跟随重定向,而是重定向链路上 Cookie 和请求头的丢失。很多库在跨域重定向时会自动丢弃敏感头,这是安全设计,但对采集来说意味着你需要在跳转后重新补齐上下文。
Cache-Control 和 ETag、Last-Modified 是一组,属于缓存协商。它们对采集的意义是双向的:一方面,你可以利用它们减少重复请求,服务端返回 304 时表示内容没变,不必重新下载;另一方面,如果你在请求里带了过期的缓存标识,可能拿到 304 而误以为拿到了内容。做增量采集时,这两个字段是天然的变化检测手段。
另外,响应头里常有一些业务自定义字段,比如请求追踪 ID、服务端处理耗时、限流剩余次数。这些字段没有标准,但信息量往往很大。限流剩余次数尤其有用,能让你在不触发保护的前提下把频率调到最优。我现在的习惯是,新接入一个站点时,先把一条完整响应头保存成文件,逐字段过一遍,很多时候能提前发现限流、分页、缓存的关键参数。
4. HTTP 与 HTTPS:差的真的不只是一个字母
很多人对这两者的理解停在“一个是明文一个是加密”。这个说法没错,但对写采集的人来说太粗了,因为它解释不了你实际遇到的很多现象。下面从报文层、连接层、证书层三个角度说清楚。
4.1 报文层的真实差异
在 HTTP 下,你的请求报文原样落在网络链路上,抓包工具能直接看到请求行、请求头、请求体,调试非常直观。在 HTTPS 下,报文在传输前会被加密封装,网络上传输的是一段二进制记录,你只能看到连接的建立过程和大致的数据量,看不到内容。这直接决定了调试手段的选择:明文协议可以靠抓包定位问题,加密协议必须从应用层打印或者借助支持解密的调试配置。
另一个差异是连接复用。HTTP 早期版本每个请求一个连接,后来支持了长连接和管线化;HTTPS 因为建立了加密通道,复用的价值更高。这带来的实际影响是:如果你的采集程序对每个请求都新建连接,在加密协议下的开销会明显大于明文协议,表现为整体变慢。合理做法是复用会话对象,让连接池和 Cookie 管理自动化。
4.2 握手流程与采集视角的观察点
加密连接在传输数据前要先完成握手,大致顺序是:客户端发起连接并声明支持的加密套件和协议版本,服务端回应选定结果并出示证书,双方协商出会话密钥,之后才开始传输加密后的报文。如果客户端还要求验证服务端身份,会校验证书的签发链、有效期和域名匹配。
对采集来说,握手阶段会带来三个可见现象。第一,首次连接的耗时明显高于后续请求,因为握手是一次性成本,这也是为什么要复用会话。第二,如果目标站点做协议版本限制,用旧版本客户端可能会握手失败,表现为连接被重置而不是返回某个状态码——这类错误不在 HTTP 层,所以你看不到状态码。第三,如果本机缺少对应的根证书,校验会失败,报错通常提到证书验证。
我在实际排查里有个经验:如果连接建立阶段就失败,先分清楚是网络不通、是域名解析不对、还是握手被拒。这三种的报错文本完全不同,不看清楚就去改代码是浪费时间。
4.3 证书校验报错的处理原则
证书校验失败是新手最常遇到的加密协议问题,报错文本通常包含“证书验证失败”之类的字样。这时候网上一搜,最常见的建议是关掉校验。我的建议是:不要关。
关掉校验意味着你失去了确认“对面是不是真的目标站点”的能力,同时也让传输失去意义。正确的处理路径有三条。第一条,检查本机的时间是否正确,证书有有效期,系统时间错乱会导致所有证书都判定过期,这个问题在容器环境里特别常见。第二条,检查运行环境的根证书是否完整,精简过的镜像经常缺少证书包,补齐即可。第三条,如果确实是对接内网的自签证书服务,正确做法是把该服务的根证书导入到信任存储里,而不是全局关闭校验。这样既保证功能,也保留了其他请求的安全性。
关闭校验只应作为一次性的本地排查手段,用完立刻还原。把它写进提交的代码里,是典型的给自己埋雷。
5. 状态码:三个数字里的完整故事
状态码是响应报文里信息密度最高的三个字符。按首位数字分五类:1xx 表示中间状态,采集里基本遇不到;2xx 成功;3xx 重定向;4xx 客户端问题;5xx 服务端问题。分类背下来不难,难的是具体场景怎么解读。下面按采集中的实际发生率讲。
5.1 2xx 与 3xx:成功也可能是假的,跳转才是常态
200 表示成功,但“成功”只代表服务端处理完了请求,不代表返回的是你要的数据。返回一个验证页、一个空页面、一个精简版页面,状态码都是 200。所以判断成功不能只看状态码,还要校验响应体的特征,比如关键字段是否存在、内容长度是否在合理区间。
206 表示部分内容,出现在服务端支持分段请求时。做断点续传或者大文件采集时会用到,通过 Range 头指定区间。这个状态码本身说明服务端具备分段能力,是一个有用信号。
301 和 302 都是重定向,区别在于前者表示永久、后者表示临时。对采集的实际影响是:永久重定向可以被缓存,临时的不行。307 和 308 是更严格的重定向,明确要求保持原方法和请求体不变。这里有个高频坑:POST 请求遇到 302 时,很多客户端会自动改成 GET 再请求,导致请求体丢失、服务端拿不到参数。如果你做的是接口采集,遇到这种情况要显式控制重定向策略。
304 表示内容未修改,是缓存协商的结果。出现 304 说明你的请求带了缓存标识而服务端判断内容没变。如果你没打算做增量,看到 304 要检查是不是误带了缓存头。
5.2 4xx:403、404、429 的成因完全不同
400 是请求格式有问题,最常见的原因是请求体格式与声明的 Content-Type 不匹配,或者必填参数缺失。这类问题几乎百分百是你的构造问题,重试没有意义,改请求才有意义。
401 是未认证,403 是已识别但无权限。这两个要分清:401 说明你的凭证缺失或无效,去补 Cookie 或令牌;403 说明凭证可能是有效的,但你不被允许访问这个资源。403 的成因非常杂,我总结过几类:路径大小写或尾斜杠不一致、请求头缺少必要字段、来源校验未通过、账号权限不足、访问频率触发保护。排查时建议逐项对照,从最简单的那一项开始。
404 是资源不存在。除了地址写错,还有一种情况是接口需要特定参数才返回内容,缺参数时表现为 404。如果同一个路径在浏览器里能打开而代码里打不开,先对比请求头的差异。
429 是请求过多,这就是明确的限流信号。响应头里通常会有重试等待时间,按提示退避即可。这个状态下继续硬刷只会延长限制时间,得不偿失。
5.3 5xx:先判断责任方,再决定要不要重试
500 是服务端内部错误。502 是网关从上游拿到了无效响应。503 是服务暂时不可用,通常是过载或维护。504 是网关等待上游超时。这四个的区别对采集决策很重要。
504 和 502 通常跟你的请求特征有关:如果单次请求处理时间过长、或者并发过高把上游压垮,网关就会返回这两个。这时候正确的动作是降低并发、缩小单次请求的数据量,而不是重试。500 有时候是服务端偶发问题,适度重试有效;但如果同一个请求稳定返回 500,说明你的参数触发了服务端的某个缺陷,重试无用。
503 往往伴随 Retry-After 响应头,按它给的秒数等待是最优解。我见过不少人遇到 503 就加大并发试图“冲过去”,结果把自己彻底封锁。这类错误的正确姿势是退让。
注意:重试策略必须区分状态码。4xx 里除了 429,其他基本都不该重试;5xx 里 502、504 要先降速再试;429 和 503 必须按服务端给的等待时间退避。
6. 实操:一次把请求头、响应头、状态码全打出来
理论说完,落到能跑的东西上。下面这段代码是我每次接入新站点时都会跑的“体检脚本”,作用是把你关心的全部信息一次性打印出来,避免来回改代码。它只依赖最常见的请求库。
import requests URL = "https://example.com/some/list" headers = { "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/122.0.0.0 Safari/537.36"), "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Accept-Encoding": "gzip, deflate", "Referer": "https://example.com/", "Connection": "keep-alive", } session = requests.Session() session.headers.update(headers) resp = session.get(URL, timeout=15, allow_redirects=False) print("最终状态码:", resp.status_code) print("重定向目标:", resp.headers.get("Location")) print("内容类型:", resp.headers.get("Content-Type")) print("内容编码:", resp.headers.get("Content-Encoding")) print("内容长度:", resp.headers.get("Content-Length")) print("服务端标识:", resp.headers.get("Server")) print("\n--- 完整响应头 ---") for k, v in resp.headers.items(): print(f"{k}: {v}") print("\n--- 本次会话持有的 Cookie ---") for c in session.cookies: print(c.name, c.value, c.domain, c.path, c.secure)这段代码有几个刻意的设计,值得展开说。
第一,用会话对象而不是单次请求。会话对象会自动维护 Cookie,把 Set-Cookie 回写到后续请求,同时复用连接。这是处理需要登录或者需要保持状态的采集的基础,也是让加密连接开销摊薄的关键。
第二,allow_redirects=False。默认行为是自动跟随,方便但会隐藏信息。体检阶段我建议先关掉,看清第一跳的状态码和 Location,再决定是手动跟随还是交给库处理。等确认跳转链路没问题,再打开自动跟随。
第三,把响应头全量打印。这比逐个get有用得多,因为业务自定义字段往往是你事先想不到的。我靠这一步发现过限流剩余次数的字段、发现过分页游标字段、也发现过标识缓存命中的字段。
第四,Cookie 连同 domain、path、secure 一起打印。这能直接暴露“存下来了但带不上”的问题,比事后猜快得多。
6.1 用命令行工具交叉验证
代码跑出来的结果如果和预期不符,第二步是用命令行工具验证同一个请求。这么做的好处是排除代码层面的干扰,比如库自动加了什么、自动改了什么。
curl -sS -D - -o /dev/null \ -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \ -H "Referer: https://example.com/" \ -L \ "https://example.com/some/list"-D -把响应头输出到标准输出,-o /dev/null丢弃响应体,-L跟随重定向,-sS让进度条安静但保留错误。
两个工具的结果对比,如果命令行正常而代码异常,问题在代码的默认行为;如果两边都异常,问题在请求构造本身;如果两边都正常但你拿不到数据,问题在响应内容的解析。这个三分法能快速收敛排查范围。
6.2 请求构造的三个经验参数
第一个是超时。不设超时是采集脚本最常见的隐患,一个卡住的连接能把整个任务拖死。我的习惯是连接超时和读取超时分开设,连接给短一些,读取给长一些,依据是目标站点的实际响应速度。
第二个是并发。并发不是越高越好。判断标准是看状态码分布:出现 429 或 502 就说明过了,出现 503 说明严重过了。我通常从个位数并发起步,观察一段时间的状态码分布,再决定是否上调。
第三个是重试的间隔策略。不要用固定间隔,固定间隔在限流场景下等于持续施压。用指数退避,也就是每次失败后等待时间翻倍,并加一点随机抖动,能显著降低被判定为异常行为的概率。抖动的作用是避免多个任务在同一时刻一起重试形成脉冲。
6.3 状态码与响应头问题速查表
| 现象 | 最可能的原因 | 优先检查项 | 处理方向 |
|---|---|---|---|
| 返回 200 但内容是验证页 | 请求特征异常或频率过高 | 请求头一致性、请求频率 | 补齐必要头、降低频率 |
| 403 但地址在浏览器能打开 | 缺少来源或凭证字段 | Referer、Cookie 有效性 | 补齐上下文、刷新凭证 |
| 返回 400 且错误信息模糊 | Content-Type 与实际内容不符 | 请求体格式与声明 | 改为一致的格式 |
| 响应体是二进制乱码 | 压缩未解压或重复解压 | Content-Encoding | 交给库自动处理 |
| 读出来是错字而非乱码 | 字符集声明与实际不符 | Content-Type 的 charset | 按头声明解码 |
| Cookie 存了但请求没带 | Domain 或 Path 不匹配 | Set-Cookie 的属性 | 按属性匹配请求路径 |
| 302 之后参数丢失 | 方法被改为 GET | 重定向策略 | 显式控制跟随行为 |
| 304 但你没打算用缓存 | 误带缓存标识 | 请求头里的缓存字段 | 清理缓存相关头 |
| 502、504 频发 | 并发过高或单次负载过重 | 并发数、单次数据量 | 降速、缩小粒度 |
| 连接阶段直接失败无状态码 | 网络、解析或握手问题 | 报错文本、系统时间、证书包 | 分别排查三个方向 |
这张表我建议直接存下来,遇到异常先对照,能省掉大量瞎试的时间。
7. 资源清单:每一项该用什么关键词去找
标题里提到资源链接,我把它做成检索清单而不是一串地址,原因是地址容易失效,而检索关键词不会。下面每一项都标注了用它解决什么问题。
| 要找什么 | 检索关键词 | 解决什么问题 |
|---|---|---|
| 请求头与响应头的完整字段定义 | RFC 9110 报文语义 | 查某个字段的准确语义和取值格式 |
| Cookie 机制的规范定义 | RFC 6265 Cookie | 搞清楚 Domain、Path、SameSite 的准确行为 |
| 状态码的权威列表 | RFC 9110 状态码 注册表 | 确认冷门状态码的含义 |
| 内容编码的取值 | Content-Encoding 取值列表 | 判断解压方式 |
| 媒体类型全表 | 媒体类型 注册表 IANA | 查 Content-Type 的合法写法 |
| 加密连接的原理说明 | TLS 握手流程 概览 | 理解连接建立与证书校验 |
| 请求库的重定向与 Cookie 行为 | 请求库名 重定向 Session Cookie | 确认所用库的默认行为 |
| 缓存协商机制 | ETag Last-Modified 条件请求 | 设计增量采集策略 |
使用建议是:不要从头读规范,太重。正确姿势是先遇到问题,再从表里找到对应条目,只看那一段。规范文档的价值在于准确,不在于通读。我自己的习惯是给这几个条目建一个书签夹,遇到字段语义拿不准的时候直接查,比搜二手文章快且不会错。
最后分享一个我在实际操作里体会最深的小技巧:每次接入一个新站点,先花十分钟把一条完整的请求和响应从头到尾抄下来,逐字段问自己“这个字段是干什么的、缺了会怎样”。这十分钟的投入,通常能让后面的调试时间减少一大半。基础这件事,前期嫌麻烦省下来的时间,后期都会以更贵的方式还回去。