powershell无法启动虚拟环境

0. 准备工作

参考文档:

什么是mitmproxy?

  • mitm 为 **Man-In-The-Middle attack**;

  • mitmproxy 即为 中间人攻击代理。

为什么要用mitmproxy?相比Fiddler 和 Charles它有什么优势?

  • mitmproxy 不仅可以截获请求帮助开发者查看、分析,更可以通过自定义脚本进行二次开发。举例来说,利用 Fiddler 可以过滤出浏览器对某个特定 url 的请求,并查看、分析其数据,但实现不了高度定制化的需求,类似于:“截获对浏览器对该 url 的请求,将返回内容置空,并将真实的返回内容存到某个数据库,出现异常时发出邮件通知”。而对于 **mitmproxy**,这样的需求可以通过载入自定义 python 脚本轻松实现。

特征

  • 拦截HTTP和HTTPS请求和响应并即时修改它们
  • 保存完整的HTTP对话以供以后重播和分析
  • 重播HTTP对话的客户端
  • 重播先前记录的服务器的HTTP响应
  • 反向代理模式将流量转发到指定的服务器
  • macOS和Linux上的透明代理模式
  • 使用Python对HTTP流量进行脚本化更改
  • 实时生成用于拦截的SSL / TLS证书
  • 还有更多……

1. 安装

1.1 模块安装

安装:

1
pip install mitmproxy

查看安装成功与否:

  • **cmd**窗口,查看版本
1
mitmdump --version

出现以下字眼,则是成功安装了。

1
2
3
4
Mitmproxy: 5.2
Python: 3.7.6
OpenSSL: OpenSSL 1.1.1g 21 Apr 2020
Platform: Windows-10-10.0.18362-SP0

1.2 证书安装

模块安装完成后,首次运行 mitmproxymitmdump,在当前用户下面会生成几个ca证书。

从**Windows**用户界面的 **.mitmproxy**中,点击进去,可以看到有多个证书,

证书作用
mitmproxy-ca.pemPEM格式的证书和私钥
mitmproxy-ca-cert.pemPEM格式的证书。使用它可以在大多数非Windows平台上分发。
mitmproxy-ca-cert.p12PKCS12格式的证书。适用于Windows(安装这个
mitmproxy-ca-cert.cer与.pem相同的文件,但某些Android设备需要扩展名。

**Windows**端:

  • mitmproxy-ca-cert.p12

**手机**端:

  • 配置好wifi连接之后,访问 mitm.it
  • 下载对应手机系统的证书,然后安装即可。

抓包示例:

  • **Windows**端
    • 要使用代理 + 走指定的端口哦!!!
  • **手机**端
    • 配置**Windows**端的ip + 指定代理!!

2. 组件

当我们谈论” **mitmproxy**“时,我们通常指这三种工具中的任何一种–它们只是同一核心代理的不同前端。

ToolsDescription
mitmproxy供交互式界面(**Windows**系统不可用
mitmdump提供简单明了的终端输出
mitmweb提供基于浏览器的图形界面

正常使用用**mitmdump**就足够了。

所以后面的案例也是使用 mitmdump 去做展示。

**mitmproxy**默认绑定的端口为 127.0.0.1:8080

注意一下:

  • 如果端口被占用了,会提示报错哦!

2.1 mitmproxy

**Windows**系统不可用,这里暂不展示。

2.2 mitmdump

查看所有命令:

1
mitmdump --help

查看版本:

1
mitmdump --version

常用命令:

1
2
3
4
5
-p 8888			# 指定端口
-s xxx.py # 执行指定脚本
-w outfile # 指定输出文件
-q quiet # 仅匹配脚本过滤后的数据包
"~m post" # 仅匹配Post请求

带有颜色的print:

  • **log**,带有输出不同颜色的功能(个人觉得没有什么用
    • info 白色
    • warn 黄色
    • error 红色

注意这里要使用cmd,使用PowerShell显示出来的颜色效果不完整。

mitmDemoOne.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Demo:
def request(self, flow: mitmproxy.http.HTTPFlow):
"""Print different colors"""
url = flow.request.url
if 'sunrise' in url:
print(type(url))
ctx.log.info('Color White:' + url)
ctx.log.warn('Color Yellow:' + url)
ctx.log.error('Color Red:' + url)


addons = [
Demo()
]

2.3 mitmweb

监听的端口是 127.0.0.1:8080

同时提供一个 web 交互界面在 127.0.0.1:8081

用 百度一下 示例。

介绍:

  • 拦截

    • 修改请求前数据
    • 修改请求后数据
  • 筛选

  • 高亮

  • 重放请求


3. 简单使用示例

3.1 使用示例

正常使用用**mitmdump**就足够了。所以这里主要用 mitmdump 来做一个展示,

后面的案例也是使用 mitmdump 去做展示。

基本操作的话,那只看 常规代理 方式就可以了;

操作模式:https://docs.mitmproxy.org/stable/concepts-modes/

脚本编写:https://docs.mitmproxy.org/stable/addons-scripting/

如何工作:https://docs.mitmproxy.org/stable/concepts-howmitmproxyworks/

测试网站:

常用的两个函数简单演示:

这里介绍一下使用的比较多的两个函数,其他的可以通过官方文档去进行一个系统的学习。

1
2
3
4
5
def request(flow):
pass

def response(flow):
pass
  • request
commonDescription
request = flow.request
request.urlurl
request.host域名
request.headers请求头
request.method方式:POST、GET等
request.scheme类型:http、https
request.path路径,URL除域名之外的内容
request.query返回MultiDictView类型的数据,URL的键值参数
request.query.keys()获取所有请求参数键值的键
request.query.values()获取所有请求参数键值的值
request.query.get('wd')获取请求参数中**wd** 键的值(前提是要有 wb 参数
request.query.set_all('wd', ['python'])wd 参数的值修改为 python

修改请求头:

mitmDemoTwo.py

1
flow.request.headers['User-Agent'] = 'Mozilla/5.0'

将百度搜索修改为python:

mitmDemoThree.py

1
2
3
4
5
6
7
8
9
10
11
12
def request(flow):
if 'https://www.baidu.com' in flow.request.url:
# 取得请求参数wd的值
print(flow.request.query.get('wd'))
# 获取所有请求参数键值的键
print(list(flow.request.query.keys()))
# 获取所有请求参数键值的值
print(list(flow.request.query.values()))
# 修改请求参数
flow.request.query.set_all('wd',['python'])
# 打印修改过后的参数
print(flow.request.query.get('wd'))

  • response
commonDescription
response = flow.response
response.status_code响应码
response.text文本(同下)
response.contentBytes类型
response.headers响应头
response.cookies响应cookie
response.set_text()修改响应的文本
response.get_text()文本(同上)
flow.response= flow.response.make(404)响应404

修改文本

mitmDemoFour.py

1
flow.response.set_text(text)

拒绝响应

mitmDemoFour.py

1
2
3
4
# 同下
flow.response = mitmproxy.http.HTTPResponse.make(401)
# 同下
flow.response= flow.response.make(404)

拒绝响应:在百度搜索 十八禁

mitmDemoFive.py

1
2
3
4
5
6
if flow.request.query.get('wd') == '十八禁':
flow.response = mitmproxy.http.HTTPResponse.make(
404, # (optional) status code
b"You son of a bitch, Please leave.", # (optional) content
{"Content-Type": "text/html"} # (optional) headers
)

3.1.1 简单应用

需求

1
2
3
1. 修改请求(如果是搜索雷锋,则修改为 是小菜一碟吖
2. 修改响应(将页面所有 Python 字眼 替换为 是小菜一碟吖
3. 如果存在少儿不宜字眼(例:十八禁、迷药等),则拒绝响应,引导他向好

代码:

mitmDemoSix.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import mitmproxy.http
from mitmproxy import ctx


class Demo:
def request(self, flow: mitmproxy.http.HTTPFlow):
"""Do somethings"""
request = flow.request
if 'https://www.baidu.com/' in request.url:
keyword = request.query.get('wd')
filter_words = ['迷药', '十八禁']
if keyword == '雷锋':
# 修改请求参数
flow.request.query.set_all('wd', ['是小菜一碟吖'])
if keyword in filter_words:
flow.response = mitmproxy.http.HTTPResponse.make(
status_code=400,
content=''' <title>娘希匹!!!</title>
<h1>警告!!!即将查水表</h1>
<h2>望你善良,愿你向上</h2>
<a>点击跳转:</a>
<a href="https://space.bilibili.com/39880798" target="_blank">是小菜一碟吖的学习频道</a>
''',
# content="你可拉倒吧!!!查询的什么娘希匹玩意儿!!!"
headers={"Content-Type": "text/html"}
)

def response(self, flow: mitmproxy.http.HTTPFlow):
"""Do somethings"""
response = flow.response
if flow.request.host == 'www.baidu.com':
replace_words = ['你好', 'python', 'Python']
text = response.get_text()
text = list(map(lambda x: text.replace(x, '是小菜一碟吖'), replace_words))[0]
flow.response.set_text(text=text)


addons = [
Demo()
]

当然,这里超纲了,也就是觉得有趣,就拉出来讲一讲。


3.2 报错解决

1
2
502 Bad Gateway
Certificate verification error for xxx: unable to get local issuer certificate (errno: 20, depth: 0)

网关证书验证错误,解决方法有二:

  1. 执行–ssl-insecure
  2. 下载最新的cacert.pem替换( Python安装路径\Lib\site-packages\certifi )的目录证书

4. 案例展示

4.1 mitmproxy + Selenium

电脑端自动化爬虫

案例说明:

  • Selenium 自动翻页,
  • mitmproxy 进行信息采集,
  • 在指定网站,输入 指定关键词 以及 爬取的页码数量,即可。

注意点:

  • 评论数量是另外一个文件,需要另外进行解析。
  • 返回评论适量的链接有两个,要区别做判断。

selenium JD:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
"""输入关键词 + 页码数量 Jd自动翻页程序"""

import time
from selenium import webdriver


class JdSpider:
"""OK"""

def __init__(self, keyword=None, page=None):
self.url = 'https://www.jd.com/'
self.browser = None
self.page = int(page)
self.keyword = keyword

def __del__(self):
self.browser.close()

def open_browser(self):
"""打开浏览器"""
self.browser = webdriver.Chrome()
# self.browser.maximize_window()
self.browser.set_window_size(1350, 850)

def search_keyword(self):
'''搜索关键字'''
self.browser.get(self.url)
# 输入内容
self.browser.find_element_by_xpath('//*[@id="key"]').send_keys(self.keyword)
# 模拟点击
self.browser.find_element_by_xpath('//*[@id="search"]/div/div[2]/button').click()

def turn_page(self):
'''翻页'''
self.browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
time.sleep(3)
if self.browser.page_source.find('pn-next disabled') == -1:
self.browser.find_element_by_class_name('pn-next').click()

def main(self):
'''函数启动接口'''
self.open_browser()
self.search_keyword()
for count in range(self.page):
self.turn_page()


if __name__ == '__main__':
keyword = input("Enter the keywords to search:")
page = input("Enter the Page to download:")
spider = JdSpider(keyword=keyword, page=page)
spider.main()

网页解析 及 保存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import re
import os
import csv
import json
from lxml import etree


def format_common(_list: list):
"""格式化函数"""
_str = ''.join(_list)
_str = _str.replace('\n', '').replace('\t', '').replace('¥', '')
return _str


def format_state(_list: list):
"""格式化函数"""
_str = ' '.join(_list)
_str = _str.replace('\n', '').replace('\t', '').replace('¥', '')
return _str


class SaveData:
"""OK"""

def __init__(self, data):
self.comment_data = data[0]
self.other_data = data[1]

def judge_exists(self, path):
"""判断文件是否已存在"""
if os.path.exists(path):
return
title = ["商铺名称", "说明", "价格", "评价人数", "商品名称"]
with open(path, 'a+', encoding='utf-8', newline='') as f:
writer = csv.writer(f) # 创建写 对象
writer.writerow(title) # 写入单行

def save_to_csv(self, data: list):
"""保存为csv"""
path = r'./data/JdGoodsInfo.csv'
self.judge_exists(path)
with open(path, 'a+', encoding='utf-8', newline='') as f:
writer = csv.writer(f) # 创建写 对象
writer.writerows(data) # 写入多行

def parse_data(self):
"""解析网页"""
if not self.other_data or not self.comment_data:
return

comments_item = json.loads(re.findall("jQuery\d+\((.*?)\);", self.comment_data)[0])['CommentsCount']
if len(comments_item) != 30:
return

_data = list()
xpath_html = etree.HTML(self.other_data)
xpath_items = xpath_html.xpath('//li[@class="gl-item"]')
for xpath_item, comment_item in zip(xpath_items, comments_item):
_parse = xpath_item.xpath
shop = format_common(_parse('.//div[@class="p-shop"]//text()'))
icons = format_state(_parse('.//div[@class="p-icons"]//text()'))
price = format_common(_parse('.//div[@class="p-price"]//text()'))
name = format_common(_parse('.//div[@class="p-name p-name-type-2"]//text()'))
comment = comment_item['CommentCountStr']
_data.append((shop, icons, price, comment, name))
self.save_to_csv(data=_data)

def main(self):
"""开始干活"""
self.parse_data()

mitm代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding:utf-8 -*-
# author : SunriseCai
# datetime : 2020/11/21 10:47
# software : PyCharm

import json
import mitmproxy.http
from mitmSaveData import SaveData


class Demo:
def __init__(self):
self.other_data = None

def response(self, flow: mitmproxy.http.HTTPFlow):
url = flow.request.url
if 'jd.com' not in url:
return
# 商品信息链接
if 'https://search.jd.com/s_new.php?keyword=' in url:
self.other_data = flow.response.text or None
# 评论链接
if 'https://club.jd.com/comment' in url:
comment_data = flow.response.text
SaveData([comment_data, self.other_data]).main()
comment_data, self.other_data = None, None


addons = [
Demo(),
]

遗留问题:

  • 搜索的首页不是 XHR 形式加载出来的,这个不想做适配了。

4.2 mitmproxy + Appium

手机端自动化爬虫

案例说明:

  • Appium 自动翻页,
  • mitmproxy 进行信息采集,
  • 在指定 App,输入 指定关键词 以及 向下滑动的数量次数,即可。