记一次请求头导致的requests乱码问题&&源码分析

引言

好好的代码,添加了请求头,也正确的解码了响应,跑的好好的突然就乱码了,换了几种方式解码都不成功,研究了好久,才发现问题。

问题复现

先来看这样一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
import requests

headers = {
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
}
r = requests.get('https://www.baidu.com', headers=headers).text[:100]
print(r)

上面的代码在正常情况下访问是没有问题的,当然了,地址我使用百度来替代。

但是跑着跑着,有的时候会遇到下面的乱码问题,开始不能稳定复现,后来发现可以稳定复现了,猜测可能是服务端改了什么东西。

image-20250207221615149

问题分析

起初我以为是编码方式变了,换了好几种编码方式都无法成功解码。

使用抓包工具抓包发现,抓包工具可以正常显示响应数据,说明服务端返回的数据是正确的。后来打印了一下响应头,和使用浏览器访问其他网站对比了一下,发现了一个异常的地方。image-20250207222228784

从上面的图中可以看到,content-type 请求头的编码是 utf-8 没有问题,但是前面的 content-encoding 却是 br。这个 br 是啥呢,查了一下,它的全称叫做:Brotli

Brotli 是谷歌开发的一种全新的压缩算法,它的压缩率相比于 gzip 来说要高得多,也就是说服务端在告诉我们,响应数据使用了 br 算法来压缩,那肯定就要用 br 算法来解压缩。

想到了这里,我们再看一下请求头,'Accept-Encoding': 'gzip, deflate, br, zstd',在发送请求头的时候,我们指定了四种算法,这里是给服务端用来协商压缩算法的,服务端可以选取自己支持的算法来进行压缩,目的是减少服务器流量占用。

问题解决

分析到这里我们应该明白了,其实主要原因就是我们提供了多种压缩算法,但是其中有我们不支持的算法,服务端正好选择了我们不支持的算法,导致程序接收到的响应无法解码。

那原因清楚了,解决问题就很简单了,删除我们不支持的 br 算法即可。删掉 br 请求头后,问题解决,响应可以正常解码。

源码分析

那 requests 为什么不支持 br 算法呢,既然可以支持 gzip,那我理解 br 算法应该也是可以支持的,我们到源码里面看一看。

image-20250207223400164

跟踪代码,我们发现在获取响应时,会进入上图中的方法,这个时候的 data 变量还是未解压缩的数据,说明解压缩操作应该在 read_chunked 里面,继续步入看看。

image-20250207223603981

image-20250207223627247

发现有一个 init_decoder 方法,看上去是一个解码器的初始化方法,进入看看,首先获取请求头中的 content-encoding 字段,可以看到目前获取的是 gzip,如果获取到了,则初始化对应的解码器,继续往下看。

image-20250207223939817

image-20250207224123080

image-20250207224028453

继续往下就到了解码的地方,在一个循环中调用刚才初始化好的解码器开始解压缩工作,并返回解压缩后的数据,可以看到 decoded 变量存储的数据已经被解压了,已经变成了我们可以看懂的明文。

image-20250207224257054

image-20250207224634223

查看解码器对应的实现,发现一共有四个实现,仔细查看对应的实现,发现其实有五个,而且底层实现是支持 br 算法的,只不过是通过 brotli 变量进行控制的,看下这个变量是怎么来的。

image-20250207224731423

看到这里,一切真相大白了,原来是尝试导入了 brotli 库,如果发现导入错误,则认定是没有安装对应的算法库,就无法进行解压缩工作了,只能原样返回响应数据。

新解决方案

image-20250207225209456

看到这里,大家应该都知道了,其实解决这个问题更加简单的方式是直接安装对应的 brotli 库,在可以正常导入 brotli 库后,解压缩工作也就能正常进行了。如果怕后续再遇到类似的问题,我们也可以提前安装 zstandard 库,下次如果服务端选择了 zstd 算法的话,我们不用更改任何代码就可以自动兼容了。

image-20250207225444120

安装 brotli 库后,重新发起请求,发现这次选择了 br 算法并且可以正常解码数据,完工。

总结

本次问题其实是由服务端改动引起的,通过源码分析,我们知道了原来底层库已经支持了br 算法以及 zstd 算法,只不过我们没有安装对应的解码库,所以才无法解码的。后面只要将对应的库都安装上,就可以畅通无阻了,因为浏览器也就支持这四种算法,短时间内应该就不会再出现类似的问题了。

不推荐删除 br 算法支持,因为更高的压缩率就意味着更少的流量,在使用流量计费的代理 IP 时,还是可以降低不少费用的。而且万一服务端通过支持的算法来识别爬虫呢?哈哈哈,不敢想不敢想。

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

本文作者:LLLibra146

更多文章请关注公众号 (LLLibra146):LLLibra146

版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!

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