【网络爬虫 | Python】数字货币ok链上bitcoin大额交易实时爬取,存入 mysql 数据库

本文主要是介绍【网络爬虫 | 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 数据库的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/263577

相关文章

Python基础语法中defaultdict的使用小结

《Python基础语法中defaultdict的使用小结》Python的defaultdict是collections模块中提供的一种特殊的字典类型,它与普通的字典(dict)有着相似的功能,本文主要... 目录示例1示例2python的defaultdict是collections模块中提供的一种特殊的字

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

基于Python实现高效PPT转图片工具

《基于Python实现高效PPT转图片工具》在日常工作中,PPT是我们常用的演示工具,但有时候我们需要将PPT的内容提取为图片格式以便于展示或保存,所以本文将用Python实现PPT转PNG工具,希望... 目录1. 概述2. 功能使用2.1 安装依赖2.2 使用步骤2.3 代码实现2.4 GUI界面3.效

MySQL更新某个字段拼接固定字符串的实现

《MySQL更新某个字段拼接固定字符串的实现》在MySQL中,我们经常需要对数据库中的某个字段进行更新操作,本文就来介绍一下MySQL更新某个字段拼接固定字符串的实现,感兴趣的可以了解一下... 目录1. 查看字段当前值2. 更新字段拼接固定字符串3. 验证更新结果mysql更新某个字段拼接固定字符串 -

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

python连接本地SQL server详细图文教程

《python连接本地SQLserver详细图文教程》在数据分析领域,经常需要从数据库中获取数据进行分析和处理,下面:本文主要介绍python连接本地SQLserver的相关资料,文中通过代码... 目录一.设置本地账号1.新建用户2.开启双重验证3,开启TCP/IP本地服务二js.python连接实例1.

Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能

《SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能》:本文主要介绍SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的... 目录原理解析1. mysql主从复制(Master-Slave Replication)2. 读写分离3.

基于Python和MoviePy实现照片管理和视频合成工具

《基于Python和MoviePy实现照片管理和视频合成工具》在这篇博客中,我们将详细剖析一个基于Python的图形界面应用程序,该程序使用wxPython构建用户界面,并结合MoviePy、Pill... 目录引言项目概述代码结构分析1. 导入和依赖2. 主类:PhotoManager初始化方法:__in

Python从零打造高安全密码管理器

《Python从零打造高安全密码管理器》在数字化时代,每人平均需要管理近百个账号密码,本文将带大家深入剖析一个基于Python的高安全性密码管理器实现方案,感兴趣的小伙伴可以参考一下... 目录一、前言:为什么我们需要专属密码管理器二、系统架构设计2.1 安全加密体系2.2 密码强度策略三、核心功能实现详解

Python Faker库基本用法详解

《PythonFaker库基本用法详解》Faker是一个非常强大的库,适用于生成各种类型的伪随机数据,可以帮助开发者在测试、数据生成、或其他需要随机数据的场景中提高效率,本文给大家介绍PythonF... 目录安装基本用法主要功能示例代码语言和地区生成多条假数据自定义字段小结Faker 是一个 python