由于家用宽带都是动态IP,所以想在外面访问家里的设备就需要动态域名,像花生壳这类的动态域名要么收费要么限制很多,用起来很不爽。下面介绍阿里云域名+DDNS API实现域名自动更新。
实现条件:
- 要有公网地址,电信用户打10000,联通用户打10010,移动用户洗洗睡吧,不要想太多
- 买个阿里云的域名,找个便宜的买,我们只需要自己访问,好记就行,十来块钱一年的多的是
- 能执行python的电脑、NAS、软路由器都行,黑白群晖是肯定没问题。
阿里云的API本身不难用,难就难在签名上,下面的代码只要改动下面几个关键参数就可以用了。
AccessKeyId = "你的AccessKeyId"
AccessKeySecret = "你的AccessKeySecret"
RR = "你的RR"
DomainName = "你的域名"
AccessKeyId和AccessKeySecret在你的阿里云账号里可以直接申请,具体操作请找度娘。关于RR、DomainName和A记录,刚接触的人可能有点晕。这里举个例子,你的域名全称是my.abc.com
,那么RR就是my
,DomainName就是abc.com
,A记录就是域名对应的IP地址。
把代码保存为.py文件,然后计划任务定时执行,执行频率不要太频繁,设定10分钟左右就行,太频繁可能会被服务器限制。
代码在python3.8下测试通过
# coding=utf-8
'''
本例全部使用python3自带库,无需额外安装第三方库,方便小白使用
'''
import base64 # 编码
import datetime # 日期时间
import hmac # 哈希算法
import re # 正则表达式
import time # 时间
import urllib.parse # url地址解析
import urllib.request # url请求处理
import xml.dom.minidom # XML处理
from uuid import uuid1 # 通用唯一识别码
AccessKeyId = "你的AccessKeyId"
AccessKeySecret = "你的AccessKeySecret"
APIServer = "http://alidns.aliyuncs.com" # API服务器地址,不需要改动
RR = "你的RR" # 对应的A记录
DomainName = "你的域名" # 你的域名
count = 0
c_para = {"Format": "XML", "Version": "2015-01-09", "SignatureMethod": "HMAC-SHA1",
"Timestamp": datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'), "SignatureVersion": "1.0",
"SignatureNonce": str(uuid1()), "AccessKeyId": AccessKeyId}
# 公共参数设置,每个DDNS API都需要的参数,不需要改动。
def perencode(strs): # 字符编码函数
res = urllib.parse.quote(strs.encode("utf_8")) # 对字符串进行 utf-8 编码,然后进行URL格式编码
res = res.replace("+", "%20").replace("*", "%2A").replace("%7E", "~") # 按签名要求进行替换字符
return res
def signature(p_para, c_para, AccessKeySecret): # 签名函数
reqstr = sorted({**p_para, **c_para}.items(), key=lambda x: x[0]) # 按关键字对合并后的字典进行排序
signaturestr = ""
for i in reqstr:
signaturestr = signaturestr + perencode(i[0]) + "=" + perencode(i[1]) + "&"
# 对排序后的字典进行转码并重新拼接:a=a&b=b&c=c 格式
signaturestr = signaturestr.strip("&") # 去除首尾的 &号
signaturestr = "GET&%2F&" + perencode(signaturestr)
n = hmac.new((AccessKeySecret + "&").encode("utf-8"), signaturestr.encode("utf-8"), "sha1").digest() # 计算哈希值
signaturestr = base64.b64encode(n) # base64编码,bytes类型
return signaturestr.decode() # 返回解码后的字符串,str类型
def urlstr(c_para, p_para, APIServer): # 最终请求地址构造函数
dic = {**p_para, **c_para}
urlstr = ""
for i in dic.items():
urlstr = urlstr + i[0] + "=" + i[1] + "&"
urlstr = APIServer + "?" + urlstr + "Signature=" + str(signature(p_para, c_para, AccessKeySecret))
# 对请求参数进行拼接,形成最终URL地址,就可以在浏览器直接访问了
return urlstr
def get_ip(): # 获取当前公网IP地址
getip_url = "http://www.net.cn/static/customercare/yourip.asp" # 通过万网获取
reqs = urllib.request.Request(getip_url)
resp = urllib.request.urlopen(reqs)
data = resp.read()
ip = re.findall(r"\d+\.+\d+\.+\d+\.+\d+", data.decode("utf-8", "ignore"))
return ip
def get_record(RR, DomainName): # 取得对应A记录的IP地址
# DomainName 你想获取记录的域名
# RR你想返回域名的哪个记录
# 域名结构:RR.DomainName,如域名为:www.abc.com ;RR=www,DomainName=abc.com
p_para = {"Action": "DescribeDomainRecords", "DomainName": DomainName}
# DescribeDomainRecords动作的私有参数设置
url = urlstr(c_para, p_para, APIServer)
try: # 捕获可能出现的错误
reqs = urllib.request.Request(url)
resp = urllib.request.urlopen(reqs)
except Exception as e: # 对应错误的处理方式
return "Error", e.reason # 返回"Error"(Error是自己定义的错误代码),并返回错误原因
else: # 没有错误执行
data = resp.read().decode('utf-8')
xmldoc = xml.dom.minidom.parseString(data)
rootNode = xmldoc.documentElement
if rootNode.nodeName == "Error":
return "Error", data
DomainRecords = rootNode.getElementsByTagName('DomainRecords')
Record = DomainRecords[0].getElementsByTagName("Record")
RecordId = Record[0].getElementsByTagName("RecordId")[0].childNodes[0].nodeValue
IP = Record[0].getElementsByTagName("Value")[0].childNodes[0].nodeValue
return RecordId, IP
def update_record(RR, DomainName): # 更新应A记录的IP地址
myrecord = get_record(RR, DomainName)
if myrecord[0] == "Error":
print("获取域名信息失败:", myrecord[1])
return "Error"
myip = get_ip()
if myip == myrecord[1]:
print("IP地址相同,无需更新。")
else:
print("A记录地址:", myrecord[1], "当前IP地址:", myip)
p_para = {"Action": "UpdateDomainRecord", "RR": RR, "RecordId": myrecord[0], "Type": "A", "Value": myip}
# UpdateDomainRecord动作的私有参数设置
url = urlstr(c_para, p_para, APIServer)
try: # 捕获可能出现的错误
reqs = urllib.request.Request(url)
resp = urllib.request.urlopen(reqs)
except Exception as e: # 错误的处理方式
print("更新地址失败:", e.reason)
return
else: # 没有错误执行
print("更新地址成功!")
return
while update_record(RR, DomainName) == "Error":
time.sleep(5)
update_record(RR, DomainName)
count += 1
if count == 5:
print("更新失败,请检查看错误信息!")
break
# 有时访问API会出现无法预料的错误造成更新失败。出现错误就再次尝试更新,每次延迟5秒,5次不成功就退出程序。