摘要:用 web3.py 8.0.0 在本机连 Sepolia 测试网:HTTPProvider 与超时设置、chain_id 安全断言、读块高/Base Fee/余额/nonce 并到 Sepolia Etherscan 逐项核对;再用 Account.create() 生成一次性测试账户做离线签名,附失效 RPC、非校验和地址、连错测试网三个常见坑。

前两篇用 JavaScript 的 ethers.js 连上了测试网(Provider 与读余额、读合约 ABI)。如果你的脚本、数据分析或后端是 Python,对应的官方库是 web3.py。它和 ethers 做的是同一件事——通过 JSON-RPC 跟节点说话——但对象名、单位换算和地址校验的习惯不太一样。本文在本机(Python 3.13 虚拟环境,pip 安装到的 web3.py 版本为 8.0.0)真实跑通三段脚本:用 HTTPProvider 连 Sepolia、读块和余额,再生成一个一次性测试账户做离线签名,全程不需要任何真私钥、不发交易、不花 Gas。

准备:虚拟环境与公共 RPC

官方 Quickstart 建议在 virtualenv 里安装。下面三行就是本文用到的全部环境命令:

python3 -m venv .venv
.venv/bin/pip install web3
.venv/bin/python -c "import web3; print(web3.__version__)"   # 本机输出 8.0.0

RPC 节点用 PublicNode 的公共 Sepolia 端点 https://ethereum-sepolia-rpc.publicnode.com。写作当天(2026-10-06)在本机实测:它对 eth_chainId 正常返回 0xaa36a7(即 11155111);老的 rpc.sepolia.org 已返回 404,sepolia.drpc.org 免费档提示不提供该链。公共节点随时可能限流或下线,正式项目建议用自己在节点服务商申请的端点,并准备备用 URL。

HTTPProvider 是什么:官方文档怎么说

web3.py 把「连接节点的方式」叫 Provider。官方 Providers 文档 里的 HTTPProvider 签名是 HTTPProvider(endpoint_uri, request_kwargs={}, session=None, exception_retry_configuration=...):endpoint_uri 填完整的 RPC 地址;request_kwargs 会原样传给底层 requests 库,最常见的用途就是加 timeout;文档还提醒同一个 URL 在一个 Python 进程里只建一个 HTTPProvider,它会复用 TCP 连接。

web3.py 连 Sepolia:HTTPProvider、读余额与本地账户-程序旅途
图1:web3py.readthedocs.io/en/stable/providers.html:HTTPProvider 的 endpoint_uri、request_kwargs、session、exception_retry_configuration 参数说明与 timeout 示例;界面为英文

除了 HTTP,文档还列了 IPC(本机节点最安全)、WebSocket(需要订阅事件时用,走 AsyncWeb3)。入门读数据,HTTPProvider 足够。

第一段代码:连上 Sepolia,读块高、块字段与余额

新建 connect.py。关键是第一时间用 chain_id 断言自己连的确实是 Sepolia——这是后面所有操作的安全闸。

from datetime import datetime, timezone
import web3
from web3 import Web3

RPC = "https://ethereum-sepolia-rpc.publicnode.com"   # 公共 Sepolia 节点
w3 = Web3(Web3.HTTPProvider(RPC, request_kwargs={"timeout": 15}))

print("web3.py:", web3.__version__)
print("is_connected:", w3.is_connected())

chain_id = w3.eth.chain_id
print("chain_id:", chain_id)
assert chain_id == 11155111, "连到的不是 Sepolia,停止"

n = w3.eth.block_number
blk = w3.eth.get_block(n)
print("block_number:", n)
print("block.hash:", blk["hash"].to_0x_hex())
print("block.time (UTC):", datetime.fromtimestamp(blk["timestamp"], timezone.utc).isoformat())
print("block.txs:", len(blk["transactions"]))
print("block.gasUsed/gasLimit:", blk["gasUsed"], "/", blk["gasLimit"])
print("block.baseFeePerGas (gwei):", Web3.from_wei(blk["baseFeePerGas"], "gwei"))
print("gas_price (gwei):", Web3.from_wei(w3.eth.gas_price, "gwei"))

addr = Web3.to_checksum_address("0xd8da6bf26964af9d7eed9e03e53415d37aa96045")
wei = w3.eth.get_balance(addr)
print("address:", addr)
print("balance (wei):", wei)
print("balance (ETH):", Web3.from_wei(wei, "ether"))
print("nonce:", w3.eth.get_transaction_count(addr))

运行 python connect.py,下图是本机真实终端输出(2026-10-06 下午运行,块高、Gas 等数值随时变化):

web3.py 连 Sepolia:HTTPProvider、读余额与本地账户-程序旅途
图2:python connect.py:is_connected True、chain_id 11155111、block_number 11854870、baseFeePerGas、gas_price,以及示例地址余额与 nonce
输出项 web3.py 写法 说明
is_connected w3.is_connected() 只告诉你能不能连通,连不上时返回 False 而不是抛异常(见下文坑 1)
chain_id w3.eth.chain_id Sepolia 为 11155111;不对就立刻停止
block_number / get_block w3.eth.get_block(n) 返回 AttributeDict,hash 是 HexBytes,需 .to_0x_hex() 才是带 0x 的字符串
baseFeePerGas / gas_price Web3.from_wei(x, "gwei") 链上返回的都是 wei 整数,换算交给 from_wei,别手写除法
get_balance w3.eth.get_balance(addr) 单位 wei;地址必须是校验和格式
get_transaction_count w3.eth.get_transaction_count(addr) 就是该地址的 nonce,下一笔交易要用

Gas 字段的含义(Base Fee、Priority Fee)在 以太坊 Gas 费怎么算:EIP-1559 字段对照 里有详细拆解,这里只要知道 web3.py 读到的数和浏览器是同一份链上数据。

到 Sepolia Etherscan 逐项核对

把脚本打印的块高 11854870 贴进 Sepolia Etherscan 区块页:页面顶部红字标明「This is a Sepolia Testnet block only」;Timestamp 为 Oct-06-2026 08:59:00 AM UTC、117 笔交易、Gas Used 14,022,681、Gas Limit 60,000,000、Base Fee Per Gas 1.107927715 Gwei,和终端输出逐项一致。

web3.py 连 Sepolia:HTTPProvider、读余额与本地账户-程序旅途
图3:sepolia.etherscan.io/block/11854870:Sepolia Testnet 标识、时间戳、117 笔交易、Gas Used/Limit、Base Fee Per Gas 1.107927715 Gwei;界面为英文

再打开示例地址的 Sepolia 地址页:ETH Balance 61.060896814128264732 ETH,与 balance (ETH) 一致;More Info 里 Transactions Sent 显示 N/A,对应脚本里 nonce 为 0——这个地址在 Sepolia 上只收过币、从没发出过交易。

web3.py 连 Sepolia:HTTPProvider、读余额与本地账户-程序旅途
图4:sepolia.etherscan.io/address/0xd8dA…6045:ETH Balance 61.0608… ETH、Transactions Sent N/A(nonce 0);测试网余额无真实价值,数值会变化;界面为英文

nonce 为什么重要、卡住的交易怎么看,可以接着读 以太坊 Nonce 是什么:卡住交易、加速与取消。

第二段代码:账户——本地生成,节点不替你保管私钥

web3.py 官方 Accounts 文档 区分了两种私钥:Local Private Key 由你的程序在本地保存和签名;Hosted Private Key 是节点替你保管、通过 w3.eth.accounts 列出的账户,文档称这是配合 EthereumTesterProvider、Anvil 这类单元测试后端使用的旧方式,早年在本地节点里托管私钥的做法如今已不推荐。连公共 RPC 时,你永远应该用本地账户。

from eth_account import Account
from eth_account.messages import encode_defunct
from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://ethereum-sepolia-rpc.publicnode.com"))
print("node accounts (w3.eth.accounts):", w3.eth.accounts)

acct = Account.create()          # 随机生成一次性测试账户,只存在内存里
key = acct.key.to_0x_hex()
print("new address:", acct.address)
print("private key:", key[:6] + "..." + key[-4:], f"({len(acct.key)} bytes, 已打码)")
print("is_checksum_address:", Web3.is_checksum_address(acct.address))
print("balance on Sepolia:", w3.eth.get_balance(acct.address))
print("nonce on Sepolia:", w3.eth.get_transaction_count(acct.address))

msg = encode_defunct(text="chengxulvtu web3.py sepolia demo")
signed = acct.sign_message(msg)  # 离线签名,不上链、不花 Gas
print("signature:", signed.signature.to_0x_hex()[:20] + "...", f"({len(signed.signature)} bytes)")
rec = Account.recover_message(msg, signature=signed.signature)
print("recovered:", rec)
print("recovered == address:", rec == acct.address)

这段代码做了四件事:看公共节点有没有托管账户;用 Account.create() 随机生成一个只存在内存里的一次性测试账户(私钥打码,只打印前后几位);查它在 Sepolia 的余额和 nonce;最后对一句话做 EIP-191 离线签名,再用 recover_message 从签名反推出地址。

第三段代码:三个最常见的坑

from web3 import Web3

# 坑 1:RPC 地址失效 / 写错 —— is_connected() 只返回 False,不抛异常
bad = Web3(Web3.HTTPProvider("https://rpc.sepolia.org", request_kwargs={"timeout": 10}))
print("rpc.sepolia.org is_connected:", bad.is_connected())

w3 = Web3(Web3.HTTPProvider("https://ethereum-sepolia-rpc.publicnode.com"))

# 坑 2:全小写地址直接传给 get_balance
try:
    w3.eth.get_balance("0xd8da6bf26964af9d7eed9e03e53415d37aa96045")
except Exception as e:
    print("lowercase address ->", type(e).__name__ + ":", str(e)[:90])
print("to_checksum_address ->", Web3.to_checksum_address("0xd8da6bf26964af9d7eed9e03e53415d37aa96045"))

# 坑 3:连错测试网 —— 用 chain_id 做保护
hoodi = Web3(Web3.HTTPProvider("https://ethereum-hoodi-rpc.publicnode.com"))
cid = hoodi.eth.chain_id
print("hoodi chain_id:", cid, "| expected Sepolia 11155111 ->", "OK" if cid == 11155111 else "MISMATCH, stop")

两段脚本的本机真实输出如下:

web3.py 连 Sepolia:HTTPProvider、读余额与本地账户-程序旅途
图5:python account.py:w3.eth.accounts 为 []、新地址余额与 nonce 为 0、65 字节签名可恢复出同一地址;python pitfall.py:失效 RPC 返回 False、小写地址触发 InvalidAddress、Hoodi chain_id 560048 被拦下

逐条解读:

  • w3.eth.accounts 为 []:公共节点不托管任何私钥。网上一些老教程写 w3.eth.send_transaction({'from': w3.eth.accounts[0], ...}),那只在本地测试链上成立。
  • 新账户余额 0、nonce 0:地址可以离线算出来,链上「不需要注册」;要发交易得先往里打测试 ETH(水龙头),这一步留到下一篇写交易时再做。
  • 签名 65 字节且能恢复出同一地址:签名只证明「这个私钥的持有者签过这句话」,不上链、不花 Gas。DApp 的「签名登录」就是这个原理——也正因如此,陌生网站让你签一段看不懂的数据时要格外小心。
  • 坑 1:is_connected() 返回 False 不报错:RPC 地址写错或失效时(本例 rpc.sepolia.org 已 404),脚本不会自己停下,必须你检查返回值,否则后面调用才报一堆网络异常。
  • 坑 2:InvalidAddress:web3.py 只接受 EIP-55 校验和地址,全小写地址直接被拒(ethers 对全小写更宽松)。从数据库或接口拿到的地址,先过一遍 Web3.to_checksum_address()。
  • 坑 3:连错网络:Hoodi 也是以太坊测试网,chain_id 为 560048。如果 RPC 填成了它,余额、合约地址都会对不上;用 chain_id 断言提前拦住,比事后排查省事得多。

web3.py 和 ethers.js 对照

做什么 web3.py ethers.js v6
连节点 Web3(Web3.HTTPProvider(url)) new JsonRpcProvider(url)
链 ID w3.eth.chain_id (await provider.getNetwork()).chainId
余额 w3.eth.get_balance(addr) await provider.getBalance(addr)
单位换算 Web3.from_wei(x, "ether") formatEther(x)
地址校验 Web3.to_checksum_address(),非校验和直接拒绝 getAddress(),全小写可接受
本地账户 Account.create() Wallet.createRandom()

自查清单

  1. RPC 来自可信来源,脚本启动先 is_connected(),再断言 chain_id。
  2. 给 HTTPProvider 设 request_kwargs={"timeout": ...},并准备备用 RPC。
  3. 地址一律 to_checksum_address(),金额一律 from_wei / to_wei。
  4. 测试私钥只放内存或环境变量,不写进代码、不提交仓库、不打印全文;绝不把主网钱包私钥拿来调试。
  5. 读到的块与余额,到对应网络的浏览器(Sepolia 用 sepolia.etherscan.io)核对一次。

真正上主网交互时,Gas 要用 ETH 支付,可以在 币安 或 欧易 准备少量 ETH;日常用的自托管钱包可以选 欧易 Web3 钱包(创建时可填推荐码 CHENGXULVTU)。开发调试始终用和资产钱包完全分开的测试账户。

相关阅读

说明:文中代码只连 Sepolia/Hoodi 测试网公共 RPC,只做只读查询和离线签名;测试账户为运行时随机生成、用后即弃,私钥未保存也未展示全文。块高、Gas、余额为 2026-10-06 运行与截图时的测试网数据,会持续变化,测试网 ETH 没有任何价值。接口以 web3.py 官方文档为准。本文只讲技术方法,不构成任何投资建议。