mitmdump实时抓包处理
mitmdump 是 mitmproxy 的命令行接口,可以对接 Python 脚本处理请求和响应,这是相比 Fiddler、Charles 等工具更加方便的地方。有了它,我们不用再手动抓取和分析 HTTP 请求与响应,只要写好请求和响应的处理逻辑即可。
正是由于 mitmdump 可以对接 Python 脚本,因此我们在 Python 脚本中获得请求和响应的内容时,就可以顺便添加—些解析、存储数据的逻辑,这样就实现了数据的抓取和实时处理。
实例引入
我们可以使用命令启动 mitmdump:
mitmdump -s script.py
这里使用 -s 参数指定了本地脚本 script.py 为处理脚本,用来处理抓取的数据,需要将其放置在当前命令的执行目录下。
我们可以在 script.py 脚本里写入如下内容:
def request(flow):
flow.request.headers['User-Agent'] = 'MitmProxy'
print(flow.request.headers)
其中定义了一个 request 方法,参数为 flow,这是一个 HTTPFlow 对象,调用其 request 属性即可获取当前的请求对象。这里将当前请求对象的请求头 User-Agent 修改成了 MitmProxy,然后打印出了所有请求头。
运行启动命令后,在手机端访问 http://www.httpbin.org/get ,可以看到手机端显示的页面显示如图 12-30 所示。
同时电脑端的控制台输出如图12-31所示的结果
图12-30中的 headers 实际上就是请求头,可以看到里面的 User-Agent 是我们修改后的 MitmProxy。图 12-31 中的 Headers 也是,其中 User-Agent 的内容正是 MitmProxy。
在这个案例中,我们通过只有 3 行代码的 script.py 脚本完成了对请求的修改,输出结果可以呈现在电脑端的控制台上,调试起来很方便。
图12-30 手机端显示的页面 图12-31 电脑端控制台的输出结果
日志输出
mitmdump 提供了专门的日志输出功能,可以设定以不同的颜色输出不同级别的结果。把 script.py 脚本的内容修改成如下这样:
from mitmproxy import ctx
def request(flow):
flow.request.headers['User-Agent'] = 'MitmProxy'
ctx.log.info(str(flow.request.headers))
ctx.log.warn(str(flow.request.headers))
ctx.log.error(str(flow.request.headers))
这里调用了 ctx 模块,它有一个名为 log 的功能,调用不同的输出方法可以输出不同颜色的结果,以便我们更直观、方便地调试(例如,调用 info 方法输出的结果为白色,调用 warn 方法输出的结果为黄色,调用 error 方法输出的内容为红色)。上述代码的运行结果如图 12-32 所示。
图12-32 使用 log 功能的运行结果
请求
我们来看看 mitmdump 还有哪些常用的功能,先用一个实例感受一下:
from mitmproxy import ctx
def request(flow):
request = flow.request
info = ctx.log.info
info(request.url)
info(str(request.headers))
info(str(request.cookies))
info(request.host)
info(request.method)
info(str(request.port))
info(request.scheme)
这里的 request 方法是 mitmdump 针对请求提供的处理接口。将 script.py 脚本修改为如上内容,然后在手机上打开 http://www.httpbin.org/get ,即可看到电脑端的控制台输出了一系列请求。这里我们找到第一个请求,控制台打印出了该请求的一些常见属性,如请求URL、请求头、请求Cookies、请求Host、请求方法、请求端口、请求协议等,结果如图 12-33 所示。
我们可以修改其中的任意属性,就像最初修改请求头 User-Agent 一样,直接赋值即可。例如,修改请求 URL:
def request(flow):
url = "https://www.baidu.com"
flow.request.url = url
这里直接将请求 URL 修改成了百度,会发生什么事情呢?手机端会显示如图 12-34 所示的页面。
图12-33 输出结果 图12-34 手机端显示的页面
有意思的是,浏览器最上方呈现的网址还是 http://www.httpbin.org ,而页面已经变成了百度首页。我们用简单的脚本就成功修改了目标网站;"意味着这种方式使修改和伪造请求变得轻而易举,这也是中间人攻击。
|
通过这个实例我们也能知道,URL 正确不代表页面肉容也正确。我们需要进一步提高安全防范意识。 |
了解了基本用法,很容易就能获取和修改请求内容,另外可以通过修改 Cookie、添加代理等方式规避反爬。
响应
对于爬虫来说,更加关心的其实是响应内容,响应体才是要爬取的结果。和请求一样,mitmdump 针对响应也提供了对应的处理接口,就是 response 方法:
from mitmproxy import ctx
def response(flow):
response = flow.response
info = ctx.log.info
info(str(response.status_code))
info(str(response.headers))
info(str(response.cookies))
info(str(response.text))
将 script.py 脚本修改为如上内容,然后用手机访问 http://www.httpbin.org/get ,电脑端控制台会输出响应状态码、响应头、响应 Cookie 和响应体几个属性,其中最后一个就是网页的源代码。电脑端控制台的输出结果如图 12-35 所示。
图12-35 电脑端控制台的输出结果
我们通过 response 方法获取了每个请求对应的响应内容,接下来对响应信息进行提取和存储,就成功完成爬取了。
实战准备
我们已经可以利用 mitmdump 对接 Python 脚本实时处理响应内容了,接下来看看能否将其应用于 App 爬虫。先尝试将一个电影 App 的接口数据爬取下来,再将结果实时保存到 MongoDB 数据库中。
请确保已经正确安装好 mitmproxy 和 mitmdump,手机和电脑处于同一个局域网下,以及配置好了 mitmproxy 的 CA 证书,具体的配置流程可以参考 https://setup.scrape.center/mitmproxy 。
本节使用的示例 App 的下载地址为 https://app5.scrape.center/ ,请在手机上安装这个 App。
抓取分析
首先获取一下当前页面的 URL 和返回内容,编写一个脚本,
def response(flow):
print(flow.request.url)
print(flow.response.text)
这里只打印了请求 URL 和响应体这两个最关键的部分,将此脚本保存 spider.py 文件。接下来启动 mitmdump:
mitmdump -s spider.py
打开手机上下载的 app5,便可以看到电脑端控制台输出了相应内容,接着不断下拉,可以看到手机屏幕上的电影数据在一页一页加载,如图 12-36 所示。
观察一下电脑端控制台,可以看到输出了类似 JSON 数据的结果,部分结果如图 12-37 所示。
图 12-36 不断加载的电影数据 图 12-37 控制台输出的部分结果
选取其中的一个 URL 观察一下,具体为 https://app5.scrape.center/api/movie/?offset=30&limit=10&token=M2U5NjYxZjEwNmQyMDlkYmYyNTIzZGFkYmZkYzdiNThlYTgzOWQ0MyWxNjIxNzAzMDA5%oA ,可以看到除了 offset、limit 参数,里面还带有一个 token。我们把结果中的响应体复制下来,并格式化处理,结果如图 12-38 所示。
图12-38 格式化处理后的响应体内容
通过对比可以发现,图 12-38 中的内容和 app5 里显示的电影数据完全一致,说明我们成功截获了响应体。
有人可能会想,为什么这里不能像 12.1 节那样直接用 Python 请求爬取数据呢?因为这次 App 的接口带有一个加密参数 token,仅凭借抓包根本没法知道它是如何生成的,而通过 Python 直接构造请求来爬取数据需要模拟 token 的生成逻辑,所以不能。
好在我们已经可以直接通过 mitmdump 抓取结果,相当于请求是 App 构造的,我们直接拿到了响应结果,得来全不费功夫,也不需要再去构造请求了,直接把响应结果保存下来就好。
数据抓取
现在我们稍微完善一下 spider.py 脚本,增加一些过滤 URL 和处理响应结果的逻辑:
import json
from mitmproxy import ctx
def response(flow):
url = "https://app5.scrape.center/api/movie/"
if flow.request.url.startswith(url):
text = flow.response.text
if not text:
return
data = json.loads(text)
items = data.get('results')
for item in items:
ctx.log.info(str(item))
之后重新打开 app5,并在电脑端控制台观察输出结果,如图 12-39 所示。
图12-39 电脑端控制台的输出结果
可以看到输出了每部电影的数据,数据以 JSON 形式呈现。
提取保存
现在再修改一下 spider.py 脚本,将返回结果保存下来,保存为文本文件即可,改后的脚本内容如下:
import json
from mitmproxy import ctx
import os
OUTPUT_FOLDER = 'movies'
os.path.exists(OUTPUT_FOLDER) or os.makedirs(OUTPUT_FOLDER)
def response(flow):
url = "https://app5.scrape.center/api/movie/"
if flow.request.url.startswith(url):
text = flow.response.text
if not text:
return
data = json.loads(text)
items = data.get('results')
for item in items:
ctx.log.info(str(item))
with open(f'{OUTPUT_FOLDER}/{item["name"]}.json','w',encoding='utf-8') as f:
f.write(json.dumps(item,ensure_ascii=False,indent=2))
然后重新打开 app5,滑一下屏幕,这时候观察 movies 文件夹,会发现成功生成了一些 JSON 文件,这些文件以电影名称命名,如图 12-40 所示。
图12-40 movies 文件夹下生成的文件
任意打开其中一个文件,如阿飞正传 json 文件,可以看到如图 12-41 所示的结果。
图12-41 打开阿飞正传 json 文件
可以看到这部电影的数据已经实时保存下来了,其他电影也是如此。