不能直接用 socket.gethostbyname(socket.gethostname()) 获取公网 IP,因其返回本地或内网地址;需调用外部 HTTP 接口如 https://www.php.cn/link/658bdae888193156bd7d5c18ce3bd6d4 反查,再通过云厂商 API(如 Cloudflare)更新 DNS 记录,并比对缓存 IP 防重复更新。

为什么不能直接用 socket.gethostbyname(socket.gethostname())
这个调用返回的几乎总是 127.0.0.1 或局域网地址(如 192.168.x.x),因为本机 hostname 解析默认指向本地或内网。NAT 环境下,路由器才掌握真实公网 IP,本机无从得知——必须向外部服务发起 HTTP 请求来“反查”。
推荐用 requests 调用轻量级公网 IP 接口
选接口的关键是:响应快、无认证、纯文本或 JSON、不带 HTML 包装。实测稳定可用的有:
-
https://www.php.cn/link/658bdae888193156bd7d5c18ce3bd6d4(纯文本,最快,但仅 IPv4) -
https://api64.ipify.org(纯文本,IPv6) -
https://httpbin.org/ip(JSON,含{"origin": "x.x.x.x"},IPv4/IPv6 双栈)
示例代码(带超时和异常兜底):
import requests
<p>def get_public_ip():
try:
resp = requests.get("<a href="https://www.php.cn/link/658bdae888193156bd7d5c18ce3bd6d4">https://www.php.cn/link/658bdae888193156bd7d5c18ce3bd6d4</a>", timeout=5)
resp.raise_for_status()
ip = resp.text.strip()
if not ip or ":" in ip: # api.ipify.org 不返回 IPv6,出现冒号说明异常
raise ValueError("invalid IPv4 response")
return ip
except (requests.RequestException, ValueError) as e:
print(f"failed to fetch IP: {e}")
return None
用 dnspython 更新 DNS 记录前先确认 API 权限方式
域名动态解析 ≠ 本地 DNS 查询,而是调用域名注册商或 DNS 服务商(如 Cloudflare、DNSPod、阿里云)的 API 修改 A 记录。关键点:
立即学习“Python免费学习笔记(深入)”;
- 没有通用“Python 自动改域名”的库,
dnspython只能做查询或构造包,**不能直接更新主流厂商记录** - Cloudflare 用
requests调PUT /zones/{zone_id}/dns_records/{id},需 Bearer Token - 阿里云用
aliyun-python-sdk-alidns,需 AccessKey + 签名 - 为免硬编码密钥,把
API_TOKEN、RECORD_ID、DOMAIN放进环境变量或.env
Cloudflare 简化更新示例(需提前用 GET 查出 record_id):
import os
import requests
<p>def update_cloudflare_record(ip):
headers = {
"Authorization": f"Bearer {os.getenv('CF_API_TOKEN')}",
"Content-Type": "application/json"
}
data = {"type": "A", "name": "@", "content": ip, "ttl": 120}
resp = requests.put(
f"<a href="https://www.php.cn/link/94cc372d721433c5dcb08a5c499432cb">https://api.cloudflare.com/client/v4/zones/{os.getenv('CF_ZONE_ID')}/dns_records/{os.getenv('CF_RECORD_ID</a>')}",
json=data, headers=headers
)
return resp.status_code == 200
定时执行要防重复更新和 DNS 缓存干扰
每次 IP 不变也发请求,既浪费又可能触发厂商频率限制。必须加比对逻辑:
- 启动时读取上一次写入的 IP(例如存在
/tmp/latest_ip.txt或 SQLite) - 只在新旧 IP 不同时才调用 DNS 更新接口
- 更新成功后立即落盘新 IP,避免断电丢失状态
- 注意 TTL 设置:设太小(如 60)会导致大量 DNS 查询;设太大(如 86400)则切换延迟高,建议 120–300
真正容易被忽略的是:运营商分配的公网 IP 可能数天甚至数周不变,但一旦变更,你的脚本得在 5 分钟内完成探测+更新+生效,而 DNS 全网生效还依赖递归服务器缓存刷新——所以别只盯着脚本跑没跑,要盯 dig @8.8.8.8 example.com A 的结果是否已变。


















