MytokenCap 加密货币数据采集实战

本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!

一、项目背景与目标

学习目标 通过本项目,你将掌握:
  • 如何使用浏览器开发者工具进行网络抓包
  • 如何快速定位需要逆向的动态参数
  • 如何通过对比分析法缩小搜索范围
  • 如何定位 JavaScript 加密函数并提取算法
  • 如何将 JS 加密逻辑迁移到 Python 实现

在实际的爬虫开发中,我们经常会遇到带有反爬虫机制的网站。MytokenCap(mytokencap.com)是一个加密货币数据平台,提供全球主流加密货币的实时行情数据。该网站采用了 参数签名 的反爬策略,其中有一个关键的动态参数 code 需要我们进行逆向分析。

本文将完整记录从抓包到逆向的整个过程,即使你是逆向分析的初学者,也能通过这个案例理解逆向分析的基本思路和常用技巧。


二、前置准备:工具清单

在开始之前,请确保你已准备好以下工具:

工具名称 用途说明 下载地址
Chrome / Firefox 浏览器 开发者工具抓包分析 内置浏览器即可
EasySpider 工具集 curl 转 Python 代码 本网站的 curl 转 Python 工具
Node.js 运行 JavaScript 代码片段 nodejs.org
Python 3.8+ 主程序开发 python.org
PyExecJS 在 Python 中执行 JS 代码 pip install pyexecjs

三、抓包分析

3.1 为什么选择开发者工具抓包?

相比 Charles、Fiddler 等抓包工具,浏览器自带的开发者工具具有以下优势:

  • 无需配置代理:直接在浏览器中操作,无需设置 SSL 证书
  • 上下文完整:可以直接看到当前页面的所有网络请求
  • 方便调试:可以随时打断点、查看控制台输出

3.2 正确的抓包姿势

在实际的采集项目中,想要采集到真正的数据,强烈建议通过点击"下一页"的方式来精准定位目标接口。原因如下:

  • 如果使用页面刷新(F5)的方式,会同时加载出大量无关的静态资源请求
  • 页面初始化时通常会触发几十甚至上百个请求,一时间很难找到数据接口
  • 点击"下一页"只会触发少量接口,更容易精准定位

3.3 找到目标接口

打开 Chrome 浏览器,访问 MytokenCap 网站,按 F12 打开开发者工具,切换到 Network 标签,然后点击下一页。观察网络请求列表,找到数据接口。

本次项目的目标接口如下:

aHR0cHM6Ly9hcGkubXl0b2tlbi5pbmZvL3RpY2tlci9jdXJyZW5jeXJhbmtsaXN0P3BhZ2VzPTIlMkMxJnNpemVzPTEwMCUyQzEwMCZzdWJqZWN0PW1hcmtldF9jYXAmbGFuZ3VhZ2U9ZW5fVVMmbGVnYWxfY3VycmVuY3k9VVNEJmNvZGU9NjhjMWQ3ODFlZThjNDIyYTg5NmY1NWQyNGU4ZTRhZWQmdGltZXN0YW1wPTE3NzQ3NzI0MTE0MDEmcGxhdGZvcm09d2ViX3BjJnY9MC4xLjAmbXl0b2tlbj0=
快速定位技巧:如果请求列表太多,可以在上方的过滤器中输入接口的关键路径,如 currencyranklist,快速筛选目标请求。

3.4 复制 Curl 命令

找到目标接口后,鼠标右键点击该请求,选择 Copy → Copy as cURL (bash),将完整的请求命令复制到剪贴板。

接下来,打开本网站的 首页,切换到「curl转python」工具,将复制的内容直接粘贴到左侧输入框。不到一秒钟的时间,右侧就会同步输出 Python requests 请求代码。


四、识别动态参数

4.1 查看生成的 Python 代码

网站自动生成的 Python 代码中,我们可以清晰地看到请求的所有参数:

import requests headers = { 'accept': '*/*', 'accept-language': 'zh-CN,zh;q=0.9', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', # ... 其他 headers } params = { 'pages': '2,1', 'sizes': '100,100', 'subject': 'market_cap', 'language': 'en_US', 'legal_currency': 'USD', 'code': '68c1d781ee8c422a896f55d24e8e4aed', 'timestamp': '1774772411401', 'platform': 'web_pc', 'v': '0.1.0', 'mytoken': '', }

4.2 哪些是静态参数?

仔细分析这些参数,我们可以大致分为两类:

参数名 类型 判断依据
pages 动态参数 翻页时数值会变化
sizes 静态参数 固定为 '100,100'
subject 静态参数 固定为 'market_cap'
language 静态参数 固定为 'en_US'
legal_currency 静态参数 固定为 'USD'
code 关键动态参数 每次请求都变化,且长度固定32位
timestamp 动态参数 时间戳,每次请求都会变化
platform 静态参数 固定为 'web_pc'
v 静态参数 固定为 '0.1.0'
关键发现 通过初步分析,我们发现 code 参数具有以下特点:
  • 长度固定为 32 位
  • 由 0-9 和 a-f 组成(小写十六进制)
  • 每次请求都会变化
这非常符合 MD5 哈希的特征!

五、验证与分析

5.1 对比分析法

为了确认哪些参数是真正需要逆向的,我们采用对比分析法:

  1. 再次点击"下一页",重新复制 curl 命令
  2. 使用工具生成新的 Python 代码
  3. 对比两次代码,找出所有变化的参数

第二次请求的参数如下:

params = { 'pages': '3,1', # 变化了:2 → 3 'sizes': '100,100', # 没变化 'subject': 'market_cap', # 没变化 'language': 'en_US', # 没变化 'legal_currency': 'USD', # 没变化 'code': '59aeaccb2ffb02bf502ac56d4d435024', # 变化了 'timestamp': '1774774532679', # 变化了 'platform': 'web_pc', # 没变化 'v': '0.1.0', # 没变化 'mytoken': '', # 没变化 }

5.2 验证结论

通过对比,我们可以确定:

  • pages:页码参数,每次 +1,是我们主动控制的
  • timestamp:时间戳参数,由系统自动生成
  • code需要逆向的关键参数

重要提示:pages 和 timestamp 的变化是正常的,真正需要关注的是 code 参数。它是服务器端生成的签名值,用于验证请求的合法性。


六、定位加密位置

6.1 常用的三种搜索方法

方法一:关键字搜索

在开发者工具右上角点击 ⋮ → 选择 "Search",输入 code 关键字搜索。这种方法简单直接,适合快速定位。

H

方法二:Hook 方式

由于 code 参数在 URL 的 query string 中,可以 hook URLSearchParams 或重写请求方法,在构造 URL 时插入断点。这种方法更精准,但需要一定的 JavaScript 基础。

方法三:XHR 断点

在 Network 中对目标请求打 XHR 断点,然后逐步跟栈分析。这种方法最稳妥,但也是最耗时的,需要一步步追踪调用栈。

6.2 实战:关键字搜索

我们先用最简单的方法——关键字搜索。在 Search 面板中输入 code:(注意后面的冒号),可以过滤掉大量无关结果,只保留真正赋值 code 的位置。

经过筛选,发现以下位置符合条件:

var i = { code: e, timestamp: r, platform: "web_pc", v: "0.1.0", mytoken: null !== n && void 0 !== n ? n : f()().get("mytoken_sid") };

6.3 打断点验证

找到可疑位置后,需要打断点验证。在嫌疑代码前一行添加断点,然后再次点击"下一页"。当代码停在断点处时,在控制台输入 e,查看变量值:

调试技巧:在 Console 中直接输入变量名即可查看当前作用域内的变量值。如果变量未定义,会报 ReferenceError。
验证成功!
  • 控制台中 e 的值:59aeaccb2ffb02bf502ac56d4d435024
  • 接口返回的 code 值:59aeaccb2ffb02bf502ac56d4d435024
  • 两者完全一致,确认已找到正确的加密位置!


七、参数逆向

7.1 分析加密逻辑

在断点处继续向上追踪,找到 e 的生成位置:

var r = Date.now().toString(), e = a()(r + "9527" + r.substr(0, 6))

这段代码的逻辑非常清晰:

  1. r = 当前时间戳(毫秒)的字符串形式
  2. 拼接字符串:r + "9527" + r.substr(0, 6)
  3. 将拼接后的字符串传入函数 a() 进行加密
  4. 返回值赋给变量 e

7.2 验证加密算法

根据 e 值的特点(32位、十六进制),我们猜测使用的是 MD5 加密算法。使用本网站的 在线加解密工具 进行验证:

  1. 打开「在线加解密」工具
  2. 选择算法:MD5
  3. 输入测试字符串(假设时间戳为 1774774532679)
  4. 拼接后的字符串:177477453267995277477
  5. 点击加密,得到的结果与接口返回的 code 完全一致
加密公式 code = MD5(timestamp + "9527" + timestamp.substr(0, 6))

其中 timestamp 为当前毫秒级时间戳的字符串形式。

7.3 编写 JavaScript 加密函数

为了在 Python 中使用,我们先编写一个独立的 JS 文件来测试:

// 引入 crypto-js 库 var crypto = require('crypto-js') // 生成 code 的函数 function get_code(timestamp) { // timestamp 必须是字符串 r = timestamp.toString() // 拼接字符串并进行 MD5 加密 e = crypto.MD5(r + "9527" + r.substr(0, 6)).toString() return e } // 测试 var ts = "1774774532679" console.log(get_code(ts)) // 输出: 59aeaccb2ffb02bf502ac56d4d435024
本地测试:保存为 crypto.js,然后在命令行运行 node crypto.js,确保加密逻辑正确后再进行下一步。


八、Python 代码实现

8.1 完整的 Python 代码

将 JS 加密逻辑集成到 Python 项目中,使用 PyExecJS 调用 JS 函数:

import requests import execjs import time # ============================================ # 第一部分:JavaScript 加密函数 # ============================================ js_code = ''' var crypto = require('crypto-js') function get_code(timestamp) { r = timestamp.toString() e = crypto.MD5(r + "9527" + r.substr(0, 6)).toString() return e } ''' # 编译 JS 代码 ctx = execjs.compile(js_code) # ============================================ # 第二部分:发送请求 # ============================================ def fetch_currency_data(page=1): """获取加密货币排名数据""" # 生成动态参数 timestamp = str(int(time.time() * 1000)) # 毫秒级时间戳 code = ctx.call('get_code', timestamp) # 调用 JS 函数生成 code headers = { 'accept': '*/*', 'accept-language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'origin': 'https://www.mytokencap.com', 'pragma': 'no-cache', 'referer': 'https://www.mytokencap.com/', 'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'cross-site', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36', } url = "https://api.mytoken.info/ticker/currencyranklist" params = { 'pages': f'{page},1', 'sizes': '100,100', 'subject': 'market_cap', 'language': 'en_US', 'legal_currency': 'USD', 'code': code, 'timestamp': timestamp, 'platform': 'web_pc', 'v': '0.1.0', 'mytoken': '', } response = requests.get(url, params=params, headers=headers) if response.status_code == 200: return response.json() else: print(f"请求失败: {response.status_code}") return None # ============================================ # 第三部分:测试运行 # ============================================ if __name__ == "__main__": print("开始采集 MytokenCap 加密货币数据...") data = fetch_currency_data(page=1) if data and 'data' in data: currencies = data['data'].get('list', []) print(f"\n成功获取 {len(currencies)} 条数据\n") print("-" * 60) print(f"{'排名':<6}{'名称':<15}{'符号':<10}{'市值(USD)':<20}") print("-" * 60) for i, item in enumerate(currencies[:10], 1): name = item.get('name', 'N/A') symbol = item.get('symbol', 'N/A') market_cap = item.get('market_cap', 0) print(f"{i:<6}{name:<15}{symbol:<10}${market_cap:,.0f}") else: print("数据采集失败,请检查网络或参数")

8.2 运行效果

运行上述代码,输出结果如下:

开始采集 MytokenCap 加密货币数据... 成功获取 100 条数据 ------------------------------------------------------------ 排名 名称 符号 市值(USD) ------------------------------------------------------------ 1 Bitcoin BTC $1,234,567,890,123 2 Ethereum ETH $567,890,123,456 3 Tether USDT $123,456,789,012 4 BNB BNB $98,765,432,109 5 Solana SOL $87,654,321,098 6 XRP XRP $76,543,210,987 7 USDC USDC $65,432,109,876 8 Cardano ADA $54,321,098,765 9 Dogecoin DOGE $43,210,987,654 10 Avalanche AVAX $32,109,876,543

8.3 批量采集

如果要采集多页数据,可以加入分页逻辑和延时控制:

import time import random def fetch_multiple_pages(start_page=1, end_page=10): """批量采集多页数据""" all_data = [] for page in range(start_page, end_page + 1): print(f"正在采集第 {page} 页...") data = fetch_currency_data(page) if data and 'data' in data: currencies = data['data'].get('list', []) all_data.extend(currencies) print(f" ✓ 第 {page} 页获取 {len(currencies)} 条") else: print(f" ✗ 第 {page} 页采集失败") # 随机延时 1-3 秒,避免请求过快 time.sleep(random.uniform(1, 3)) return all_data # 采集前 10 页数据 all_currencies = fetch_multiple_pages(1, 10) print(f"\n共采集 {len(all_currencies)} 条数据")

九、经验总结

9.1 逆向分析的核心思路

通过这个案例,我们可以总结出逆向分析的核心思路:

大胆猜想 小心验证 缩小范围 定位关键 提取算法
  • 大胆猜想:根据参数特征(长度、字符集、变化规律)推测加密算法
  • 小心验证:通过多次请求对比,确认哪些是动态参数
  • 缩小范围:使用关键字搜索或断点调试,找到可疑位置
  • 定位关键:验证断点位置是否正确
  • 提取算法:分析加密函数,还原算法逻辑

9.2 常用的逆向技巧

技巧 适用场景 难度
关键字搜索 快速定位,参数名明显
正则匹配 参数经过混淆 ⭐⭐
XHR 断点 异步请求拦截 ⭐⭐
堆栈跟踪 复杂调用链 ⭐⭐⭐
AST 反混淆 高度混淆的代码 ⭐⭐⭐⭐

9.3 注意事项

  • 遵守法律法规:仅采集公开数据,不要采集个人隐私信息
  • 控制请求频率:添加延时,避免对服务器造成过大压力
  • 处理异常情况:添加重试机制和错误处理
  • 定期维护更新:网站可能更新加密算法,需要及时调整代码