某空气质量检测平台的爬虫——mitmproxy的简单使用
前言
笔者越来越喜欢使用mitmproxy这个工具了,哈哈哈哈哈
网站如下
aHR0cHM6Ly93d3cuYXFpc3R1ZHkuY24v
正文
前置分析
进去之后,按F12,发现如下

居然不准使用,不是,哥们。
使用快捷键ctrl+shift+i。然后

还有debugger
右键选择永远不再此处暂停

再次刷新

啊,可以啊,搞这么多东西
既然如此,看看源码
如下

在his这个文件里面可以发现如下东西

原来是这个,是时候启动mitmproxy了,当然可以使用注入的方式,笔者就使用mitmproxy了
拦截并确定关键信息
首先,慢慢来。
新建一个proxy.py文件,其中内容暂时如下
from mitmproxy import httpdef response(flow: http.HTTPFlow) -> None:path = flow.request.pathprint(path)
启动
mitmweb --mode reverse:xxxxxx -p 8082 --ssl-insecure -s proxy.py
笔者一般使用mitmweb 这个工具,网页更好看,
因为笔者反向代理了网站,并监听了8082端口,因此
在浏览器输出127.0.0.1:8082
可以查看终端的输出,笔者给出关键的路由

也可以在mitmweb打开的页面中搜索,关键字,~bs是搜索响应的内容
如下

笔者不给出关键信息,总之,可以确定路由 / 就是需要拦截并修改的路由
修改 / 路由返回的结果
复制本来的数据,然后把有反调试代码去掉,去掉后的代码如下
if path == '/':flow.response.text="""......
......
<script type="text/javascript">txsdefwsw();$(function(){loadTab();if(!isSupportCanvas()){$("#browertip").show();}});function isSupportCanvas(){var elem = document.createElement('canvas');return !!(elem.getContext && elem.getContext('2d'));}
</script>
"""
刷新页面,发现还是有debugger

查看堆栈的调用

可以发现是txsdefwsw这个函数调用的,直接去掉
刷新
发现还是有debugger

不是哥们,再次查看堆栈的调用

发现是city_realtime.php?v=2.3这个东西调用的,进去看看

发现是一个eval,里面还有一些看不懂的代码,不管三七二十一,把这个eval去掉
复制其中的内容,修改关键代码
修改city_realtime的路由
首先确定完整的路由,结果如下

很好。
去掉863行的eval就可以了,即
if path== '/html/city_realtime.php?v=2.3':flow.response.text="""
.......
......
"""
再次刷新

看到这里,我就知道,解决了,反调试。
还没完。
爬虫的分析
看参数

看响应

发现都是加密的,哈哈哈哈哈哈,好好好好好
可以发现一个信息,控制台有输出
![]()
点进去,看看是什么打印的,如下

再次查看堆栈的调用

发现一个getData,看看

没什么信息
再往前看看

哈哈哈哈哈,笔者直觉,就是这里了。
首先,在ajax哪里打个断点

输出pUpOMOU看看,如下

看来这个pvkWB5TRrM9kT5就是加密参数的函数,进去看看

加密过程很简单,有个MD5,还有base64。
解法的解密也很容易发现

就这个,可以解密结果,AES、DES那些是自定义的,搜索关键字即可
后面是事情感觉很简单
===========明天再说=============
编写爬虫
笔者直接给出关键文件index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="https://cdn.bootcdn.net/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script><script>const askkGFJ5KlKY = "a5slhqEdLos7hvjr";const asi1rHbZTh7V = "bhb9eTODiif32jv0";const dskoNjS2oMS6 = "hXenRdbrF1iqaMLr";const dsi7NK4eh564 = "xAWozmps3eqYftMA";function hex_md5(data) {return CryptoJS.MD5(data).toString()}var DES = {decrypt: function (text, key, iv) {var secretkey = (CryptoJS.MD5(key).toString()).substr(0, 16);var secretiv = (CryptoJS.MD5(iv).toString()).substr(24, 8);secretkey = CryptoJS.enc.Utf8.parse(secretkey);secretiv = CryptoJS.enc.Utf8.parse(secretiv);var result = CryptoJS.DES.decrypt(text, secretkey, {iv: secretiv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7});return result.toString(CryptoJS.enc.Utf8)}};var AES = {decrypt: function (text, key, iv) {var secretkey = (CryptoJS.MD5(key).toString()).substr(16, 16);var secretiv = (CryptoJS.MD5(iv).toString()).substr(0, 16);secretkey = CryptoJS.enc.Utf8.parse(secretkey);secretiv = CryptoJS.enc.Utf8.parse(secretiv);var result = CryptoJS.AES.decrypt(text, secretkey, {iv: secretiv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7});return result.toString(CryptoJS.enc.Utf8)}};var BASE64 = {encrypt: function (text) {return btoa(unescape(encodeURIComponent(text)));},decrypt: function (base64Str) {return decodeURIComponent(escape(atob(base64Str)));}};function get_data(data) {data = AES.decrypt(data, askkGFJ5KlKY, asi1rHbZTh7V);data = DES.decrypt(data, dskoNjS2oMS6, dsi7NK4eh564);return BASE64.decrypt(data)}function get_params(method, obj) {var appId = '0e5df784c9068129938846e4fd750fdd';var clienttype = 'WEB';var timestamp = new Date().getTime();var param = {appId: appId,method: method,timestamp: timestamp,clienttype: clienttype,object: obj,secret: hex_md5(appId + method + timestamp + clienttype + JSON.stringify(obj))};return BASE64.encrypt(JSON.stringify(param));}</script>
</head>
<body></body>
</html>
get_params用于加密参数,get_data用于解密结果。
确定一下参数

笔者,随便选择了一个城市,发现是一个对象
{city:'南京'}
还有另一个参数

是一个字符串——GETDATA
行,现在万事俱备,准备些爬虫。
笔者决定使用feapder,认真地写一个爬虫。
feapder官方文档|feapder-document
https://feapder.com/笔者使用轻量爬虫
feapder create -s spider
feapder create --setting
确定url是http://127.0.0.1:8082/apinew/aqistudyapi.php,是类型是POST
import feapder
from DrissionPage import ChromiumPageclass Spider(feapder.AirSpider):def __init__(self, page):super(Spider, self).__init__()self.page = pagedef start_requests(self):params=self.page.run_js("return get_params(arguments[0],arguments[1])",'GETDATA',{"city":"北京"})data={'h1zlb1QoZ':params}yield feapder.Request("http://127.0.0.1:8082/apinew/aqistudyapi.php",data=data)def parse(self, request, response):text=response.textresult=self.page.run_js("return get_data(arguments[0])",text)print(result)if __name__ == "__main__":page = ChromiumPage()page.get("http://localhost:63342/study-spider/src/course/25-11-14/aqistudy/index.html")Spider(page).start()
结果如下

没问题。
笔者还发现,会把数据保存到本地,使用aes保存的

可以右键清除

在左边的选项栏,如下

每个都有一个对应的php文件,每个php文件发送请求的参数不一样,其他不变
全部拦截并修改,笔者使用正则替换,因此,proxy.py文件的内容如下
from mitmproxy import http
import re
replace_path=['/html/city_realtime.php?v=2.3','/html/city_detail.php?v=1.10','/html/city_map.php?v=1.6','/html/province_map.php?v=1.6','/html/city_compare.php?v=1.10','/html/city_time.php?v=1.3','/html/city_statistics.php?v=1.6','/html/city_weather_compare.php?v=1.6'
]def response(flow: http.HTTPFlow) -> None:path = flow.request.pathif path == '/':text=flow.response.textpattern = r'<script type="text/javascript">\s*var debugflag = false;[\s\S]*?endebug\(.*?\)[\s\S]*?</script>'replacement = '''
<script type="text/javascript">$(function(){loadTab();if(!isSupportCanvas()){$("#browertip").show();}});function isSupportCanvas(){var elem = document.createElement('canvas');return !!(elem.getContext && elem.getContext('2d'));}
</script>'''flow.response.text = re.sub(pattern, replacement, text, count=1)if path in replace_path:print(path)text=flow.response.textresult = re.sub(r"eval\((.*?)\)\);", "console.log('gg')", text, count=1)flow.response.text=result
截止到目前,是可以使用的,以后可能会发生变化,随机应变,无所谓。
就这样
总结
mitmproxy反向代理原来是可以主动访问127.0.0.1:8082的,会把网站代理到这里,当然,并不是全部的网站都行,有些会有其他限制的。
总之,简单使用了一下

