当前位置: 首页 > news >正文

【网络爬虫 | 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 从网站上拿。今天的主题不是合约,废话不多说开始爬

在这里插入图片描述
抓包,随便一个交易哈希值,直接定位到了惟一的一个数据包,一眼丁真,交易数据都是从这儿加载的

看一下数据包头部

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这个网站还是很好爬的,通过数据包头部我们可以知道:

  1. 这是一个get请求
  2. 请求携带参数,t 是时间戳,limit一页显示的数量,sort,curType 排序方式

我们直接请求url,不带任何 request body 试试

在这里插入图片描述

响应 API_KEY_NOT_FIND。显然,请求缺乏 api key 这个参数
再回到数据包中,发现请求头里面有一个参数叫 X-Apikey
带上这个参数,发现请求成功了

但是过一会再请求,发现响应:

在这里插入图片描述

不懂英文没关系,看到有个单词叫 expired
某个东西过期了。
我们请求的东西,跟时间有关的有两个

  1. get 请求的 params 的时间戳
  2. 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&#xff1a;https://www.oklink.com/ btc 大额交易&#xff1a;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文件&#xff0c;我们可…...

binlog 和 redolog 有什么区别

binlog 和 redolog 都是 Mysql 里面用来记录数据库数据变更操作的日志. binlog 其中 binlog 主要用来做数据备份、数据恢复和数据同步&#xff0c;在Mysql 的主从数据同步的场景中&#xff0c;master 节点的数据变更&#xff0c;会写入到 binlog 中&#xff0c;然后再把 binl…...

Git 修改已提交的用户名和邮箱

Git 修改已提交的用户名和邮箱 修改上一次提交的邮箱和用户名 git commit --amend --author Name<email>批量修改多次提交的邮箱和用户名 新建一个 .sh 脚本在 git 根目录下.sh脚本内容如下 git filter-branch --env-filter an"$GIT_AUTHOR_NAME" am"…...

小游戏外包开发流程及费用

小游戏的开发流程和费用会因项目的规模、复杂性和所选技术平台而有所不同。以下是一般的小游戏开发流程和可能的费用因素&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 开发流程&#xff1a; 概念和…...

Homeassistant docker配置

Homeassistant docker配置 【说明】本系列为自用教程&#xff0c;记录以便下次使用 【背景】一台J1900 4G64G的小主机&#xff0c;安装了OP系统&#xff0c;里面自带了Docker。为实现Homeassistant&#xff08;简称HA&#xff09;控制智能家居设备&#xff0c;进行如下配置。 【…...

Go 深入解析非类型安全指针

一、引言 非类型安全指针&#xff08;也称为“裸指针”或“原始指针”&#xff09;在编程领域中一直是一个具有争议和挑战性的主题。它们赋予程序员直接操作计算机内存的能力&#xff0c;为高级性能优化和底层系统交互提供了可能。然而&#xff0c;这种能力往往伴随着高风险&a…...

vue动态绑定class

Vue.js 允许您使用 v-bind 指令或简写的 : 来动态绑定 class 属性。这允许您基于某些条件为元素添加或删除类名&#xff0c;从而实现动态样式控制。以下是一些示例&#xff1a; 动态添加单个类名&#xff1a; <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层将数据打包封装向下传递后&#xff0c;网络层将其整个看为一个数据&#xff0c;然后对其数据加网络报头操作&#xff0c;在网络层最具有代表的协议就是ip协议。在这里我们探究ipv4的报头。 ip报头 4位版本&#xff1a;指定ip的版本号&#xff0c;对于ipv4来说就是4。 …...

linux之应用编程回顾总结

gcc编译过程 一个c/c文件要经过预处理、编译、汇编和链接4个阶段&#xff0c;才能变成可执行文件 1.预处理 C/C源文件中&#xff0c;以“#”开头的命令被称为预处理命令&#xff0c;如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。预处理…...

nginx配置负载均衡--实战项目(适用于轮询、加权轮询、ip_hash)

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…...

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 …...

【数据结构】线性表(四)双向链表的各种操作(插入、删除、查找、修改、遍历打印)

目录 线性表的定义及其基本操作&#xff08;顺序表插入、删除、查找、修改&#xff09; 四、线性表的链接存储结构 1. 单链表 2. 循环链表 3. 双向链表 a. 双向链表节点结构 b. 创建一个新的节点 c. 在链表末尾插入节点 d. 在指定位置插入节点 e. 删除指定位置的节点…...

数据结构和算法——图

图 有向图 带权图 邻接矩阵 邻接表相较于邻接矩阵&#xff0c;减少了存储空间&#xff1b; 邻接表 参考视频&#xff1a;【尚硅谷】数据结构与算法&#xff08;Java数据结构与算法&#xff09;_哔哩哔哩_bilibili...

大数据学习(16)-mapreduce详解

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主哦&#x1f91…...

Android---OkHttp详解

OkHttp 是一套处理 HTTP 网络请求的依赖库&#xff0c;由 Square 公司设计研发并开源&#xff0c;目前可以在 Java 和 Kotlin 中使用。对于 Android App&#xff0c;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数据中心机房建设中的应用

当今社会互联网发展迅速&#xff0c; 随着带宽需求的提升&#xff0c; 网络的保密性、安全性的要求就越来越迫切。PDU(Power Distribution Unit) 是 PDU具备电源分配和管理功能的电源分配管理器。PDU电源插座是多有设备运行的第一道也是最为密切的部件&#xff0c; PDU的好坏直…...

Elasticsearch学习笔记

1.核心概念 bucket: 一个数据分组&#xff08;类似于sql group by以后的数据&#xff09;metric&#xff1a;对bucket执行的某种聚合分析的操作&#xff0c;比如说求平均值&#xff0c;最大值&#xff0c;最小值。一些系列的统计方法(类似 select count(1) MAX MIN AVG) 请…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...