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

news2024/12/19 20:59:27

文章目录

  • 一、网站分析
  • 二、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 Logger

class Spider:
    LAST_HASH = ""
    def __init__(self) -> None:
        self.X_ApiKey = False
        self.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 l
        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
        api_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 = True
        if parse.get("code") != 0 or    \
                        parse.get("msg") != "" or   \
                        parse.get("data") is None:
            status = False
        return (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 = idx
                break
        return result[:index]
    
    def write(self, data: list[list]) -> None:
        if len(data) == 0:
            return
        status = 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 = None
            time.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 pysql

class 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 = logger
    
    def 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 = database
        self.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 True
        except 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 threading

try:
    os.mkdir("log")
except:
    pass

class 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'

目录结构

在这里插入图片描述


六、结尾

喜欢的话,点个关注吧~
在这里插入图片描述

原创文章,禁止抄袭!!!!!!!!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1121611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Fiddler抓包VSCode和探索

前言&#xff1a; 最近在使用 VSCode 调试 web 程序时&#xff0c;遇到一些问题&#xff0c;当时不知道如何是好。所以决定抓看来看一看&#xff0c;然后一顿操作猛如虎&#xff0c;成功安装了抓包软件 – Fiddler Classic。我并没有使用 Postman 这种重量级的 HTTP 测试软件&a…

windows系统kettle9.3一键安装启动

程序下载、解压 通过百度网盘下载&#xff0c;直接解压即可 解压之后 双击运行 程序路径 pdi-ce-9.3.0.0-428一键安装启动\pdi-ce-9.3.0.0-428\data-integration

【FA-BP预测】基于萤火虫算法优化BP神经网络回归预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux中常见的权限问题

目录 前言1. 目录权限2. umask3. 粘滞位结语 前言 在了解完上一篇文章 Linux权限的理解与操作 之后&#xff0c;还有一些比较常见的权限问题需要我们去了解。其中包括目录的权限&#xff0c;umask 以及 粘滞位的使用。 1. 目录权限 问题一&#xff1a;进入一个目录&#xff0…

STM32F4_FATFS

目录 前言 1. 文件系统简介 2. FATFS文件系统 2.1 实际演练 2.2 FATFS读书笔记整理 2.3 FAT文件系统的神秘面纱 2.3.1 引导扇区 2.3.2 引导代码 2.3.3 FSINF0信息扇区 2.3.4 FAT表 2.3.5 FAT32数据区 2.3.6 子目录 2.3.7 目录项 3. 实验程序 3.1 main.c 3.2 di…

美团面试:Oracle JDK那么好,为何要用Open JDK?

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 既然 Oracle JDK 这么好&#xff0c;那为什么还要有 OpenJDK&…

YZ系列工具之YZ12:VBA_4种方法设计下拉列表

我给VBA下的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套一部VBA手册&#xff0c;教程分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的…

OJ第五篇

文章目录 用队列实现栈用栈实现队列设计循环队列 用队列实现栈 链接&#xff1a;用队列实现栈 这道题是让我们用两个队列实现一个栈&#xff0c;简单来说&#xff0c;就是利用队列来实现一个先入后出的功能&#xff0c;我们知道队列是先入先出&#xff0c;如何用两个队列来实…

Vue2基础知识(四) 自定义指令

目录 一 自定义指令1.1 定义1.2 自定义局部指令1.3 全局注册指令1.4 钩子函数1.5 动态传参1.6 使用场景 &#x1f48c; 所属专栏&#xff1a;【Vue2】&#x1f600; 作 者&#xff1a;长安不及十里&#x1f4bb;工作&#xff1a;目前从事电力行业开发&#x1f308;目标&#xf…

全国342个城市往返最短通勤时间(铁路)数据

全国342个城市往返最短通勤时间&#xff08;铁路&#xff09;数据 1、时间&#xff1a;采集时间是2022年 2、来源&#xff1a;12306 3、数据说明&#xff1a;数据采集12306数据&#xff0c;整理全国342个城市往返最短通勤时间&#xff0c;本数据是铁路包含动车、高铁所有路线…

【GESP】2023年06月图形化三级 -- 计算最终值

文章目录 计算最终值【题目描述】【输入描述】【输出描述】【参考答案】其他测试用例 计算最终值 【题目描述】 默认小猫角色&#xff0c;白色背景。存在一种仅支持2种操作和1个变量的编程语言&#xff1a; X 使变量 “X” 的值增加1X-- 使变量 “X” 的值减少 1 最初&#…

基于SAE堆叠自编码器的单维时间序列预测研究(matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

在没有康托尔对角化方法的情况下证明实数的不可数性

乔治康托尔 |图片来源&#xff1a; 维基百科 一、说明 对于那些对数学感兴趣的人来说&#xff0c;无穷大实际上可以有不同的大小&#xff0c;这可能是一个众所周知的事实。事实上&#xff0c;最著名的例子是所有实数的集合比所有自然数的集合“大”。你可能知道&#xff0c;这实…

Docker部署SpringBoot +Vue项目流程详解(含域名 + HTTPS)

文章目录 前言1、选购服务器2、部署 Docker3、前端、后端项目打包4、配置 Dockerfile5、配置 Nginx6、配置 DockerCompose6、启动 DockerCompose 完成部署7、【可选】域名 SSL证书 前言 本次整体部署操作使用阿里云服务器&#xff0c;这里我选择的是香港地区的2核2G ECS&…

打击勒索病毒:防御.kat6.l6st6r勒索病毒的最佳策略

导言&#xff1a; 我们日常生活和工作的方方面面都离不开数字化&#xff0c;但这也意味着面临日益复杂的网络威胁。.kat6.l6st6r勒索病毒就是其中之一&#xff0c;如果你的计算机感染了这种恶意软件&#xff0c;你的数据可能会遭到加密并要求支付赎金才能解锁。在这篇终极指南…

STM32F4_音乐播放器

目录 前言 1. WAV简介 1.1 WAVE文件的内部结构 2. WM8978简介 3. I2S简介 4. 硬件设计 5. 实验程序 5.1 main.c 5.2 I2S.c 5.3 I2S.h 5.4 WM8978.c 5.5 WM8978.h 前言 STM32F4开发板拥有全双工I2S&#xff08;也就是可以同时双向进行传输&#xff0c;A到B传输信息的…

强化学习问题(7)--- Python和Pytorch,Tensorflow的版本对应

1.问题 之前下载的python3.8&#xff0c;在对应Pytorch和Tensorflow时没太在意版本&#xff0c;在运行一些代码时&#xff0c;提示Pytorch和Tensorflow版本过高&#xff0c;直接降下来&#xff0c;有时候又和Python3.8不兼容&#xff0c;所以又在虚拟环境搞一个Pyhon3.7&#x…

Brachistochrone:使用变分法找到最快下降曲线

一、说明 对于任何对数学和科学感兴趣的人&#xff0c;您可能已经知道了急速线&#xff0c;因为它经常在各种流行的教学频道&#xff08;例如 Vsauce 和 3Blue1Brown&#xff09;上谈论。虽然有多种方法可以解决急速线问题&#xff0c;但在这篇文章中&#xff0c;本文将使用变分…

【C++】哈希应用——海量数据面试题

哈希应用——海量数据面试题 一、位图应用1、给定100亿个整数&#xff0c;设计算法找到只出现一次的整数&#xff1f;2、给两个文件&#xff0c;分别有100亿个整数&#xff0c;我们只有1G内存&#xff0c;如何找到两个文件交集&#xff1f;&#xff08;1&#xff09;用一个位图…

Mac安装nginx(Homebrew)

查看需要安装 nginx 的信息 brew info nginxDocroot 默认为 /usr/local/var/www 在 /opt/homebrew/etc/nginx/nginx.conf 配置文件中默认端口被配置为8080&#xff0c;从而使 nginx 运行时不需要加 sudo nginx将在 /opt/homebrew//etc/nginx/servers/ 目录中加载所有文件 …