抓取开盘啦App(com.aiyu.kaipanla)的板块行情数据及板块-个股关联数据,输出为CSV。
板块列表通过 RealRankingInfo API 动态分页获取(不再依赖静态 kaipanla-plates-267.json 文件),数量随市场变化。
- PlateTCConfig HTTP接口只返回58个行业板块
- 完整267板块(含概念板块)通过TCP Socket + TLS + Protobuf传输
- Socket服务器:
124.71.166.244:14000,1.94.128.64:14000,60.204.212.123:14000 - IP获取接口:
getsockip.longhuvip.com/getIPList?UserID=...&Token=...(HTTP, 已验证可用) - 板块列表已通过 RealRankingInfo API 动态获取,无需静态映射文件
App → AES加密(protobuf数据) → TLS加密 → TCP → 服务器
- DEX中发现
getSocketAESKey/getHttpAESKey→ Socket通信有AES加密层 libsockSign.so负责签名计算,AuthReq.signature字段需要此库生成
libzxprotect.so加固框架,导致:- Frida
Java.perform报 "Java is not defined" — Java层hook完全不可用 - Frida Native hook(send/recv/SSL_write/BIO_write)全部0调用
- classes.dex是壳,真正代码被加密
- Frida
- frida-dexdump运行时脱壳成功:41/54个DEX已dump
- 不强制SSL pinning — mitmproxy无需Frida bypass即可解密HTTPS
- Socket连接绕过系统HTTP代理 — 不走CONNECT隧道,直连服务器
- iptables DNAT在MuMu模拟器上不生效
- 可行的Socket拦截方案: mitmproxy劫持getIPList响应,把IP改为10.0.2.2:14000
| 方案 | 结果 | 原因 |
|---|---|---|
| Frida Java.perform hook | ❌ | libzxprotect.so加固导致Java VM不可达 |
| Frida Native hook send/recv | ❌ | 加固替换libc函数指针 |
| Frida Native hook SSL_write/BIO_write | ❌ | App用内嵌BoringSSL |
| iptables DNAT重定向Socket | ❌ | MuMu模拟器QEMU用户模式网络不经过内核netfilter |
| Python直连Socket发AuthReq | ❌ | AES加密层+签名缺失,服务器关闭连接 |
| GetConceptJXBKw23 HTTP接口 | ❌ | 接口存在但返回空List |
| jadx反编译 | ❌ | classes.dex是壳无法静态分析 |
| 方案 | 结果 | 产出 |
|---|---|---|
| mitmproxy抓包HTTPS API | ✅ | captures/ 800+个JSON |
| frida-dexdump运行时脱壳 | ✅ | 41个DEX(已清理) |
| 从脱壳DEX提取Protobuf定义 | ✅ | proto/*.proto |
| getIPList劫持+TCP代理拦截Socket | ✅ | tcp_proxy_raw.py收到TLS ClientHello |
| HTTP API动态获取板块数据 | ✅ | data/ (板块+个股, CSV格式) |
| 域名 | 用途 |
|---|---|
| apphwshhq.longhuvip.com | 行情数据(板块/指数/盘口) |
| applhb.longhuvip.com | 题材/概念/龙虎榜 |
| apphis.longhuvip.com | 历史数据(个股列表) |
| apparticle.longhuvip.com | 文章/资讯 |
| applog.longhuvip.com | 日志 |
| getsockip.longhuvip.com | Socket服务器IP列表 |
PhoneOSNew=1&DeviceID={did}&VerSion=5.23.0.4&apiv=w44&UserID={uid}&Token={token}
⚠️ 必须使用Dalvik User-Agent! 服务器校验UA,非Dalvik UA返回空数据User-Agent: Dalvik/2.1.0 (Linux; U; Android 12; ALN-AL00 Build/W528JS) Connection: Keep-Alive- 认证信息从抓包获取,Token可能过期需重新抓包
- 非交易时间需传
Date=交易日期(如2026-05-08)
- 参数: PlateID=801159, Date=2026-05-08
- 概念板块(267个)和行业板块(58个)字段映射不同!
概念板块格式(267个,PlateID以8010-8018开头):
List[0] = 涨跌家数差/强度排名 (如1)
List[1] = 强度 (如16304)
List[2] = 成交额(元) (如812032290529 → 8120亿)
List[3] = 主力净额(元) (如8437255508 → 84亿)
List[4] = 未知 (如1.98) ⚠️ 含义待确认
List[5] = 涨停数 (如24)
List[6] = 涨停封单(元) (如2123483126 → 21.23亿)
List[7] = 大单封单(元) (如1347672506 → 13.48亿)
涨跌幅不在GetPlate_Info_QJ中! 需从 Index/GetInfo → BaceFaceList 获取:
BaceFaceList = [[板块名称, 涨幅%, PlateID], ...]
例: ["机器人概念", "1.67", 801159]
行业板块格式(58个,PlateID以8019/803/880开头):
List[0] = 强度 (如239, 或'--'表示无数据)
List[1] = 涨跌幅*100 (如-38 → -0.38%)
List[2] = 成交额(元)
List[3] = 主力净额(元)
List[4] = 量比 (如0.16)
List[5-7] = 通常为0
- 参数: StockID=801159, Date=2026-05-08
⚠️ 控制器是 ZhiShuL2Data 不是 ZhiShuRanking!之前用错控制器导致返回空- 返回盘口数据(12字段数组):
pankou[0] = 成交额(元)
pankou[1] = 换手率(%) (如3.994)
pankou[2] = 未知(220.505)
pankou[3] = 未知(615.74亿)
pankou[4] = 未知(-531.37亿)
pankou[5] = 主力净额(元)
pankou[6] = 上涨家数 (如872)
pankou[7] = 下跌家数 (如295)
pankou[8] = 未知(18)
pankou[9] = 未知(203298.83亿)
pankou[10] = 未知(258127.24亿)
pankou[11] = 强度 (16304.42)
- 参数: StockID=801159
⚠️ 控制器是 ZhiShuL2Data 不是 ZhiShuRanking!- 返回当日爆发原因
- 参数: PlateID=801159
⚠️ 控制器是 ConceptionPoint 不是 ZhiShuRanking!- 返回分时直播事件(涨停股等)
- 参数: PlateID=801159
- 返回子板块列表: [[代码, 名称, 强度], ...]
- 参数: PlateID=801821, Date=2026-05-08, Type=0~19
⚠️ 域名必须是 apphis.longhuvip.com,不能用apphwshhq!⚠️ 响应key是小写list不是大写List! 之前用大写取一直显示0⚠️ Type参数是排序标签,每种Type返回9只个股(Top9),需遍历Type 0~19合并去重才是完整列表!- Type对应GetGPCPHBTS_Tag的排序标签(机构增仓TSZB=20、低PE=7、高股息=8等)
- 个股字段(63个) — 2026-05-10v4 用户确认:
[0]=代码, [1]=名称, [4]=所属板块标签,
[5]=价格, [6]=涨幅%, [7]=成交额(元),
[8]=实际换手率%, [9]=涨速, [10]=实际流通(元),
[11]=主力买(元), [12]=主力卖(元), [13]=主力净额(元),
[18]=卖流占比, [19]=净流占比, [20]=区间涨幅,
[21]=量比, [23]=几天几板, [25]=换手率%,
[28]=收盘封单(元), [29]=最大封单(元),
[33]=振幅, [37]=总市值(元), [38]=流通市值(元),
[40]=领涨次数, [42]=机构增仓Q1(元), [50]=300万以上大单净额(元),
[53]=市净率,
[58]=人气值, [59]=人气排名变化,
[60]=市盈率动, [61]=市盈率TTM, [62]=PE静
- 字段名来源: PaiHangBangOption/GetUserOptionB的Bind数组含字段名→arrayPosition映射
- 参数: PlateID=801821
- 返回标签配置(最正宗、机构增仓等排序选项)
- 参数: PlateID=801159
- 返回爆发原因历史列表
- 参数: ZSCode=801159
- 返回子概念列表
- 返回首页聚合数据,含BaceFaceList活跃板块(涨跌幅来源)
- 返回Theme.List热门板块
service HQ {
rpc SubThemeList(Empty) returns (themeListResp);
rpc SubPlateTypeQuotasList(PlateTypeQuotasListReq) returns (PlateTypeQuotasListResp);
rpc SubAuth(AuthReq) returns (AuthResp);
}deviceId(1,string), platformId(2,int), versionName(3,string),
channelId(4,int), signature(5,string), userId(6,string),
token(7,string), connType(8,int), curTime(9,int)
id(1), name(2), desc(3), pinyin(4), isHot(5),
ztNum(6), ratio(7), sortType(8), conceptType(9),
codeSwitch(10), state(11)
plateId(1), plateName(2), strength(3), incRate(4),
tur(5), mainNetAmount(6), volRatio(7), institutionIncrease(8),
circularCaptital(9), yearPE(10), nextYearPE(11)
- volRatio=量比, institutionIncrease=机构增仓 — 这两个字段在HTTP API中找不到,只在Socket推送中
proto/kpl.proto— 合并后的完整protobuf定义proto/kpl_pb2.py— 编译后的Python模块(需protobuf v6运行时)
| 路径 | 内容 |
|---|---|
| data/main_plate_info_{date}.csv | 主板块汇总(强度/成交额/主力/涨停封单/大单封单/换手率/上涨下跌/爆发原因,merge 4 组数据) |
| data/sub_plate_info_{date}.csv | 子板块关联(父代码/子代码/子板块名/子板块强度) |
| data/stock_info_{date}.csv | 个股详情(代码/名称/价格/涨幅/成交额/主力/换手率/量比/封单/市值/PE 等 63 字段) |
| captures/ | mitmproxy 抓包原始数据(用于 token 刷新) |
- ADB路径:
/Applications/MuMuPlayer.app/Contents/MacOS/MuMuEmulator.app/Contents/MacOS/tools/adb - ADB端口: 不稳定,每次重启后需扫描
- 网关: 10.0.2.2 = Mac主机
- 默认root
- 极不稳定 — 频繁操作后容易崩溃,ADB断连
- 版本: 17.9.1
- Server路径: /data/local/tmp/frida-server
- 必须用attach模式(-p PID),spawn模式(-f)会导致libzxprotect.so crash
- 必须adb forward tcp:27042
- Java.perform不可用 / Native hook不可用
- CA证书:
~/.mitmproxy/mitmproxy-ca.pem - 抓包脚本:
mitm_capture.py(项目根目录下) - 启动命令:
mitmdump -s mitm_capture.py --listen-port 8080 ⚠️ protobuf版本冲突: 修复:pip install protobuf==4.25.9 typing-extensions==4.4.0
- 依赖: requests, pandas, akshare, mitmproxy
- 运行: 直接
python3 crawler_batch.py或在 Jupyter/Notebook 中 import 调用
| 脚本 | 用途 | 状态 |
|---|---|---|
| crawler_batch.py | 批量爬虫(并行):token校验/刷新 + 交易日获取 + 并行抓取板块+个股数据,输出CSV | ✅ 推荐 |
| crawler_raw.py | 原版串行爬虫:逐板块串行抓取,硬编码日期和Token,输出CSV | ✅ 可用(调试参考) |
一键启动/停止(推荐):
| 脚本 | 用途 | 状态 |
|---|---|---|
start_capture.sh |
一键启动抓包:自动配置代理 (10.0.2.2:8080) + 启动 mitmproxy | ✅ |
stop_capture.sh |
一键停止抓包:清理进程 + 关闭代理 | ✅ |
使用方法:
# 启动抓包
cd /Users/yixin/agent_project/开盘啦_数据解析
./start_capture.sh
# 在 MuMu 模拟器中打开 开盘啦 App 并操作
# 停止抓包
./stop_capture.sh- 代理地址必须是
10.0.2.2:8080(MuMu 网关 = Mac 主机) - 不能用
127.0.0.1:8080(模拟器内 127.0.0.1 指向自己,会导致网络受限)
| 脚本 | 用途 | 状态 |
|---|---|---|
| mitm_capture.py | 统一版抓包脚本:过滤 longhuvip 域名,保存到 captures/,可选 SOCKET_HIJACK 劫持 getIPList,用于 crawler_batch.py 的 token 自动刷新 | ✅ 推荐 |
在 .env 文件中配置认证参数(已自动生成):
KPL_USER_ID=7079210
KPL_TOKEN=你的 token 值
KPL_DEVICE_ID=你的设备 ID
# 抓取指定日期范围(推荐)
python3 crawler_batch.py --start 2026-05-01 --end 2026-05-15
# 只抓最近一个交易日
python3 crawler_batch.py
# 自定义并发线程数和请求间隔
python3 crawler_batch.py --start 2026-05-01 --end 2026-05-15 --workers 5 --delay 0.1或在 Python 中直接调用:
from crawler_batch import main
main(start_date="2026-05-01", end_date="2026-05-15", delay=0.05, workers=10)crawler_batch.py 按天循环:
├── 加载 .env 中的认证参数
├── 验证 token → 过期则自动启动 mitmproxy 抓包刷新
├── 获取交易日列表(akshare)
├── 对每个交易日:
│ ├── 阶段1:串行分页 RealRankingInfo → 动态获取板块列表
│ ├── 阶段2:并行抓取板块数据 (PlateInfo_QJ + PanKou + BoomReason + SonPlate)
│ ├── 阶段3:并行抓取成分股 (ZhiShuStockList_W8)
│ └── 阶段4:构建 DataFrame → 输出 3 个 CSV
│ ├── data/main_plate_info_{date}.csv (主板块汇总)
│ ├── data/sub_plate_info_{date}.csv (子板块关联)
│ └── data/stock_info_{date}.csv (个股详情)
脚本自动检测 token 是否过期。过期时:
- 自动启动 mitm_capture.py(mitmproxy 抓包)
- 轮询 captures/ 目录等待新 Token
- 从捕获数据中提取新 UserID 和 Token
- 自动更新 .env 文件
- 继续执行抓取
如何获取新 token(手动方式):
- 在 MuMu 模拟器中打开 App 并登录
- 运行
./start_capture.sh启动抓包 - 从抓包数据中找到新的 Token 值
- 更新
.env文件 - 重新运行
python3 crawler_batch.py
脚本会自动检查 data/main_plate_info_{date}.csv 是否存在:
- 已存在 → 自动跳过该日期
- 目前不支持
--force覆盖,需手动删除已有 CSV 后重新运行
控制台实时输出抓取进度,包括:
- 板块数量、token 状态、每个板块的抓取结果
- 并发线程执行情况
- 失败重试记录
- Dalvik UA 必须 — 不用则 API 返回 errcode=0 但 List=[]
- 概念板块 vs 行业板块字段映射不同 — GetPlate_Info_QJ 两种板块格式不同
- List[4] 不是涨跌幅 — 涨跌幅在 Index/GetInfo 的 BaceFaceList 中
- List[6] 是涨停封单不是主力流入 — List[7] 是大单封单不是主力流出
- GetPanKou 控制器是 ZhiShuL2Data 不是 ZhiShuRanking — 参数用 StockID 不是 PlateID
- BKFenShiZhiBo 控制器是 ConceptionPoint — 不是 ZhiShuRanking
- ZhiShuStockList_W8 域名是 apphis 不是 apphwshhq — 用错域名返回 0
- ZhiShuStockList_W8 响应 key 是小写 list 不是 List — 用大写取一直显示 0
- mitmproxy 需放行非 longhuvip 域名 — 否则 DNS 失败返回 502 阻断 App 网络
- 非交易时间 Index/GetInfo 的 BaceFaceList 返回空 — 涨跌幅列暂为空
- ZhiShuStockList_W8 只返回 9 只 — 需遍历 Type 0~19 合并去重才是完整个股列表
- [5] 是价格不是涨幅 — 之前误以为是涨幅导致数据错乱
- [11] 是主力买不是流通市值 — 之前映射错误
- [28]=收盘封单 [29]=最大封单 [50]=300 万以上大单净额 — 从 PaiHangBangOption/GetUserOptionB 的 Bind 数组确认
- 子板块个股数据 App 走 Socket 不走 HTTP — App 进入子板块时不请求个股列表接口,数据通过 Socket 推送
⚠️ MuMu 代理必须用 10.0.2.2:8080 — 127.0.0.1 在模拟器内指向自己,会导致 App 网络受限
开盘啦_数据解析/
├── README.md # 本文件(项目文档)
├── crawler_batch.py # 批量爬虫(并行,token自动刷新,推荐使用)
├── crawler_raw.py # 原版串行爬虫(硬编码日期和Token,调试参考)
├── mitm_capture.py # mitmproxy 抓包脚本(域名过滤 + 可选 SOCKET_HIJACK)
├── aes.min.js # AES 加密 JS(App 加密逻辑参考)
├── start_capture.sh # 一键启动抓包(自动配置代理 + 启动 mitmproxy)
├── stop_capture.sh # 一键停止抓包(清理进程 + 关闭代理)
├── .env # 认证配置(KPL_TOKEN/KPL_USER_ID/KPL_DEVICE_ID)
├── data/ # 输出数据
│ ├── main_plate_info_{date}.csv # 主板块汇总
│ ├── sub_plate_info_{date}.csv # 子板块关联
│ └── stock_info_{date}.csv # 个股详情
├── captures/ # mitmproxy 原始抓包(用于 token 刷新)
└── .gitignore # 排除 .env / data / captures / __pycache__