Scrapy默认不支持Brotli解压,需手动安装brotli库、启用HttpCompressionMiddleware中间件,并在process_response中调用brotli.decompress()解压br编码响应,否则response.text会因字节未解压而抛出UnicodeDecodeError。

Scrapy 默认会自动解压 gzip 和 deflate 编码的响应,但不会处理 br(Brotli)——它直接透传原始字节,不触发解压逻辑,容易导致 UnicodeDecodeError 或解析失败。
为什么 response.text 报错 'utf-8 codec can't decode byte'?
这是最常见现象:响应头含 Content-Encoding: br 或 gzip,但你没做任何处理就直接调用 response.text。Scrapy 不会自动解压 br;对 gzip 虽默认支持,但若中间件链中禁用了 HttpCompressionMiddleware,或你手动设置了 stream=True 并读了 response.raw,也会跳过解压。
- 检查
response.headers.get('Content-Encoding')是否为br、gzip或deflate - 确认
settings.py中未将scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware设为None - 避免在
parse中使用response.raw.read()—— 这绕过全部解压流程
如何启用 Brotli 解压支持?
必须手动补全三块:安装依赖、注册中间件、编写解压逻辑。Scrapy 本身不带 Brotli 支持。
- 安装
brotli或brotlicffi:pip install brotli - 在
settings.py中启用内置压缩中间件(它只负责识别编码,不实现解压):DOWNLOADER_MIDDLEWARES['scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware'] = 810 - 自定义
process_response方法,检测br后调用brotli.decompress(response.body),再构造新Response对象并替换原响应 - 注意:不要对已解压过的响应重复解压,需用
response.meta.get('decompressed')标记状态
怎样安全地复用 Scrapy 的 gzip 自动解压能力?
别自己调 gzip.decompress() —— 容易漏掉 header 清理、Content-Length 重算等细节。直接信任 HttpCompressionMiddleware,但要确保它没被意外关闭。
- 默认启用时,
response.body已是解压后字节,response.text可直接用 - 若需兼容性兜底(比如部分 CDN 返回
gzip但服务端又禁用了中间件),可在parse开头加一层防御:
if response.headers.get('Content-Encoding') == b'gzip':
import gzip
try:
body = gzip.decompress(response.body)
response = response.replace(body=body)
except (OSError, EOFError):
pass # 不是合法 gzip,保持原样该逻辑仅作 fallback,不能替代中间件。
哪些场景必须手动干预压缩响应?
三种典型情况无法靠默认中间件解决:
- 目标站返回
br且你无法控制其 Accept-Encoding 行为(比如固定 UA 导致只收到 br) - 你启用了
stream=True做分块处理(如大文件下载),此时response.body为空,必须自己从response.raw流式解压 - 某些反爬站点故意混用编码(例如先 gzip 再 base64),需在中间件里组合解码
真正麻烦的从来不是“能不能解”,而是“解完要不要改 response.url、encoding、selector 状态”——这些元信息不会自动同步,漏掉就会让后续 css() 或 xpath() 失效。

















