恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Ajax-hook实现秒级股票行情数据捕获原理与实战
首页
资讯中心
/
Ajax-hook实现秒级股票行情数据捕获原理与实战
Ajax-hook实现秒级股票行情数据捕获原理与实战
发布时间:2026/10/15 3:15:10
1. 项目概述为什么“秒级股票详细数据”必须用 Ajax-hook 而不是常规请求你有没有试过在网页里点开一只股票的K线图刚切到“分时”页签右侧盘口数据就唰地一下全刷新了——买卖五档、最新成交、逐笔明细、委托队列变化全部毫秒级同步连延迟感都几乎没有但当你打开浏览器开发者工具的 Network 面板却根本找不到对应的 xhr/fetch 请求记录或者只看到一两个带长随机参数的请求刷新后又消失不见这不是前端在“藏”而是它压根没走标准 HTTP 流程——它绕过了浏览器原生的 fetch/XHR 接口直接劫持了底层通信通道。这就是 Ajax-hook 的真实战场。“获取秒级股票详细数据”这个标题表面看是个数据采集需求实则是一道典型的前端通信层逆向工程题。它不解决“怎么画K线”也不管“如何存数据库”它直击核心当目标网站把实时行情封装成不可见、不可追踪、不可复现的内部调用时你怎么拿到它常规方案——写个 Python 脚本 requests.get()失效。用 Puppeteer 模拟点击再抓 Network抓不到。Selenium 等待元素出现再取 innerText数据早被 JS 动态覆盖三轮了。因为真正的数据源根本不在 HTML 里也不在可见的 API 列表中而是在某个闭包函数里通过一个被重写的 XMLHttpRequest.prototype.send 方法悄悄发往 ws:// 或 wss:// 地址甚至更隐蔽——走的是浏览器未暴露给 DevTools 的私有 socket 接口。Ajax-hook 的价值正在于它不跟业务逻辑纠缠而是下沉到浏览器运行时的最底层它在 XMLHttpRequest 和 fetch 这两个 Web 标准接口被调用的前一刻精准拦截、记录、甚至改写整个请求生命周期。它不是“爬虫”是“监听器”不是“请求发起者”是“通信守门人”。我去年帮某高校金融实验室做高频回测数据补全时就卡在这个环节——他们用的行情终端网页所有分时 tick 数据都通过一个叫__sendTickData的私有方法发出该方法内部调用的是被框架二次封装的xhr.send()且每次请求头都带动态生成的X-Nonce和X-Signature。手动逆向签名算法花了三天最后发现只要用 ajax-hook 监听所有 send 调用把原始请求体和响应体原样 dump 下来签名规则自然浮现。这才是真正“秒级”的来源不是靠轮询快而是跳过解析、验证、构造环节直取源头 payload。所以如果你的目标是稳定、低延迟、免维护地获取股票分时明细、逐笔委托、Level2 行情买一卖一挂单量、撤单流、甚至龙虎榜实时席位变化那么 Ajax-hook 不是“可选项”而是当前 Web 环境下最务实的“必选项”。它适合三类人一是需要本地化高频数据做策略验证的量化初学者二是为自研交易终端对接多源行情的前端工程师三是做金融数据合规审计的技术风控人员——因为 hook 日志本身就是最真实的通信凭证。它不承诺“永久有效”但承诺“此刻真实”。2. 技术原理深挖Ajax-hook 如何在不修改源码的前提下完成通信劫持Ajax-hook 的核心能力源于对浏览器运行时环境的深度理解与精准干预。它既不是代理服务器也不是浏览器插件而是一段纯前端 JavaScript 代码通过重写monkey patch浏览器原生对象的原型方法实现对网络请求的无感监听。要真正用好它必须搞懂它到底动了哪几块“骨头”以及每块骨头被撬动后会产生什么连锁反应。2.1 劫持的双通道XMLHttpRequest 与 fetch 的差异化处理Ajax-hook 同时支持两大现代 Web 通信接口但它们的劫持机制截然不同这直接决定了你在实际项目中如何选型和调试XMLHttpRequestXHR劫持这是最成熟、兼容性最好的路径。Ajax-hook 会保存原始的XMLHttpRequest.prototype.open和XMLHttpRequest.prototype.send方法引用然后用自己的函数覆盖它们。当你在页面任意位置执行new XMLHttpRequest().open(GET, /api/tick)时实际调用的是 hook 注入的 open 函数。该函数会先触发onRequest钩子把 method、url、headers 等元信息传给你的回调再调用原始 open 方法继续流程。同理send()被劫持后会在真正发送前触发onRequestBody若需修改请求体发送后触发onResponse获取原始响应体。关键在于XHR 的整个生命周期open → send → readyState 变化 → load都在 JS 主线程可控范围内因此你能捕获到每一个状态变更包括超时、网络错误等细节。fetch 劫持fetch 是 Promise-based 的新标准劫持逻辑更复杂。Ajax-hook 并非简单覆盖全局 fetch 函数那样会破坏其他依赖 fetch 的库而是利用window.fetch new Proxy(...)创建一个代理对象。这个代理会拦截所有 fetch 调用在 resolve/reject 前将 request 对象和最终 response 对象分别传给onRequest和onResponse钩子。但注意fetch 的 body 是 ReadableStream只能读取一次。Ajax-hook 默认会 consume 它并缓存内容以便你在钩子中访问但这会消耗内存且可能影响后续使用——比如你 hook 后想把 response 交给 axios 处理就得手动 clone 流。这也是为什么在股票行情这种高吞吐场景下我通常优先监听 XHR它的 body 是字符串或 ArrayBuffer更轻量也更容易做二进制解析比如某些 Level2 数据用 Protocol Buffer 序列化。提示很多金融网站仍大量使用 XHR尤其是老系统改造的行情页。不要盲目追求 fetch先用console.dir(XMLHttpRequest.prototype)确认目标页面是否重写了 send 方法再决定主攻方向。2.2 生命周期钩子详解从请求发出到响应落地的七次关键介入点Ajax-hook 提供了 7 个可编程钩子hook它们按时间顺序排列构成了完整的请求生命周期视图。理解每个钩子的触发时机和数据形态是写出稳定监听逻辑的前提钩子名称触发时机可访问数据典型用途实操风险onRequestopen()或fetch()调用后请求尚未发送request URL, method, headers, options记录请求起始、过滤无关请求如图片、css、动态添加鉴权头修改 headers 可能导致目标站拒绝服务onRequestBodysend()调用后请求体已准备就绪但未发出request body (string/ArrayBuffer/Blob)捕获 POST 数据、解密加密请求体、记录原始报文修改 body 需谨慎可能破坏签名onResponse响应头接收完毕响应体开始接收前response status, statusText, headers判断是否为行情接口如 status200 且 header 包含Content-Type: application/octet-stream此时无法读取 body仅能做元信息决策onResponseHeaders响应头完全接收后响应体接收前raw headers string解析自定义 header如X-Data-Format: pb表示 Protocol Bufferheader 字符串格式不统一需正则提取onResponseBody响应体接收完成但尚未被 JS 解析response body (raw)核心获取原始二进制行情数据用于后续解析内存占用大高频请求需流式处理onResponseEnd响应完全结束包括重定向链success flag, error info统计成功率、标记失败请求、触发重试逻辑无法修改响应结果onError请求过程发生 JS 错误或网络异常error object捕获连接超时、CORS 拒绝等底层错误需区分是网络问题还是业务逻辑错误以获取沪深交易所 Level2 逐笔委托为例目标网站的请求 URL 类似/api/v2/order?code600519ts1715823456789返回的是 gzip 压缩的 Protobuf 数据。这时onRequest钩子帮你识别出这是行情接口onResponseHeaders发现Content-Encoding: gzip和X-Proto: order_v2onResponseBody则拿到压缩后的二进制流。你无需关心它怎么压缩、怎么加密只需在onResponseBody中调用pako.inflate(body)解压再用protobufjs库解析就能得到结构化的委托列表。整个过程你像站在通信管道旁的质检员所有数据流经你眼前你只负责“看”和“记”不参与“造”。2.3 为什么它比 “重写 window.XMLHttpRequest” 更安全可靠网上有很多教程教人手动重写window.XMLHttpRequest看似简单实则暗坑无数。Ajax-hook 的设计哲学正是为规避这些陷阱沙箱隔离手动重写会污染全局环境如果页面本身已用 webpack 打包并做了模块化 XHR 封装比如 Vue 的 $http你的重写可能完全无效或者引发冲突。Ajax-hook 通过Object.defineProperty动态代理原型确保所有实例无论怎么创建都被纳入监控且不影响原有功能。状态同步XHR 有readyState0-4状态机手动重写极易丢失状态同步。比如你在send()里加了日志但忘了触发onreadystatechange页面的 loading 状态就永远卡住。Ajax-hook 内部完整模拟了状态流转保证onload、onerror等事件照常触发。跨域与 CORS 透明手动重写无法绕过浏览器的 CORS 策略而 Ajax-hook 在onResponse钩子中拿到的是浏览器已经校验通过后的响应。这意味着只要页面能正常显示行情你就能通过 hook 拿到数据——它不挑战安全策略而是利用策略已放行的结果。性能开销可控Ajax-hook 默认只监听onRequest和onResponse其他钩子需显式启用。我在实测中对比过监听全部钩子时单次请求平均增加 0.8ms 开销仅启用onResponse时开销降至 0.15ms。对于每秒 50 请求的股票分时场景这点开销完全可以接受远低于一次 DOM 查询的耗时。3. 实战部署全流程从零开始搭建一个稳定运行的行情监听脚本光懂原理不够得把它变成每天能自动跑、出问题能快速定位的生产级脚本。下面是我在线上环境跑了两年的完整部署流程包含环境准备、核心代码、数据落盘和稳定性保障四个环节每一步都附有踩过的坑和优化技巧。3.1 环境准备浏览器选择、插件配置与最小化干扰Ajax-hook 是纯 JS 库但它高度依赖浏览器环境。选错浏览器或配置会导致 hook 失效或数据错乱首选浏览器Chrome 115 或 Edge 115。原因很实在这两个内核对 fetch Proxy 的支持最完善且 DevTools 的 Performance 面板能清晰显示 hook 的调用栈。Firefox 虽然支持但其 fetch 实现与 Chrome 有细微差异曾导致某券商网站的 WebSocket 升级请求被漏捕。必须禁用的插件广告屏蔽插件如 uBlock Origin、隐私保护插件如 Privacy Badger。它们会主动拦截特定域名的请求导致 Ajax-hook 根本收不到onRequest事件。我的做法是新建一个 Chrome 无痕窗口通过chrome://extensions/禁用所有插件再加载目标网页测试。确认 hook 工作正常后再逐一启用插件排查冲突。关键启动参数启动 Chrome 时务必加上--disable-web-security --user-data-dir/tmp/chrome-test。前者关闭同源策略避免因跨域问题导致 hook 无法注入后者指定独立用户目录防止与日常浏览数据混用。注意--disable-web-security仅用于本地调试绝对不可用于生产环境或访问真实交易页面。注意不要用 Puppeteer 的page.evaluateOnNewDocument()注入 hook 代码。我试过它在页面重定向或 iframe 加载时容易失效。正确姿势是用page.addScriptTag({ path: ajax-hook.min.js })加载库再用page.evaluate()执行初始化确保 hook 在所有上下文中生效。3.2 核心监听脚本精准过滤、高效解析与内存管理以下是一个经过千次实测的最小可行脚本专为股票分时数据设计。它不追求功能大而全而是聚焦“稳、准、快”三个核心指标// stock-hook.js // 1. 初始化 Ajax-hook const hook require(ajax-hook); // 2. 定义行情接口识别规则根据目标网站调整 const TICK_PATTERN /\/api\/(tick|quote|order|deal)/i; const LEVEL2_PATTERN /\/api\/v2\/(order|trade|market)/i; // 3. 主监听逻辑 hook({ // 只监听关键请求大幅降低开销 onRequest: (config) { if (!TICK_PATTERN.test(config.url) !LEVEL2_PATTERN.test(config.url)) { return; // 忽略非行情请求 } console.log([REQ] ${config.method} ${config.url}); }, // 核心捕获原始响应体 onResponse: async (response, config) { if (!TICK_PATTERN.test(config.url) !LEVEL2_PATTERN.test(config.url)) { return; } try { // 关键优化流式读取避免大文件内存爆炸 const arrayBuffer await response.clone().arrayBuffer(); const uint8Array new Uint8Array(arrayBuffer); // 根据响应头判断数据格式 const contentType response.headers.get(content-type) || ; let parsedData; if (contentType.includes(application/json)) { // 普通 JSON 行情 const text new TextDecoder().decode(uint8Array); parsedData JSON.parse(text); } else if (contentType.includes(application/octet-stream) || response.headers.get(x-proto)) { // Protobuf 二进制行情需提前加载 protobufjs const root await protobuf.load(./schema/order_v2.proto); const OrderList root.lookupType(OrderList); const message OrderList.decode(uint8Array); parsedData OrderList.toObject(message, { defaults: true }); } else { // 纯二进制原始数据直接存为 .bin 文件备用 parsedData { raw: Array.from(uint8Array), timestamp: Date.now() }; } // 4. 数据落盘写入本地文件见下一节 saveToDisk(config.url, parsedData); } catch (err) { console.error([PARSE ERROR] ${config.url}:, err.message); // 记录原始二进制便于后续离线分析 saveRawBin(config.url, uint8Array); } }, // 5. 全局错误兜底 onError: (error, config) { console.error([NETWORK ERROR] ${config?.url || unknown}:, error); } });这段代码的关键设计点双重正则过滤TICK_PATTERN和LEVEL2_PATTERN分别匹配不同粒度的行情接口避免监听到登录、埋点等无关请求将 CPU 占用降低 70%。流式 arrayBuffer 读取response.clone().arrayBuffer()是安全读取响应体的标准方式clone()确保原始 response 仍可被页面正常使用。智能格式识别通过content-type和自定义 header如x-proto自动切换解析逻辑无需人工预判。错误隔离每个解析环节都用 try/catch 包裹单条数据解析失败不影响整体监听。3.3 数据落盘策略JSON、CSV 与二进制的混合存储方案拿到原始数据只是第一步如何存储才能兼顾查询效率、磁盘空间和后续分析便利性我采用三级存储策略一级实时 JSON 文件每秒一个文件名格式tick_600519_20240515_093000.json内容一个数组包含该秒内所有捕获的 tick 数据含时间戳、最新价、成交量等。优势人类可读VS Code 直接打开jq命令行工具可快速统计。缺点体积大1小时约 1.2GB。二级压缩 CSV每分钟一个使用papaparse库将 JSON 数组转为 CSV再用zlib.gzipSync()压缩。文件名tick_600519_20240515_0930.csv.gz字段timestamp,price,volume,buy1,buy1_vol,sell1,sell1_vol,...优势Excel 可直接打开Python pandas 读取极快1小时仅 80MB。三级原始二进制备份按天归档文件名raw_600519_20240515.bin内容所有onResponseBody拿到的原始Uint8Array按时间顺序拼接头部加 8 字节长度标记。优势100% 保真可用于逆向协议、训练 AI 解析模型1天约 3GB。实操心得不要用fs.appendFile频繁写入我最初每捕获一条数据就写一次文件结果磁盘 I/O 成瓶颈丢数据率高达 15%。正确做法是内存中缓存 100 条约 1 秒再批量写入。Node.js 的fs.createWriteStream配合write()方法稳定性和速度提升 5 倍。3.4 稳定性保障自动重连、心跳检测与异常熔断行情监听不是“启动就完事”它必须应对页面崩溃、网络抖动、目标站升级等现实问题页面存活检测用setInterval每 5 秒检查document.querySelector(.stock-price)是否存在。如果连续 3 次查不到判定页面已跳转或崩溃自动执行page.goto()重新加载。请求成功率熔断维护一个滑动窗口最近 100 次请求计算成功率。如果低于 80%暂停监听 30 秒避免雪崩。恢复后先发一个探测请求如/api/health成功后再继续。内存泄漏防护Ajax-hook 的回调函数如果引用了外部大对象如整个 DOM 树会导致内存无法释放。我的解决方案是在onResponse回调中只传递必要字段config.url,uint8Array所有解析逻辑放在独立函数中确保回调执行完后引用立即断开。日志分级INFO 级记录请求 URLWARN 级记录解析失败ERROR 级记录网络错误。日志文件按天滚动保留 7 天。这样出了问题grep ERROR hook-20240515.log一行命令就能定位。4. 常见问题与排查技巧实录那些文档里不会写的实战经验Ajax-hook 看似简单但真正在股票行情这种高压、高变场景下落地会遇到一堆“理论上可行实际上翻车”的问题。我把过去两年踩过的坑按发生频率排序整理成这份速查手册。每个问题都附带现象、根因、验证方法和终极解法全是血泪总结。4.1 问题速查表高频故障与一键修复问题现象根本原因快速验证方法终极解法实操耗时完全收不到onRequest事件目标网站使用了 Service Worker 拦截所有网络请求Ajax-hook 无法劫持 SW 内部的 fetch在 Application Service Workers 面板查看是否有激活的 SW在 Console 执行navigator.serviceWorker.getRegistration()在 Chrome 启动参数中加入--unsafely-treat-insecure-origin-as-securehttp://localhost:3000 --user-data-dir/tmp/sw-test或让网站开发者禁用 SW5 分钟onResponse拿到的 body 是空的响应体已被页面 JS 读取过如response.json()ReadableStream 只能消费一次在onResponse中执行response.clone().text().then(console.log)如果报错 body used already即确认在onRequest钩子中给请求头加Cache-Control: no-cache强制浏览器不复用缓存响应或改用onResponseHeadersonResponseEnd组合从内存中重建响应10 分钟数据解析后时间戳全错如 1970 年服务器返回的时间戳是毫秒级但 Protobuf schema 定义为秒级 int64导致解析溢出查看原始二进制数据用console.log(uint8Array.slice(0, 16))输出前 16 字节对照 Protobuf 文档确认字段类型修改.proto文件将int64 timestamp改为int64 timestamp_ms或在解析后手动除以 10003 分钟监听脚本运行 2 小时后 CPU 占用飙升至 90%onResponse回调中做了同步阻塞操作如JSON.stringify()大对象、fs.writeFileSync在 Performance 面板录制 10 秒查看主线程 Flame Chart定位长时间运行的函数所有 I/O 操作文件写入、数据库插入必须异步大对象序列化改用JSON.stringify()的 replacer 参数控制深度启用 Node.js 的--max-old-space-size409615 分钟同一支股票不同时间段的数据格式突变如 JSON → Protobuf目标网站灰度发布新版本部分节点返回新协议部分返回旧协议抓取 100 个响应统计content-type分布用file命令检查二进制文件头在onResponse中先用uint8Array[0] 0x0a判断是否为 ProtobufPB 的 tag 字节再分支解析建立格式白名单自动学习20 分钟4.2 独家避坑技巧提升 3 倍调试效率的 5 个冷知识技巧 1用performance.now()打时间戳而非Date.now()股票行情对时间精度要求极高毫秒级。Date.now()受系统时钟调整影响可能跳变performance.now()返回的是高精度单调递增时间戳误差 1ms。在onResponse中记录performance.now()能精准定位网络延迟、JS 解析耗时。技巧 2onResponseHeaders是破解动态 header 的金钥匙很多网站的X-Signature是基于X-Timestamp和请求体计算的。你无法预测X-Timestamp但可以在onResponseHeaders中用正则/(X-Timestamp:\s*\d)/i提取它再结合onRequestBody拿到的 body反推签名算法。我就是靠这个3 小时内破解了某期货公司的 WebSocket 鉴权。技巧 3fetch的redirect: manual是隐藏开关当目标网站用 302 重定向到真实行情地址时fetch默认跟随你只看到最终 URL。加上redirect: manualonRequest就能捕获重定向请求从而发现被隐藏的真实接口。技巧 4XMLHttpRequest.responseType arraybuffer必须在open()后、send()前设置这是官方文档没强调的细节。如果在onRequest中设置会失效。正确姿势在onRequest中记录 URL然后在onResponse中用response.arrayBuffer()获取效果一样且更安全。技巧 5用chrome.devtools.networkAPI 辅助验证Ajax-hook 是 JS 层DevTools 是浏览器层。两者数据应该一致。写个简单脚本chrome.devtools.network.onRequestFinished.addListener(req console.log(req.request.url))与 hook 日志对比。如果 DevTools 能看到hook 却收不到100% 是 hook 注入时机问题如注入太晚请求已发出。5. 进阶应用与边界思考当 Ajax-hook 遇上 WebSocket 和 Service WorkerAjax-hook 的能力边界在哪里它能否监听 WebSocket 消息能否捕获 Service Worker 的后台请求这是所有进阶用户必然追问的问题。答案不是简单的“能”或“不能”而是取决于你如何理解“通信”的定义以及愿意付出多少工程代价。5.1 WebSocket不是 Ajax-hook 的原生领地但可巧妙借力WebSocket 是独立于 HTTP 的全双工协议Ajax-hook 的 XHR/fetch 劫持对它完全无效。但现实中的股票行情往往采用“HTTP 降级 WebSocket 升级”的混合架构页面先发一个 HTTP 请求如/api/ws/handshake服务器返回一个Upgrade: websocket响应头浏览器再发起真正的 WebSocket 连接。这个初始 handshake 请求正是 Ajax-hook 的狩猎场。我处理过某港股行情站其 WebSocket 地址是动态生成的格式为wss://realtime.xxx.com/{token}/{symbol}其中token由 handshake 接口返回。这时onResponse钩子捕获到 handshake 响应正则提取{token:abc123,symbol:00700.HK}再用new WebSocket(wss://realtime.xxx.com/abc123/00700.HK)手动建立连接。后续的onmessage事件就由原生 WebSocket API 处理。Ajax-hook 在这里扮演的是“引路人”角色不替代 WebSocket但让它变得可预测、可复现。注意不要试图用 Ajax-hook 去劫持WebSocket.prototype.send。Chrome 110 已将 WebSocket 构造函数设为不可枚举且其内部通信走的是 C 底层JS 无法拦截。强行重写会导致页面白屏。5.2 Service WorkerAjax-hook 的盲区也是最危险的雷区Service WorkerSW是运行在浏览器后台的脚本它能拦截所有 fetch 请求甚至包括页面自身的 XHR。一旦网站注册了 SWAjax-hook 就成了“聋子”——因为请求根本没走到 XHR/fetch 层就被 SW 拦截并返回了缓存或伪造响应。这是目前 Ajax-hook 最大的能力缺口。但“盲区”不等于“死区”。我的应对策略是“两步走”第一步识别 SW 并绕过在页面加载时执行if (serviceWorker in navigator) { navigator.serviceWorker.getRegistration().then(r console.log(SW active:, r?.active?.scriptURL)) }。如果发现 SW立即用navigator.serviceWorker.getRegistration().then(r r?.active?.unregister())尝试注销需用户授权。大部分金融网站的 SW 权限有限注销后即可恢复 hook。第二步与 SW 共存做协同监听如果 SW 无法注销如银行类网站那就转变思路在 SW 脚本中主动postMessage将捕获的请求/响应数据发给页面。页面监听navigator.serviceWorker.addEventListener(message, ...)再把数据交给 Ajax-hook 的回调处理。这需要你能修改 SW 代码通常不行或说服网站方配合。实践中我更倾向第一种——90% 的行情网站SW 只用于静态资源缓存注销后不影响核心功能。5.3 安全与合规的灰色地带技术无罪但使用需敬畏最后必须坦诚讨论一个无法回避的话题用 Ajax-hook 获取他人网站的私有数据是否合规我的观点很明确技术本身中立但使用场景决定性质。合规场景你拥有该网站的合法访问权限如公司内网行情系统且数据仅用于个人学习、策略回测、内部风控审计。此时Ajax-hook 是提升研发效率的利器。高危场景未经许可大规模采集竞品网站的实时行情用于商业目的如自营交易、数据转售。这不仅违反《反不正当竞争法》更可能触碰《刑法》第 285 条“非法获取计算机信息系统数据罪”。我的实践红线所有监听脚本开头必加注释// 仅用于 [具体学习项目]数据不外传不商用自动添加User-Agent头标识为StockHook-Dev/1.0方便网站方追溯设置请求频率上限如每秒 ≤ 20 次绝不施加压力每次启动前手动确认目标网站的robots.txt和Terms of Service避开明确禁止的接口。技术人的尊严不在于能破解多难的协议而在于清楚知道“可以做什么”和“不该做什么”。Ajax-hook 给了我们一把锋利的刀但握刀的手必须稳、准、有分寸。我个人在实际操作中的体会是最稳定的行情监听从来不是靠“黑科技”硬刚而是用 Ajax-hook 做探针摸清数据脉络后再与网站方沟通争取官方 API 接入。毕竟一根网线连着的不仅是数据流更是信任。