【网络爬虫 | Python】数字货币ok链上bitcoin大额交易实时爬取,存入 mysql 数据库
文章目录
- 一、网站分析
- 二、js 逆向获取 X-Apikey
- 三、python 调用 js 获取 X-Apikey
- 四、python 爬虫部分
- 五、mysql 数据库、日志、配置文件、目录结构
- 六、结尾
一、网站分析
oklink:https://www.oklink.com/
btc 大额交易:https://www.oklink.com/btc/tx-list/large

Txn hash,交易哈希。链上的交易都会有一个交易哈希值
block,区块。链上交易都会被矿工打包到区块上,成功打包的区块会被添加到区块链上
input amount,交易数额
Txn fee,就是gas 费,矿工打包肯定不能白干活,这些钱是给矿工的
交易数据是动态加载的,这些数据要么智能合约直接从链上抓取,要么抓包 requests 从网站上拿。今天的主题不是合约,废话不多说开始爬
抓包,随便一个交易哈希值,直接定位到了惟一的一个数据包,一眼丁真,交易数据都是从这儿加载的
看一下数据包头部
这个网站还是很好爬的,通过数据包头部我们可以知道:
- 这是一个get请求
- 请求携带参数,t 是时间戳,limit一页显示的数量,sort,curType 排序方式
我们直接请求url,不带任何 request body 试试
响应 API_KEY_NOT_FIND。显然,请求缺乏 api key 这个参数
再回到数据包中,发现请求头里面有一个参数叫 X-Apikey
带上这个参数,发现请求成功了
但是过一会再请求,发现响应:
不懂英文没关系,看到有个单词叫 expired
某个东西过期了。
我们请求的东西,跟时间有关的有两个
- get 请求的 params 的时间戳
- X-Apikey
肯定就是 X-Apikey 过期了
好,下一步,js 逆向,构造 X-Apikey
二、js 逆向获取 X-Apikey
抓包,搜索一下 X-Apikey
一眼就能看出来,x-apikey 这个参数是在 index.exxxx.js 里面构造的。没错,这个网站逆向就是这么顺利
右键,在来源面板中打开
ctrl+f 查找 X-Apikey
发现只有一个搜索结果
var n = new XMLHttpRequest;
n.open("get", e, !0),
n.setRequestHeader("x-apiKey", p.Z.getApiKey()),
显然,在这块代码,构造了一个 XMLHttpRequest请求
在请求头添加了 x-apiKey 参数
那么,这个参数就是从 p.Z.getApiKey() 获取的
我们抓包页面查找 getApiKey 这个函数,注意不要在当前js代码查找
对比一下,应该可以确定,这个函数是在第二、三个js代码里面被定义的
打开那段代码
key: "getApiKey",
value: function() {var e = (new Date).getTime(), t = this.encryptApiKey();return e = this.encryptTime(e),this.comb(t, e)
}
学过 js 的应该知道,这段代码定义了object中的 getApiKey 这个方法,下面是方法体
这段代码很明显了
首先获取当前时间的时间戳
然后把 ApiKey 加密一下
把时间加密一下
最后调用 comb 函数,返回最终结果
现在,要用上面的方法,查找这段代码里面出现的自定义函数,以及里面定义的函数,ctrl+f 查找
encryptApiKey:
encryptTime
comb
嗯,是这三个,但是还不止这三个
encryptApiKey 有一个参数,this.API_KEY
encryptTime 有一个 l 参数
查找一下
嗯,就在这儿了
注意哈,等会我们改写 js 代码的时候,一定要把这两个参数设置成请求获取的,不能保证这两个参数永远站方不会变,但是调试的时候可以
综合一下上面的 js 代码
key: "getApiKey",
value: function() {var e = (new Date).getTime(), t = this.encryptApiKey();return e = this.encryptTime(e),this.comb(t, e)
}key: "encryptApiKey",
value: function() {var e = this.API_KEY, t = e.split(""), r = t.splice(0, 8);return e = t.concat(r).join("")
}key: "encryptTime",
value: function(e) {var t = (1 * e + l).toString().split(""), r = parseInt(10 * Math.random(), 10), n = parseInt(10 * Math.random(), 10), i = parseInt(10 * Math.random(), 10);return t.concat([r, n, i]).join("")
}key: "comb",
value: function(e, t) {var r = "".concat(e, "|").concat(t);return window.btoa(r)
}
把它改写一下
API_KEY = "a2c903cc-b31e-4547-9299-b6d07b7631ab";
l = 1111111111111;function encryptApiKey(API_KEY) {var e = API_KEY, t = e.split(""), r = t.splice(0, 8);return t.concat(r).join("");
}function encryptTime(e, l) {var t = (1 * e + l).toString().split(""), r = parseInt(10 * Math.random(), 10), n = parseInt(10 * Math.random(), 10), i = parseInt(10 * Math.random(), 10);return t.concat([r, n, i]).join("")
}function comb(e, t) {var r = "".concat(e, "|").concat(t);return btoa(r);
}function getApiKey(API_KEY, l) {var e = (new Date).getTime(), t = encryptApiKey(API_KEY);e = encryptTime(e, l);return comb(t, e);
}a = getApiKey(API_KEY, l);
console.log(a);
用 node.js 运行一下
运行成功了
但是,我们等会用 python 执行的话,comb 下的 btoa 这个函数是运行不了的,因为它属于 window.btoa,属于 bom 而不是 ecmascript
所以我们等会只能先把 r 返回,再通过 python 实现 btoa
三、python 调用 js 获取 X-Apikey
在 python 中,有很多库可以调用 js,本文选择 js2py。你用哪个都行
首先创建一个 js 运行环境
把上面写的那段 js 代码读进来
请求获取刚刚我们说的 api_key 这个变量,通过正则表达式提取 api_key
python 调用 js 的 getApiKey 方法,获取未 btoa 过的数据
python 实现 btoa ,获取 X-Apikey
context = js2py.EvalJs()
with open("config\\X-Apikey.js", "r") as f:js = f.read()
context.execute(js)
# 获取 API_KEY
r = requests.get(url="https://static.oklink.com/cdn/assets/okfe/oklink-nav/vender/index.681aa2a6.js").text
API_KEY = re.findall('this.API_KEY.*?=.*?"(.*?)"', r)[0]
l = 1111111111111
# 调用 js
api_key = context.getApiKey(API_KEY, l)
return base64.b64encode(api_key.encode("utf-8")).decode("utf-8") # btoa
至此,X-Apikey 解决了,那所有问题都解决了,无非就是构造一下请求,存一下 mysql
四、python 爬虫部分
import re
import yaml
import time
import json
import base64
import js2py
import requests
import datetime
from requests.models import Response
from db import Database
from logger import Loggerclass Spider:LAST_HASH = ""def __init__(self) -> None:self.X_ApiKey = Falseself.readConfig()self.init(host=self.config.get('host'),port=self.config.get('port'),user=self.config.get('user'),password=self.config.get('password'))def init(self, host, port, user, password) -> None:self.logger = Logger()self.databse = Database(host=host,port=port,user=user,password=password,logger=self.logger)def readConfig(self) -> None:with open("config\\config.yaml", "r") as f:self.config = yaml.safe_load(f.read())keys = ['refresh', 'host', 'port', 'user', 'password']for k in keys:if self.config.get(k) is None:raise Exception("missing config key: ", k)self.__init_X_ApiKey()def __init_X_ApiKey(self) -> None:if not self.X_ApiKey:self.X_ApiKey = self.__getApiKey()def __getApiKey(self) -> str:context = js2py.EvalJs()with open("config\\X-Apikey.js", "r") as f:js = f.read()context.execute(js)# get API_KEY and lr = requests.get(url="https://static.oklink.com/cdn/assets/okfe/oklink-nav/vender/index.681aa2a6.js").textAPI_KEY = re.findall('this.API_KEY.*?=.*?"(.*?)"', r)[0]l = 1111111111111api_key = context.getApiKey(API_KEY, l)return base64.b64encode(api_key.encode("utf-8")).decode("utf-8")def request(self) -> list:r = requests.get(url='https://www.oklink.com/api/explorer/v1/btc/transactionsNoRestrict?offset=0&txType=&limit=20&sort=realTransferValue,desc&curType=large&t='+str(int(time.time())),headers={"X-Apikey": self.X_ApiKey})parse = r.json()status = Trueif parse.get("code") != 0 or \parse.get("msg") != "" or \parse.get("data") is None:status = Falsereturn (status, r)def dataClean(self, res: Response) -> list:data: list = res.json()['data']['hits']result = []for each in data:item = [each['hash'], each['blockHeight'], each['blocktime'], each['inputsCount'], each['outputsCount'], each['inputsValue'],int(each['fee'])*0.000000001]t = datetime.datetime.fromtimestamp(int(item[2]))item.append(f'{t.month}/{t.day}/{t.year}, {t.hour}:{t.minute}:{t.second}')result.append(item)result.sort(key=lambda x: x[2], reverse=True)index = len(result)for idx in range(len(result)):if result[idx][0] == self.LAST_HASH:index = idxbreakreturn result[:index]def write(self, data: list[list]) -> None:if len(data) == 0:returnstatus = self.databse.write(data)if status:self.LAST_HASH = data[0][0]self.logger.info(msg="入库")def run(self) -> None:while True:res = self.request()if res[0]: # 请求成功data = self.dataClean(res[1])self.write(data)else:self.logger.write_log(location='oklink.run',err=json.dumps(res[1]))self.X_ApiKey = Nonetime.sleep(self.config.get('refresh'))if __name__ == "__main__":spider = Spider()while True:try:spider.run()except:pass
五、mysql 数据库、日志、配置文件、目录结构
mysql
import time
import datetime
import threading
from logger import Logger
import pymysql as pysqlclass Database:database_lock: threading.Lock = threading.Lock()def __init__(self, host, port, user, password, logger: Logger) -> None:self.connect(host=host,port=port,user=user,password=password)self.sql_sentences()self.init_database()self.logger = loggerdef connect(self, host, port, user, password) -> None:self.conn = pysql.connect(host=host,port=port,user=user,passwd=password)self.cursor = self.conn.cursor()def sql_sentences(self, database: str="oklink") -> None:t = datetime.datetime.fromtimestamp(time.time())table_name = 'bitcoin'self.database = databaseself.sql_create_database = '''create database if not exists %s''' % (database, )self.sql_create_table = '''create table if not exists %s (hash char(64) primary key comment '交易哈希',block int comment '区块',t int comment '时间戳',input int comment 'input',output int comment 'output',input_amount char(30) comment '交易数额',Txn_fee char(30) comment 'gas费',transaction_time char(30) comment '交易时间')''' % (table_name)self.sql_store = f'''insert into {database}.{table_name} (hash, block, t, input, output, input_amount, Txn_fee, transaction_time) value ('%s', %d, %d, %d, %d, '%s', '%s', '%s');'''def init_database(self) -> None:self.cursor.execute(self.sql_create_database)self.cursor.execute('use %s' % self.database)self.cursor.execute(self.sql_create_table)self.conn.commit()def write(self, data: list[list]) -> bool:try:with Database.database_lock:for item in data:self.cursor.execute(self.sql_store % tuple(item))self.conn.commit()return Trueexcept Exception as e:self.conn.rollback()self.logger.write_log(location="db.write",err=e)return False
日志
import os
import csv
import time
import datetime
import threadingtry:os.mkdir("log")
except:passclass Logger:def __init__(self) -> None:self.f = open("log\\"+datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")+".csv", "w", newline="", encoding="u8")self.csv_writer = csv.writer(self.f)self.logger_lock: threading.Lock = threading.Lock()def write_log(self, location: str, err) -> None:with self.logger_lock: self.print_log(location=location, err=err)self.csv_writer.writerow([datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),location,err])self.f.flush()def print_log(self, location: str, err) -> None:format = f'time: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")} | location: <{location}> | error: {err}'print(format)def info(self, msg: str) -> None:format = f'time: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")} | msg: 'print(format, msg)
配置文件
refresh:10
host:'localhost'
port:3306
user:'root'
password:'SpiderXbest'
目录结构
六、结尾
喜欢的话,点个关注吧~

原创文章,禁止抄袭!!!!!!!!!!!
相关文章:
【网络爬虫 | Python】数字货币ok链上bitcoin大额交易实时爬取,存入 mysql 数据库
文章目录 一、网站分析二、js 逆向获取 X-Apikey三、python 调用 js 获取 X-Apikey四、python 爬虫部分五、mysql 数据库、日志、配置文件、目录结构六、结尾 一、网站分析 oklink:https://www.oklink.com/ btc 大额交易:https://www.oklink.com/btc/tx-…...
【Servlet】实现Servlet程序
文章目录 1. 最朴素方式1. 创建项目2. 引入依赖3. 创建目录4. 编写代码5. 打包程序6. 部署程序7. 验证程序 2. 更方便方式1. 安装Smart TomCat插件2. 启动 1. 最朴素方式 1. 创建项目 选择Maven项目 2. 引入依赖 Maven项目创建完后会生成一个pom.xml文件,我们可…...
binlog 和 redolog 有什么区别
binlog 和 redolog 都是 Mysql 里面用来记录数据库数据变更操作的日志. binlog 其中 binlog 主要用来做数据备份、数据恢复和数据同步,在Mysql 的主从数据同步的场景中,master 节点的数据变更,会写入到 binlog 中,然后再把 binl…...
Git 修改已提交的用户名和邮箱
Git 修改已提交的用户名和邮箱 修改上一次提交的邮箱和用户名 git commit --amend --author Name<email>批量修改多次提交的邮箱和用户名 新建一个 .sh 脚本在 git 根目录下.sh脚本内容如下 git filter-branch --env-filter an"$GIT_AUTHOR_NAME" am"…...
小游戏外包开发流程及费用
小游戏的开发流程和费用会因项目的规模、复杂性和所选技术平台而有所不同。以下是一般的小游戏开发流程和可能的费用因素,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 开发流程: 概念和…...
Homeassistant docker配置
Homeassistant docker配置 【说明】本系列为自用教程,记录以便下次使用 【背景】一台J1900 4G64G的小主机,安装了OP系统,里面自带了Docker。为实现Homeassistant(简称HA)控制智能家居设备,进行如下配置。 【…...
Go 深入解析非类型安全指针
一、引言 非类型安全指针(也称为“裸指针”或“原始指针”)在编程领域中一直是一个具有争议和挑战性的主题。它们赋予程序员直接操作计算机内存的能力,为高级性能优化和底层系统交互提供了可能。然而,这种能力往往伴随着高风险&a…...
vue动态绑定class
Vue.js 允许您使用 v-bind 指令或简写的 : 来动态绑定 class 属性。这允许您基于某些条件为元素添加或删除类名,从而实现动态样式控制。以下是一些示例: 动态添加单个类名: <template> <div> <p :class"{ active: isActi…...
UDP网络通信反复发收
package UDP2;import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Scanner;/* * 完成UDP 通信快速入门 实现发1收1*/ public class Client {public static void main(String[] args) throws Exception{// …...
ip报头和ip报文切片组装问题
在tcp层将数据打包封装向下传递后,网络层将其整个看为一个数据,然后对其数据加网络报头操作,在网络层最具有代表的协议就是ip协议。在这里我们探究ipv4的报头。 ip报头 4位版本:指定ip的版本号,对于ipv4来说就是4。 …...
linux之应用编程回顾总结
gcc编译过程 一个c/c文件要经过预处理、编译、汇编和链接4个阶段,才能变成可执行文件 1.预处理 C/C源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。预处理…...
nginx配置负载均衡--实战项目(适用于轮询、加权轮询、ip_hash)
👨🎓博主简介 🏅云计算领域优质创作者 🏅华为云开发者社区专家博主 🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! 🐋 希望大家多多支…...
Mac GPU MPS常用方法
Requirements Mac computers with Apple silicon or AMD GPUs macOS 12.3 or later Python 3.7 or later Xcode command-line tools: xcode-select --install 判断是否可用 import torch if torch.backends.mps.is_available():mps_device torch.device("mps")x …...
【数据结构】线性表(四)双向链表的各种操作(插入、删除、查找、修改、遍历打印)
目录 线性表的定义及其基本操作(顺序表插入、删除、查找、修改) 四、线性表的链接存储结构 1. 单链表 2. 循环链表 3. 双向链表 a. 双向链表节点结构 b. 创建一个新的节点 c. 在链表末尾插入节点 d. 在指定位置插入节点 e. 删除指定位置的节点…...
数据结构和算法——图
图 有向图 带权图 邻接矩阵 邻接表相较于邻接矩阵,减少了存储空间; 邻接表 参考视频:【尚硅谷】数据结构与算法(Java数据结构与算法)_哔哩哔哩_bilibili...
大数据学习(16)-mapreduce详解
&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦ᾑ…...
Android---OkHttp详解
OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App,OkHttp 现在几乎已经占据了所有的网络请求操作。RetroFit OkHttp 实现网络请求似乎成了一种标配。 因此&…...
向某文件中逐秒追加带序号输入当前时间 fgets fputs fprintf sprintf
//向某文件中逐秒追加带序号输入当前时间 #include<stdio.h> #include<stdlib.h> #include<time.h> #include<string.h> #include <unistd.h> int main(int argc, char const *argv[]) { time_t tv; // time(&tv);//法1:获取秒数 …...
同为科技(TOWE)机架PDU产品在IDC数据中心机房建设中的应用
当今社会互联网发展迅速, 随着带宽需求的提升, 网络的保密性、安全性的要求就越来越迫切。PDU(Power Distribution Unit) 是 PDU具备电源分配和管理功能的电源分配管理器。PDU电源插座是多有设备运行的第一道也是最为密切的部件, PDU的好坏直…...
Elasticsearch学习笔记
1.核心概念 bucket: 一个数据分组(类似于sql group by以后的数据)metric:对bucket执行的某种聚合分析的操作,比如说求平均值,最大值,最小值。一些系列的统计方法(类似 select count(1) MAX MIN AVG) 请…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...


















