Mitmproxy - Python抓包神器

做App爬虫、Web接口调试的时候,你是不是遇到过这些痛点:Fiddler跨Windows为主,Mac/Linux体验一般;Charles功能全但要付费;纯命令行tcpdump/Wireshark要手动筛HTTP/HTTPS太麻烦?

Mitmproxy 完美解决这些问题——它是一款基于Python编写的免费开源、跨平台、支持Python脚本深度定制的交互式HTTP/HTTPS代理,不管是临时抓接口、还是自动化爬取/修改流量,都是开发者和爬虫er的首选神器。

本文带你快速入门,从安装、基础模式,到一个完整的**App定向流量拦截脚本,最后讲最佳实践。


一、安装和基础模式快速上手

Mitmproxy 核心提供三个独立工具,按从入门到深入、从可视化到自动化**覆盖不同场景:

1.1 安装

只需要一条Python的pip命令即可(建议用Python 3.8+):

pip install mitmproxy

1.2 三种模式一览

# 模式1:Web可视化模式(新手友好首选)
# 运行后自动打开浏览器http://127.0.0.1:8081
mitmweb -p 8081

# 模式2:纯命令行交互模式(适合终端控/临时调试)
# 带彩色终端上的类Vim快捷键操作
mitmproxy -p 8082

# 模式3:自动化脚本模式(核心功能,本文重点)
# 直接运行自定义Python脚本处理流量
mitmdump -s your_script.py -p 8080

二、实战脚本:App定向流量拦截与分析

下面的脚本是一个**为App爬虫定制的核心工具,它可以:

  • 自动识别`api/mobile/app/sdk/analytics等特征的请求
  • 提取完整的请求/响应数据(包括JSON、Headers、Query参数)
  • 自动保存结果到本地JSON文件
  • 统计API调用次数、状态码错误率

将代码保存为app_traffic_analyzer.py即可使用。

from mitmproxy import http, ctx
import json
import re
import time
import os
from datetime import datetime
from typing import Dict, Any

class AppTrafficAnalyzer:
    """App定向流量拦截与分析器"""
    
    def __init__(self):
        self.intercepted_data = []
        # 可以根据目标应用/网站的域名特征(按需添加)
        self.target_features = {
            'domains': [r'.*api\..*', r'.*mobile\..*', r'.*app\..*', r'.*sdk\..*'],
            'ua_keywords': ['mobile', 'android', 'ios', 'app'],
            'path_keywords': ['/api/', '/mobile/', '/sdk/', '/v1/', '/v2/']
        }
        self.output_dir = 'mitmproxy_analysis'
        os.makedirs(self.output_dir, exist_ok=True)
        
    def request(self, flow: http.HTTPFlow) -> None:
        """请求拦截入口"""
        if not self._is_target_flow(flow):
            return
            
        # 提取请求信息
        req_info = {
            'type': 'request',
            'timestamp': datetime.fromtimestamp(flow.request.timestamp_start).isoformat(),
            'method': flow.request.method,
            'url': flow.request.pretty_url,
            'host': flow.request.host,
            'path': flow.request.path,
            'query': dict(flow.request.query) if flow.request.query else {},
            'headers': dict(flow.request.headers),
            'text': flow.request.text if flow.request.content else ''
        }
        self.intercepted_data.append(req_info)
        ctx.log.info(f"🎯 捕获目标请求: {flow.request.pretty_url}")
                
    def response(self, flow: http.HTTPFlow) -> None:
        """响应拦截入口"""
        if not self._is_target_flow(flow):
            return
            
        # 提取响应信息
        resp_info = {
            'type': 'response',
            'timestamp': datetime.fromtimestamp(flow.response.timestamp_end).isoformat(),
            'status_code': flow.response.status_code,
            'url': flow.response.url,
            'headers': dict(flow.response.headers),
            'text': flow.response.text if flow.response.content else ''
        }
        self.intercepted_data.append(resp_info)
        ctx.log.info(f"📡 捕获目标响应: {flow.response.url} - {flow.response.status_code}")
    
    def _is_target_flow(self, flow: http.HTTPFlow) -> bool:
        """判断是否为目标流量"""
        # 先检查域名
        for pattern in self.target_features['domains']:
            if re.search(pattern, flow.request.host, re.IGNORECASE):
                return True
                
        # 检查User-Agent
        ua = flow.request.headers.get('User-Agent', '').lower()
        if any(k in ua for k in self.target_features['ua_keywords']):
            return True
            
        # 检查路径
        path = flow.request.path.lower()
        if any(k in path for k in self.target_features['path_keywords']):
            return True
            
        return False
    
    def done(self) -> None:
        """mitmdump停止时自动执行"""
        # 保存所有捕获的数据
        output_file = os.path.join(self.output_dir, f"traffic_{int(time.time())}.json")
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump(self.intercepted_data, f, ensure_ascii=False, indent=2)
        
        ctx.log.info(f"✅ 分析完成,结果已保存到: {output_file}")

# 必须添加到addons列表供mitmproxy识别
addons = [AppTrafficAnalyzer()]

三、完整使用流程

3.1 启动代理并配置设备/浏览器

(1)启动脚本代理

运行刚才保存的脚本:

mitmdump -s app_traffic_analyzer.py -p 8080

注意记住**你运行代理的设备IP(本地就是127.0.0.1,手机抓包的话就是局域网IP,比如Windows/Linux/Mac的话可以用ifconfig/ipconfig查看)和端口8080

(2)配置浏览器/手机代理

  • 浏览器:直接在设置→网络→代理→手动配置HTTP/HTTPS代理,填入IP和端口。
  • 手机
    • iOS:设置→无线局域网→点击连接的WiFi→配置代理→手动,填入IP和端口。
    • Android:设置→WLAN→长按连接的WiFi→修改网络→高级选项→代理→手动,填入IP和端口。

3.2 安装并信任Mitmproxy CA证书(必做!否则无法抓HTTPS)

设备配置好代理后,浏览器/手机浏览器访问mitm.it

  • 下载对应平台的证书。
  • iOS特别注意
    1. 安装描述文件后,去设置→通用→VPN与设备管理信任描述文件。
    2. 再去设置→通用→关于本机→证书信任设置,**完全信任Mitmproxy CA证书。
  • Android 7.0+特别注意: 系统默认不信任用户证书,两种方案:
    1. 非Root友好:用VirtualXposed/太极等虚拟环境,把目标App放进去,只信任虚拟环境的用户证书。
    2. Root:把下载的证书放到/system/etc/security/cacerts/目录下,修改权限为644

3.3 开始抓包并查看结果

打开目标App/网站操作,mitmdump的终端会实时打印捕获的流量;停止mitmdump(按Ctrl+C),查看mitmproxy_analysis目录下的JSON文件即可。


四、简单的最佳实践

  1. 按需修改target_features:脚本默认的特征比较宽泛,如果你只抓某个特定App,可以去mitmweb先临时抓一波,找到它的固定域名/UA/路径特征填进去。
  2. 扩展脚本功能:可以在request/response方法里添加更多逻辑,比如:
    • 修改请求Headers/参数,测试接口边界情况。
    • 自动提取JSON里的关键数据(比如Token、商品列表)。
    • 把数据直接存入MongoDB/MySQL数据库。
  3. 移动端抓包避坑:iOS要确保设备和代理在同一局域网,Android要注意SSL Pinning的App——如果抓不到HTTPS,说明App做了SSL Pinning,可以尝试用Frida脱壳+Hook SSL Pinning。