很多数据只在 App 上有,网页版要么没有要么做了反爬。这一篇讲怎么抓 App 的包、怎么解析接口、怎么用 Python 自动化采集。
一、抓包原理
App 和服务器通信走 HTTP/HTTPS 协议,抓包就是在中间截获请求和响应。
App → 抓包工具 → 服务器
↓
记录请求数据(URL、参数、请求头、响应)
两种抓包方式:
| 方式 | 工具 | 适用场景 | 难度 |
|---|---|---|---|
| 代理抓包 | Fiddler、Charles | 简单,新手首选 | ⭐ |
| 中间人代理 | mitmproxy | 可编程、可自动化 | ⭐⭐ |
二、Fiddler 抓包------快速上手
1. 设置 Fiddler
- 打开 Fiddler → Tools → Options → HTTPS
- 勾选 Capture HTTPS CONNECTs
- 勾选 Decrypt HTTPS traffic
- 弹出证书安装提示,点 Yes
2. 手机连接代理
- 手机和电脑连同一个 Wi-Fi
- 手机 Wi-Fi 设置 → 代理 → 手动
- 填入电脑的 IP 和 Fiddler 端口(默认 8888)
- 手机浏览器访问
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/爬虫 实战干货,不让你白来。