mitmdump实时抓包处理

mitmdumpmitmproxy 的命令行接口,可以对接 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 数据库中。

请确保已经正确安装好 mitmproxymitmdump,手机和电脑处于同一个局域网下,以及配置好了 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 ,可以看到除了 offsetlimit 参数,里面还带有一个 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 文件

可以看到这部电影的数据已经实时保存下来了,其他电影也是如此。

总结

本节主要讲解了 mitmdump 的用法及脚本的编写方法,借助 mitmdump,我们可以直接使用 Python 脚本实时处理拦截的请求和响应,由于可以实时获取响应内容,因此可以在此基础上进行一些实时处理,如提取和存储数据,这样就能轻而易举地把数据爬取下来了。