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. 安装

pip install mitmproxy

启动:

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

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

2. 手机配置

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

3. 编写拦截脚本

# 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 字符

运行:

mitmweb -s capture.py

4. 自动保存数据到文件

# 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()]
mitmweb -s save_data.py

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

四、App 爬虫的常见问题

1. 证书校验(SSL Pinning)

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

解决方案:

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

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

2. 请求签名

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

# 分析签名生成逻辑
# 常见情况:把所有参数按字典序排序 + 拼接密钥 + 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。

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 里,抓包看到的是乱码。

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

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

五、App 爬虫全流程示例

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

# 步骤一:用 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/爬虫 实战干货,不让你白来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值