目标页面:https://quote.eastmoney.com/concept/sz300624.html?from=classic 抓取目标:页面右侧"筹码分布"图所表达的价格-持仓量数据 完成日期:2026-05-22
页面右侧的"筹码分布"是一个 canvas 渲染的柱状图。看到 canvas,最容易掉进两个坑:
chip / cyq 关键字 → 抓不到任何东西就以为是接口被加密了。正确认知:canvas 只是渲染层。绝大多数 canvas 图表的数据来自后端 JSON,但也存在一种情况:前端用更基础的数据(如 K 线)在本地算出图表所需的衍生数据。这次的筹码分布就属于后者。
遇到任何不知来源的页面数据,按这个顺序定位:
1. 监听完整请求生命周期 → 是 XHR / Fetch / Script(JSONP) / WebSocket / EventSource?
2. 找到触发点 → 哪个用户操作引起哪条请求?
3. 都找不到匹配请求?→ 数据多半是前端本地算的,去 JS 源码里找算法
本次实战路径:
| 阶段 | 操作 | 结论 |
|---|---|---|
| ① 用户初判 | 在 Network 面板按"筹码分布"按钮后搜 chip |
搜不到 → 误以为是普通 K 线接口 kline/get |
| ② 工具复核 | 用 MCP js-reverse 打开页面,程序化点击"日K" + "筹码分布"按钮,对比点击前后的所有请求 |
发现点击筹码分布后唯一新增的 script 请求仍是同一个 K 线接口 |
| ③ 推论 | 既然没有专门接口,必是前端算的 | 去前端代码里找算法 |
| ④ 定位算法 | 在 quotechart2022.js 里搜 cyq("筹码"拼音首字母) |
找到 ./src/modules/tools/indicator/cyq.ts 里的 CYQCalculator 类 |
关键命名线索:东方财富前端用拼音首字母给模块命名——筹码分布 = ChouMaYun →
cyq、除权除息 = ChuQuanChuXi →cqcx。遇到中文产品名的接口/类,先试拼音首字母。
点击"筹码分布"按钮前后,所有 XHR/Fetch/Script 请求里没有任何新的数据接口,只新增了:
Web_Event.gif(用户行为统计)push2his.eastmoney.com/api/qt/stock/kline/get(点击触发了图表重绘)→ 没有专属接口 = 数据是前端算的。
<div class="quotechart2022_c_cyq">
<canvas width="270" height="577"></canvas>
</div>
cyq 容器的存在直接坐实了模块归属。
quotechart2022.js 包含两个 cyq 相关 webpack 模块:
./src/modules/kline/cyq.ts(drawCYQ —— 画图)./src/modules/tools/indicator/cyq.ts(CYQCalculator —— 算法)源码大小适中(约 2KB),适合人肉逆向。
输入:
klinedata : [{open, close, high, low, hsl}, ...] K 线 + 换手率
fator : 150 价格档位数 (默认)
range : None | int 滑窗根数 (None=全部)
e : int 要算到第几根 K 线 (索引)
输出:
x : [w0, w1, ..., w149] 各价位的筹码权重
y : [p0, p1, ..., p149] 对应的价格档
benefitPart : float 获利比例
avgCost : "xx.xx" 平均成本 (50% 分位价)
percentChips : { 90: {...}, 70: {...} } 成本集中区间
筹码分布建模思路:
A = hsl/100,意味着旧筹码被替换 A 比例。所以处理每根 K 线时:
chips[*] *= (1 - A)A 单位的筹码摊进 [low, high] 区间。这个模型的合理性:老股东持仓被换手稀释,新成交价格在均价附近最密集——符合直觉。
对当日 K 线(low=L, high=H, mid=M=(O+C+H+L)/4),在价格 x 处的权重:
峰值 y₀ = 2 / (H - L) (面积归一化为 1)
左半区: x ∈ [L, M] w(x) = (x - L) / (M - L) · y₀
右半区: x ∈ (M, H] w(x) = (H - x) / (H - M) · y₀
其他: w(x) = 0
最终再乘以本日换手比例 A,叠加到对应价格档位。
benefitPart:当前 close 之下的累计筹码占比(即"在当前价位之下买入的人都赚了")。avgCost:累计筹码到 50% 时对应的价格(中位数成本)。percentChips[N]:累计筹码从 (1-N)/2 到 (1+N)/2 区间对应的价格段——例如 90% 意味着丢掉最高 5% 和最低 5% 的极端档位。concentration = (high - low) / (high + low):区间相对宽度,越小代表筹码越集中。url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
params = dict(
secid = "0.300624", # 0.=深市, 1.=沪市
ut = "fa5fd1943c7b386f172d6893dbfba10b", # 通用 token,无需动态获取
fields1 = "f1,f2,f3,f4,f5,f6",
fields2 = "f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61",
klt = 101, # 101=日K, 102=周K, 103=月K, 5/15/30/60=分钟K
fqt = 1, # 0=不复权, 1=前复权, 2=后复权 (网页默认前复权)
end = "20990101",
lmt = 210, # 网页用 210 根
cb = "cb", # JSONP 回调名占位
)
| 字段 | 含义 | 备注 |
|---|---|---|
| f51 | 日期 | YYYY-MM-DD 字符串 |
| f52 | 开盘 | |
| f53 | 收盘 | |
| f54 | 最高 | |
| f55 | 最低 | |
| f56 | 成交量 | 单位:手 |
| f57 | 成交额 | |
| f58 | 振幅 | % |
| f59 | 涨跌幅 | % |
| f60 | 涨跌额 | |
| f61 | 换手率 | 筹码算法必需 % |
数据格式:data.klines 是字符串数组,每行用 , 拼接,按 fields2 顺序对应。
import re, json, requests
r = requests.get(url, params=params, timeout=10)
data = json.loads(re.search(r"\((.*)\);?$", r.text.strip()).group(1))["data"]
a/b||0 这种 JS 习惯写法 —— Python 翻译时要加空值兜底:(d["hsl"] or 0) / 100。toPrecision(12)/1 —— JS 用来截断浮点误差,Python 用普通浮点累加误差也小到可忽略,无需特意处理。.toFixed(2) 返回字符串 —— Python 用 f"{x:.2f}" 对齐,别用 round()(类型不一致会害下游对比)。Math.floor / Math.ceil —— 直接 math.floor / math.ceil。if abs(m - low) < 1e-8 这种是避免除零,必须保留。这次实战用到的关键能力:
| 工具 | 用途 |
|---|---|
new_page / select_page |
控浏览器开页面 |
evaluate_script |
程序化点击、读 DOM、查 window 全局变量 (注意 mainWorld:true 才能拿到页面真实 JS 上下文) |
list_network_requests |
关键。可按 resourceTypes 过滤、按 urlFilter 子串过滤 |
search_in_sources |
全量 JS 源码搜关键字。逆向利器 —— 用拼音首字母搜中文功能模块 |
get_script_source |
取指定行/字节范围的源码(minified 文件用 offset/length) |
1. new_page → 打开目标
2. evaluate_script (DOM/click) → 触发目标功能
3. list_network_requests → 找触发后新增的请求
├─ 找到了 → 复刻接口参数即可
└─ 找不到 → 进入第 4 步
4. search_in_sources → 用功能关键字 (拼音首字母 / 英文 / 业务名) 搜源码
5. get_script_source → 读出算法
6. 把算法移植到 Python / 其他语言
script 类型而不是 xhr,按 resourceType 过滤时别漏。ut 参数 fa5fd1943c7b386f172d6893dbfba10b 是东方财富一个长期通用的访问 token,可以直接硬编码。eastmoney_spider.py —— 抓取脚本(含完整算法移植)筹码分布逆向分析.md —— 本文档| 指标 | 截图(2026-05-21 盘中) | 本脚本(2026-05-22 收盘后) |
|---|---|---|
| 平均成本 | 69.05 | 68.64 |
| 获利比例 | 0.18% | 0.73% |
| 价格区间 | 61.16 ~ 125.17 | 与之极接近 |
| 主峰位置 | 70 元附近 | 67~68 元(权重最高 15 档全在此区间) |
差异在合理范围(差一日 + 盘中/收盘价差),算法移植正确。