App 爬虫抓包与数据采集实战——mitmproxy + Fiddler

很多数据只在 App 上有,网页版要么没有要么做了反爬。这一篇讲怎么抓 App 的包、怎么解析接口、怎么用 Python 自动化采集。

一、抓包原理

App 和服务器通信走 HTTP/HTTPS 协议,抓包就是在中间截获请求和响应。

复制代码
App → 抓包工具 → 服务器
      ↓
    记录请求数据(URL、参数、请求头、响应)

两种抓包方式:

方式 工具 适用场景 难度
代理抓包 Fiddler、Charles 简单,新手首选
中间人代理 mitmproxy 可编程、可自动化 ⭐⭐

二、Fiddler 抓包------快速上手

1. 设置 Fiddler

  1. 打开 Fiddler → Tools → Options → HTTPS
  2. 勾选 Capture HTTPS CONNECTs
  3. 勾选 Decrypt HTTPS traffic
  4. 弹出证书安装提示,点 Yes

2. 手机连接代理

  1. 手机和电脑连同一个 Wi-Fi
  2. 手机 Wi-Fi 设置 → 代理 → 手动
  3. 填入电脑的 IP 和 Fiddler 端口(默认 8888)
  4. 手机浏览器访问 http://电脑IP:8888,下载安装 Fiddler 根证书

3. 开始抓包

手机上操作目标 App,Fiddler 里就能看到所有 HTTP 请求。关注这几个信息:

复制代码
URL → 接口地址
Method → GET/POST
Headers → Cookie、Token、User-Agent
Body → 请求参数
Response → 返回数据(JSON 格式)

找接口的技巧: 在 Fiddler 里按接口域名筛选,排除广告 SDK、统计 SDK 等无关请求。

三、mitmproxy------Python 自动化抓包

Fiddler 适合手动分析,mitmproxy 适合写脚本自动化处理。

1. 安装

bash 复制代码
pip install mitmproxy

启动:

bash 复制代码
mitmweb  # 启动带 Web 界面的代理,默认端口 8080

浏览器打开 http://127.0.0.1:8081 就能看到实时请求。

2. 手机配置

和 Fiddler 一样,手机 Wi-Fi 代理设为电脑 IP:8080,访问 mitm.it 安装证书。

3. 编写拦截脚本

python 复制代码
# capture.py
from mitmproxy import http
import json

def request(flow: http.HTTPFlow):
    """拦截请求"""
    url = flow.request.pretty_url
    
    # 只关注目标接口
    if "api.target.com" in url:
        print(f"[请求] {flow.request.method} {url}")
        print(f"[请求头] {dict(flow.request.headers)}")
        
        if flow.request.method == "POST":
            print(f"[请求体] {flow.request.text}")

def response(flow: http.HTTPFlow):
    """拦截响应"""
    url = flow.request.pretty_url
    
    if "api.target.com" in url:
        print(f"[响应] {url}")
        data = flow.response.text
        print(f"[响应数据] {data[:500]}")  # 只打印前 500 字符

运行:

bash 复制代码
mitmweb -s capture.py

4. 自动保存数据到文件

python 复制代码
# save_data.py
from mitmproxy import http
import json
import os
from datetime import datetime

class AppDataCollector:
    def __init__(self):
        self.data_file = f"app_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jsonl"
        self.results = []

    def response(self, flow: http.HTTPFlow):
        url = flow.request.pretty_url
        
        # 过滤目标接口
        if "/api/product/list" in url:
            try:
                data = json.loads(flow.response.text)
                # 提取商品列表
                products = data.get("data", {}).get("list", [])
                for p in products:
                    item = {
                        "id": p.get("id"),
                        "name": p.get("name"),
                        "price": p.get("price"),
                        "sales": p.get("sales"),
                        "crawl_time": datetime.now().isoformat()
                    }
                    self.results.append(item)
                    # 实时写入文件
                    with open(self.data_file, "a", encoding="utf-8") as f:
                        f.write(json.dumps(item, ensure_ascii=False) + "\n")
                
                print(f"已采集 {len(products)} 条商品数据")
            except Exception as e:
                print(f"解析失败: {e}")

addons = [AppDataCollector()]
bash 复制代码
mitmweb -s save_data.py

手机滑动 App 商品列表,数据自动保存到本地。

四、App 爬虫的常见问题

1. 证书校验(SSL Pinning)

很多金融类、支付类 App 做了证书绑定,装了代理证书也无法抓包。

解决方案:

bash 复制代码
# 方案一:用 Android 模拟器 + Xposed 框架
# 安装 JustTrustMe 模块,绕过证书校验

# 方案二:用 Frida 动态注入(更通用)
pip install frida-tools
frida -U -f com.target.app -l bypass_ssl.js
javascript 复制代码
// bypass_ssl.js(Frida 脚本示例)
Java.perform(function() {
    var trustManager = Java.use('javax.net.ssl.X509TrustManager');
    // 覆盖证书校验方法,直接通过
    trustManager.checkServerTrusted.implementation = function() {
        // 什么也不做,跳过校验
    };
});

2. 请求签名

App 的接口参数往往经过加密或签名,直接模拟请求会失败。

python 复制代码
# 分析签名生成逻辑
# 常见情况:把所有参数按字典序排序 + 拼接密钥 + MD5

def generate_sign(params, secret_key="xxx"):
    """模拟 App 的签名算法"""
    sorted_keys = sorted(params.keys())
    raw = "&".join([f"{k}={params[k]}" for k in sorted_keys])
    raw += secret_key
    return hashlib.md5(raw.encode()).hexdigest()

更省事的做法: 用 mitmproxy 拿到完整的请求,直接用 Python 的 requests 复现。

3. 请求头补全

App 发起的请求通常有固定特征,缺了某个头就会返回 403。

python 复制代码
headers = {
    "User-Agent": "Mozilla/5.0 (Linux; Android 13; Xiaomi 13) AppleWebKit/537.36",
    "Content-Type": "application/json; charset=utf-8",
    "Accept-Encoding": "gzip",
    "x-app-version": "3.2.1",
    "x-device-id": "sn-xxxxxxxxxxxx",
    "Authorization": "Bearer xxxxxx"  # Token 一般在登录后获取
}

4. 参数加密(抓包也看不到原文)

现在的 App 很多把参数放在加密的 body 里,抓包看到的是乱码。

python 复制代码
# 逆向分析加密算法(用反编译工具)
# jadx-gui 反编译 APK,搜索 "encrypt"、"sign"、"MD5" 等关键词

# 或者用更简单的方法:hook 加密函数
# Frida 脚本 hook 加密方法,在运行时输出明文参数

五、App 爬虫全流程示例

以抓取某电商 App 商品列表为例:

python 复制代码
# 步骤一:用 mitmproxy 分析接口
# 找到商品列表接口 /api/v2/product/list
# 参数:page、page_size、category_id
# 签名算法:md5(page + page_size + category_id + secret)

# 步骤二:在 Python 中复现

import requests
import hashlib
import time

class AppSpider:
    def __init__(self, token=None):
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36",
            "x-app-version": "4.5.0",
            "Authorization": f"Bearer {token}" if token else "",
        })

    def generate_sign(self, params):
        """复现 App 的签名算法"""
        secret = "your_secret_key"  # 通过反编译 APK 得到
        sorted_keys = sorted(params.keys())
        raw = "&".join([f"{k}={params[k]}" for k in sorted_keys]) + secret
        return hashlib.md5(raw.encode()).hexdigest()

    def get_products(self, category_id, page=1):
        url = "https://api.target.com/v2/product/list"
        params = {
            "page": page,
            "page_size": 20,
            "category_id": category_id,
            "timestamp": int(time.time()),
        }
        params["sign"] = self.generate_sign(params)

        resp = self.session.get(url, params=params)
        if resp.status_code == 200:
            return resp.json()
        else:
            print(f"请求失败: {resp.status_code} {resp.text}")
            return None

    def crawl_all(self, category_id, max_pages=50):
        """翻页采集所有商品"""
        all_products = []
        for page in range(1, max_pages + 1):
            data = self.get_products(category_id, page)
            if not data:
                break
            products = data.get("data", {}).get("list", [])
            if not products:
                print(f"第 {page} 页没有数据,采集结束")
                break
            all_products.extend(products)
            print(f"第 {page} 页,已采集 {len(all_products)} 条")
            time.sleep(1)  # 不要太快
        return all_products

# 使用
spider = AppSpider(token="your_token")
results = spider.crawl_all(category_id="手机")

六、App 爬虫 vs Web 爬虫

对比项 Web 爬虫 App 爬虫
数据格式 HTML 解析麻烦 通常是 JSON,解析简单
反爬方式 IP 封禁、验证码 签名校验、SSL Pinning
接口变更频率 相对稳定 版本更新可能改动大
数据完整度 可能缺少 App 专有数据 数据更全(如推荐算法数据)
上手难度 简单,requests 就行 需要抓包和逆向

建议: 优先考虑 Web 爬虫,Web 拿不到或太难拿了再考虑 App 爬虫。

总结

App 爬虫的核心流程就四步:

复制代码
装代理证书 → 抓包分析接口 → 识破签名算法 → 用代码复现请求

遇到 SSL Pinning 就上 Frida,遇到加密就用反编译工具分析 APK。但也要注意------App 爬虫比 Web 爬虫更靠近灰色地带,采集数据时注意合规性,不要采集用户隐私信息。


💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。