中间人(Man-in-the-Middle,MITM)攻击是指攻击者与通信的两端分别创建独立的联系,并交换其所收到的数据,使通信的两端认为其正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通信双方的通话,并插入新的内容或者修改原有内容。
中间人攻击,名字看起来很高级的样子,其实大多数人在小时候都遭受过甚至使用过——上课传纸条。A要把纸条传给B,但是A与B距离太远,于是让C来转交纸条。此时,C作为一个中间人,他有两种攻击方式:仅仅偷偷查看纸条的内容,或者先篡改纸条的内容再传给B。
中间人爬虫就是利用了中间人攻击的原理来实现数据抓取的一种爬虫技术。数据抓包就是中间人爬虫的一个简单应用。所以使用Charles也是一种中间人攻击。
在前面分享中讲到模拟登录的时候,有一种方法是在Chrome的开发者工具中把Cookies复制出来,拿给requests来实现绕过登录。所以肯定有读者会想到,使用Charles也可以获得网站的Cookies,然后复制出来给requests登录。这个想法很好,但问题是,无论Chrome还是Charles都是图形界面的软件,所有操作都需要鼠标来协助完成,把Cookies复制出来这个简单的动作难以实现自动化。这个时候,就需要使用mitmproxy了。
一、mitmproxy的介绍和安装
对编程有了解的读者一定听说过“Linux比Windows好”这种言论,那么Linux哪一点比Windows好呢?Linux好就好在它里面有无数强大的命令行下的软件。这些软件不需要鼠标,也没有图形界面,所有的操作就是输入命令。而命令本质上就是文本。操作文本比操作图形界面容易得多。操作文本可以实现自动化,而操作图形界面却难以实现自动化。
所以,请想一想,有什么办法能自动把Charles里面的Cookies拿给爬虫使用?如何把鼠标指针自动移动到Charles的窗口里?如何自动选中Cookies然后自动复制?如何再把鼠标指针自动移动到另一个终端窗口里面,自动写代码并把Cookies放到Redis中,最后爬虫自动从Redis里面取Cookies,安装到自己身上再去爬网站?
除了最后一步,爬虫从Redis取Cookies很容易以外,其他操作要实现自动化都极其困难。
但是如果有一个命令行下面的抓包工具,所有的操作都是文本型的命令,不需要鼠标,不需要图形界面,它抓到的数据包直接就是文本信息,并且可以无缝传递给其他程序,会怎么样呢?
mitmproxy是一个命令行下的抓包工具,它的作用和Charles差不多,但它可以在终端下工作。使用mitmproxy就可以实现自动化的抓包并从数据包里面得到有用的信息。
请开发者在Linux、Mac OS或者Windows 10自带的Ubuntu Bash下使用mitmproxy,只有这样,才能发挥它的最大能力。
对于Mac OS系统,使用Homebrew安装mitmproxy,命令为:
brew install mitmproxy
在Ubuntu中,要安装mitmproxy,首先需要保证系统的Python为Python 3.5或者更高版本,然后执行下面两条命令:
sudo apt-get install python3-dev python3-pip libffi-dev libssl-dev
sudo pip3 install mitmproxy
二、mitmproxy的使用
安装好mitmproxy以后,在终端执行下面的命令,此时会弹出一个窗口,窗口的文字根据系统的不同,可能是英文也可能是中文,大意是询问是否允许Python 3.6接收收入的网络连接,单击“允许”按钮或者“Allow”按钮,如图
接下来会打开一个命令行下的数据监控窗口,如图所示:
mitmproxy的端口为8080端口,在浏览器或者在手机上设置代理,代理IP为计算机IP,端口为8080端口,如图:
设置好代理以后,在手机上打开一个App或者打开一个网页,可以看到mitmproxy上面有数据滚动,如图:
用鼠标在终端窗口上单击其中的任意一个请求,可以显示这个数据包的详情信息,如图:
此时只能访问HTTP网站,要访问HTTPS网站,还需要安装mitmproxy的证书。在手机设置了mitmproxy的代理以后,通过手机浏览器访问http://mitm.it/这个网址,会出现图所示的网页。
根据自己的手机选择对应的图标,就可以弹出安装证书的界面。界面与安装Charles的证书界面基本一样。安装完成证书以后,就可以截获HTTPS的数据包了,如图所示。
到目前为止,mitmproxy就可以像Charles一样使用了。但是既然有了Charles,何必单独介绍一个功能重复的东西呢?那是因为mitmproxy有它擅长的地方,就是可使用Python来定制mitmproxy的行为。
三、使用Python定制mitmproxy
mitmproxy的强大之处在于它还自带一个mitmdump命令。这个命令可以用来运行符合一定规则的Python脚本,并在Python脚本里面直接操作HTTP和HTTPS的请求,以及返回的数据包。
为了自动化地监控网站或者手机发出的请求头部信息和Body信息,并接收网站返回的头部信息和Body信息,就需要掌握如何在Python脚本中获得请求和返回的数据包。
1. 请求数据包
创建一个parse_request.py文件,其文件内容只有两行代码:
def request(flow):
print(flow.request.headers)
在命令行下执行命令:
mitmdump -s parse_request.py
运行命令以后,在手机上打开一个App,可以看到这个App请求的头部信息已经出现在终端窗口中,如图所示。
当然,除了显示头部信息以外,还可以查看请求的Cookies或者Body信息。修改parse_request.py:
def request(flow):
req = flow.request
print(f'当前请求的URL为: {req.url}')
print(f'当前的请求方式为: {req.method}')
print(f'当前的Cookies为: {req.cookies}')
print(f'请求的body为: {req.text}')
运行结果如图:
2. 返回数据包
对于网站返回的数据包,可以再实现一个response()函数。创建一个parse_response.py文件,其内容如下:
import json
def response(flow):
resp = flow.response
print(f'返回的头部为:{resp.headers}')
print(f'返回的body为:{json.loads(resp.content)}'
)
如果网站返回的body正好是JSON格式的字符串,那么就可以使用Python的JSON模块来解析。当然,由于这个函数要处理所有的网络返回的数据包,对返回内容不是JSON格式字符串的情况就会报错,如图:
此时,可以在Python脚本里面针对性地处理某个网站返回的数据。这个时候,就把请求和返回内容放在一起,且函数名必须为“response”,代码如下:
def response(flow):
req = flow.request
response = flow.response
if 'kingname.info' in req.url:
print('这是kingname的网站,也是我的目标网站')
print(f'请求的headers是: {req.headers}')
print(f'请求的UA是: {req.headers["User-Agent"]}')
print(f'返回的内容是:{response.text}')
运行结果如图:
四、mitmdump的使用场景
网站返回的Headers中经常有Cookies,如图:
那是否可以这样写代码:
得到网站返回的Cookies以后就直接塞进Redis里面,这样爬虫就可以直接从Redis里面取已经登录的Cookies了。
这个想法非常好,但是实际做起来会发现,Redis里面始终是空的。这是由于mitmdump的脚本对第三方库的支持有缺陷,很多第三方库都不能运行,甚至Python自带的写文件功能都不能运行。
为了解决这个问题,就需要用到Linux/Mac OS里面非常厉害又很简单的一个工具——管道。这个工具在终端里面就是一根竖线。它可以把左边的内容传递给右边。
mitmdump的脚本使用print()函数把Cookies打印出来,然后通过管道传递给另一个普通的正常的Python脚本。在另一个脚本里面,得到管道传递进来的Cookies,再把它放进Redis里面。
首先是mitmdump的脚本,其代码如下:
代码的运行结果如图:
代码运行以后,会把Cookies放在>>>和<<<中间。
另外创建一个普通的extract.py文件,其内容如下:
如果直接使用Python运行这个代码,会发现它似乎“卡住”了,不会自动结束,也没有任何输出,如图:
如果输入不同的内容,就会发现奥妙所在。当输入普通字符串并按下Enter键的时候,不会有任何内容返回。但是如果按照“>>>内容<<<”的格式输入,就会发现有内容返回出来,如图:
现在需要使用管道把mitmdump运行的脚本和这个Python脚本连接在一起。运行命令:
mitmdump -s parse_one_site.py | python3 extract.py
运行命令以后,在手机上访问网站或者刷新App就会看到图所示的输出。
整个窗口看起来简洁了非常多。这个命令会一直运行,之后就不需要关闭它了。只要有包含Cookies的网络请求,并且域名包含“kingname.info”,那么Cookies就会被截获。由于extract.py是一个普通的Python脚本,任何第三方库都可以在上面正常运行,于是就可以使用Redis来存放Cookies了,代码如下:
到目前为止,自动获取Cookies的功能已经实现了。当然不仅仅是Cookies,Headers里面的所有数据、请求发送的Body里面的所有数据都可以使用这种方式截取下来。
假设有这样一个网站,它的每一个请求都带有一个参数:Token。这个Token的有效期为5min,每过5min就会自动更换。如果使用错误的或者过期的Token来请求网站,就会自动跳转到404页面。现在不知道这个Token是怎么生成的,只知道如果使用浏览器访问的话,这个Token就会出现在请求的Headers里面。有了mitmpdump脚本的功能以后,就不需要关心这个Token是怎么生成的了。首先使用Python与Selenium操作浏览器,设置代理,从而使数据经过mitmdump的脚本,然后用PhantomJS访问这个网页,于是mitmdump就可以成功把这个Token给截获下来并放到Redis里面。那么爬虫就可以从Redis里面读取这个Token并直接使用了。
这里给出为PhantomJS设置代理的代码:
这个脚本可以每5min运行一次来刷新Token。
由于Windows自带的CMD是没有管道这种强大的工具的,因此Windows是没有办法直接完成这个流程的。
也许会有读者疑惑,直接使用Python的第三方库Selenium中的接口,就可以从Phantomjs中读取出Headers和Cookies,为什么还要使用mitmproxy呢?
这是因为,mitmproxy在这里做的是代理的角色,网络请求都可以通过它发出去。而Selenium只是一个网页自动化测试工具,它们负责的是不同的事情,只不过在获取计算机网页的请求上面有一小部分功能重叠而已。Selenium也无法获取App和微信小程序的网络请求。
同时,由于mitmproxy是一个代理,HTTP/HTTPS流量数据经过它,就会被截获。那么Phantomjs没有必要和mitmproxy运行在同一个服务器上。只要mitmproxy运行在一个公网服务器即可。Phantomjs可以运行在个人计算机中,并将代理IP设置为公网服务器的IP。如果Phantomjs写代码的操作特别麻烦,直接为浏览器设置代理来实现手工操作网页也可以让mitmproxy实现抓包。
五、总结
抓包是爬虫开发过程中非常有用的一个技巧。使用Charles,可以把爬虫的爬取范围从网页瞬间扩展到手机App和微信小程序。由于微信小程序的反爬虫机制在大多数情况下都非常脆弱,所以如果目标网站有微信小程序,那么可以大大简化爬虫的开发难度。
当然,网站有可能会对接口的数据进行加密,App得到密文以后,使用内置的算法进行解密。对于这种情况,单纯使用抓包就没有办法处理了,就需要使用下一章所要讲到的技术来解决。
使用mitmproxy可以实现爬虫的全自动化操作。对于拥有复杂参数的网站,使用这种先抓包再提交的方式可以在一定程度上绕过网站的反爬虫机制,从而实现数据抓取。
--------------------------------------
没有自由的秩序和没有秩序的自由,同样具有破坏性。