3、区块数据解析:理解区块结构,用Web3.py抓取最新数据
说实话,刚接触链上数据时,我也觉得区块就是个黑盒子。
直到有一次做回测,发现某笔交易的时间戳对不上——嗯,那才逼着我老老实实把区块结构啃了一遍。今天咱们就把这个「黑盒子」拆开看看。
3.1 区块到底长什么样?
一个区块,说白了就是「区块头 + 交易列表」的组合体。我习惯把区块头想象成快递包裹的运单信息,交易列表就是包裹里的商品明细。
核心要点:区块头只有80字节,却决定了整个区块的合法性。交易列表才是数据量的主要来源。
咱们先看区块头的结构,我用一张图帮你理清思路:
3.2 区块头里藏着什么秘密?
每个字段都有它的使命。我挑几个重点说说:
| 字段 | 大小 | 作用 |
|---|---|---|
| 版本号 | 4字节 | 标识区块协议版本,目前主流是1或2 |
| 前一个区块哈希 | 32字节 | 指向父区块,形成链式结构。这就是「区块链」名字的由来 |
| Merkle根 | 32字节 | 所有交易的哈希树根,用于快速验证交易是否在区块内 |
| 时间戳 | 4字节 | Unix时间戳,精确到秒。注意:它可能比实际时间早几分钟 |
| 难度目标 | 4字节 | 挖矿难度压缩表示,决定了Nonce需要满足的条件 |
| Nonce | 4字节 | 矿工不断调整这个值,直到区块哈希满足难度要求 |
💡 我的经验:做回测时,时间戳字段最容易踩坑。区块时间戳允许有2小时的前后偏差,所以别拿它当精确时间用。我一般用区块号来排序,时间戳只做参考。
3.3 交易结构:不只是「谁转给谁」
一笔交易的结构,比你想象的要复杂。咱们用Web3.py抓一笔看看:
from web3 import Web3
# 连接节点(我用的是Infura,你也可以用本地节点)
w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/YOUR_PROJECT_ID'))
# 获取最新区块
latest_block = w3.eth.get_block('latest')
print(f"区块高度: {latest_block['number']}")
print(f"交易数量: {len(latest_block['transactions'])}")
# 获取区块内第一笔交易的详情
tx_hash = latest_block['transactions'][0]
tx = w3.eth.get_transaction(tx_hash)
print(f"交易哈希: {tx['hash'].hex()}")
print(f"发送方: {tx['from']}")
print(f"接收方: {tx['to']}")
print(f"金额: {w3.from_wei(tx['value'], 'ether')} ETH")
print(f"Gas价格: {tx['gasPrice']} wei")
print(f"Nonce: {tx['nonce']}")
运行这段代码,你会看到类似这样的输出:
区块高度: 19283746
交易数量: 234
交易哈希: 0xabc...def
发送方: 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18
接收方: 0x123...456
金额: 0.5 ETH
Gas价格: 25000000000 wei
Nonce: 42
⚠️ 注意:get_block('latest') 获取的是最新区块,但节点可能还没完全同步。我建议用 get_block('pending') 获取待处理区块,或者指定具体区块号。
3.4 批量获取:别一次只抓一个
做量化回测时,你肯定需要连续区块的数据。一个一个抓太慢了。我习惯这么干:
def get_blocks_range(start_block, end_block):
"""批量获取区块数据"""
blocks = []
for block_num in range(start_block, end_block + 1):
block = w3.eth.get_block(block_num, full_transactions=True)
blocks.append(block)
# 加个进度提示,免得等得心慌
if (block_num - start_block) % 100 == 0:
print(f"已获取 {block_num - start_block + 1} 个区块...")
return blocks
# 获取最近100个区块
blocks_data = get_blocks_range(19283700, 19283799)
print(f"共获取 {len(blocks_data)} 个区块")
不过要注意,full_transactions=True 会返回完整的交易数据,数据量很大。如果你只需要交易哈希,用 full_transactions=False(默认值)就行。
💡 性能优化:我曾经一次性抓了1000个区块,结果内存爆了。后来改用生成器,边抓边处理:
def block_generator(start, end):
for num in range(start, end + 1):
yield w3.eth.get_block(num, full_transactions=True)
for block in block_generator(19283700, 19283799):
# 处理每个区块
process_block(block)
3.5 解析交易:从原始数据到有用信息
原始交易数据是十六进制字符串,得解析成我们能用的格式。这里有个实用函数:
def parse_transaction(tx):
"""解析交易数据,提取关键字段"""
return {
'hash': tx['hash'].hex(),
'from': tx['from'],
'to': tx['to'],
'value_eth': w3.from_wei(tx['value'], 'ether'),
'gas_used': tx['gas'],
'gas_price_gwei': w3.from_wei(tx['gasPrice'], 'gwei'),
'nonce': tx['nonce'],
'input_data': tx['input'][:50] + '...' if len(tx['input']) > 50 else tx['input']
}
# 示例用法
tx = w3.eth.get_transaction('0xabc...def')
parsed = parse_transaction(tx)
print(f"转账金额: {parsed['value_eth']} ETH")
print(f"Gas费用: {parsed['gas_price_gwei']} Gwei")
你可能会问:为什么要把 input_data 截断?因为智能合约调用时,input 可能长达几千字节,全打印出来太占地方。做分析时,我们通常只关心前几个字节——那是函数选择器。
3.6 实战:构建区块分析流水线
好了,理论说完了,咱们来点实际的。下面是一个完整的区块分析脚本:
from web3 import Web3
from datetime import datetime
class BlockAnalyzer:
def __init__(self, provider_url):
self.w3 = Web3(Web3.HTTPProvider(provider_url))
def analyze_block(self, block_number):
"""分析单个区块"""
block = self.w3.eth.get_block(block_number, full_transactions=True)
# 区块基本信息
info = {
'number': block['number'],
'timestamp': datetime.fromtimestamp(block['timestamp']),
'tx_count': len(block['transactions']),
'size_kb': block['size'] / 1024,
'miner': block['miner'],
'difficulty': block['difficulty']
}
# 交易统计
total_value = 0
gas_prices = []
for tx in block['transactions']:
total_value += tx['value']
gas_prices.append(tx['gasPrice'])
info['total_value_eth'] = self.w3.from_wei(total_value, 'ether')
info['avg_gas_price_gwei'] = self.w3.from_wei(
sum(gas_prices) / len(gas_prices), 'gwei'
) if gas_prices else 0
return info
def analyze_recent_blocks(self, count=10):
"""分析最近N个区块"""
latest = self.w3.eth.block_number
results = []
for i in range(count):
block_num = latest - i
info = self.analyze_block(block_num)
results.append(info)
print(f"区块 {block_num}: {info['tx_count']} 笔交易, "
f"总价值 {info['total_value_eth']:.2f} ETH")
return results
# 使用示例
analyzer = BlockAnalyzer('https://mainnet.infura.io/v3/YOUR_PROJECT_ID')
stats = analyzer.analyze_recent_blocks(5)
核心收获:通过这个分析器,你能快速了解链上活跃度、Gas价格趋势、大额转账情况。这些数据对量化策略的入场时机判断很有帮助。
3.7 避坑指南
最后分享几个我踩过的坑:
- 节点同步延迟:用Infura等公共节点时,最新区块可能延迟几秒到几分钟。做实时策略时,建议用WebSocket订阅新块事件,而不是轮询。
- Gas价格单位:Web3.py返回的gasPrice单位是wei,记得转成gwei或eth再分析。1 gwei = 10^9 wei。
- 空区块:有些区块可能没有交易(只有Coinbase交易)。处理时记得判断交易列表是否为空。
- 数据量爆炸:全量交易数据下载很快会撑爆内存。我建议只保存你策略需要的字段,比如只存转账金额大于1 ETH的交易。
嗯,区块解析这块就聊到这儿。记住:理解数据结构是量化分析的地基,地基不稳,上层策略再花哨也白搭。动手试试上面的代码,把最新区块的数据抓下来看看,你会对链上世界有更直观的感受。
公众号:蓝海资料掘金营,微信deep3321