引言

大家好,本篇文章来分享一下如何使用 requests 的时候保持 headers 有序。

header反爬

这是一个不太明显的反爬手段,正常来说在进行反爬策略测试的时候,我们最先要保证的就是保证自己的程序发送的请求和 web 端发送的请求是一模一样的(sign 之类的除外)。但是,有的时候即使是发送了一模一样的请求,最终的请求结果还是失败的,这个时候就要将两个请求的请求体放到一起来比较,逐字符分析到底是哪个字段不一样了。

例如我这里比较一下两个请求的响应,只有一个字段不一样:

image-20250105172722485

OK,我们进入正题,在遇到类似的反爬时,应该如何保证 requests 的 header 字段有序呢?先看一段示例代码:

import requests
import urllib3

urllib3.disable_warnings()
h = {
    'Connection': 'keep-alive',
    'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'X-Requested-With': 'XMLHttpRequest',
    'sec-ch-ua-mobile': '?0',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-Mode': 'cors',
    'Sec-Fetch-Dest': 'empty',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
}
params = {'page': 1}
url = 'https://httpbin.org/post'
r = requests.post(url, headers=h, data=params, verify=False, proxies={'https': 'http://127.0.0.1:9090'})

以上代码运行后,可以看到,请求头的顺序和我在代码中定义的不一样了。

image-20250105174518316

除了自动添加的 UA 和 host 字段外,红框中的字段被提前了,代码中其实是放到最后的。

header 有序

那么应该如何保证 header 有序呢?来看代码:

import requests
import urllib3

urllib3.disable_warnings()
h = {
    'Connection': 'keep-alive',
    'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'X-Requested-With': 'XMLHttpRequest',
    'sec-ch-ua-mobile': '?0',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-Mode': 'cors',
    'Sec-Fetch-Dest': 'empty',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
}
session = requests.Session()
# 别忘了 clear 方法
session.headers.clear()
session.headers.update(h)
params = {'page': 1}
url = 'https://httpbin.org/post'
r = session.post(url, data=params, verify=False, proxies={'https': 'http://127.0.0.1:9090'})

使用 session 来发送请求,并且在 session 初始化之后先要清除之前的 header,然后传入我们已经定义好顺序的 header,即可控制 header 的顺序,看下效果:

image-20250105175056507

除了自动生成的 host 和 UA 之外, 其他的 header 都是按照我们传入的顺序发送的请求,这样就达到控制 header 的顺序的目的了。

源代码分析

那么为什么会这样呢?直接传入的 header 为什么不能保持有序呢?我们从源代码中来寻找答案。

session 方式

打个断点,开始调试,先看为什么 session 可以让 header 保持有序:

image-20250105175400716

可以看到,在 session 初始化后,header 就已经被初始化了三个值进去,这就是为什么要在 update 之前要 clear 的原因,不 clear 的话,默认的三个 header 会影响我们传入的 header 顺序。

接下来我们看下 header 的 update 方法。

image-20250105175614634

update 方法主要就是判断传入的变量类型,根据不同的类型进行不同的操作,最终将我们传入的字典根据 key 来新增或者覆盖原来的值。

image-20250105180329907

来看 post 方法,这个时候我们传入的 header 已经被更新到 session 中的 header 属性了。

image-20250105180559073

接下来就是 session 的 requests 方法,重点就是这个 prepare 方法,我们进入看一下。

image-20250105183506736

可以看到,request 对象的 headers 属性中,值是一个空的字典,但是 session 的 headers 属性中,是我们传入的排序好的 header,这里使用 merge_setting 方法来将两个 headers 进行合并,我们看下合并的逻辑。

image-20250105183856482

合并逻辑不长,首先判断 session 和 request 是否为 None,然后进行类型判断,接下来初始化 dict_class,从这里可以看出来,session 的 header 优先级是高于 request 的,也就是说如果 session 中已经有的 header,在 request 中再次传入会覆盖 session 中原有的 header。最后就是删除 valueNone 的值,返回合并后的 headers。

image-20250105184421721

至于传入的 dict_class,其实是一个自定义的类似 dict 的对象,因为 http 的 header 是不区分大小写的,所以这里自定义类的作用就是忽略大小写限制,让相同的 header 值进行相互覆盖。其中使用了有序字典来存储我们传入的 header,这也是为什么 header 可以被有序发送的原因。

image-20250105184817391

合并完 header 之后,会进入到 prepare 方法中,对每个 http 请求的每个元素进行准备。我们来看下 prepare 方法对 header 做了什么。

image-20250105185224836

image-20250105185553841

image-20250105185619170

新建了一个字典类,覆盖了原有的 session 的 headers 属性,然后将合并后的 headers 依次放入新建的 headers 对象中,结束。其中 check_header_validity 方法会对每个 header 的 keyvalue 进行检查,确保它们符合 http 规范。

image-20250105180752575

接下来就是调用了 session 的 send 方法,在 send 方法中,会通过 get_adapter 方法获取可用的 adapter 适配器,然后调用 adaptersend 方法完整最终请求的发送。这里提一下,因为我们在 headers 中没有添加 host 和 UA,所以在最终请求发送时会自动被添加上,这里就不深究了。

post 方式

看完了以上的流程,那为什么直接使用 requests.post 的方式不能保持请求头的顺序呢?

image-20250105193459030

image-20250105193523043

玄机就在这里,如果不使用 session 的话,session 会被隐式初始化,其实最终也是通过 session 发送的请求,只不过 requests 帮我们初始化了一个一次性的 session,session 会设置一个默认的 headers,再结合前面说的,session 的 header 优先级比较高,也就是说这四个 header 就会排在最前面,顺序无法改变了,这就是 post 方式顺序不对的原因。

本文章首发于个人博客 LLLibra146’s blog

本文链接:https://blog.d77.xyz/archives/9da0fdbc.html


LLLibra146
35 声望6 粉丝

会修电脑的程序员