由于家用寬帶都是動(dòng)態(tài)IP,所以想在外面訪問家里的設(shè)備就需要?jiǎng)討B(tài)域名洼裤,像花生殼這類的動(dòng)態(tài)域名要么收費(fèi)要么限制很多,用起來很不爽。下面介紹阿里云域名+DDNS API實(shí)現(xiàn)域名自動(dòng)更新离斩。
實(shí)現(xiàn)條件:
- 要有公網(wǎng)地址,電信用戶打10000瘪匿,聯(lián)通用戶打10010跛梗,移動(dòng)用戶洗洗睡吧,不要想太多
- 買個(gè)阿里云的域名棋弥,找個(gè)便宜的買核偿,我們只需要自己訪問,好記就行顽染,十來塊錢一年的多的是
- 能執(zhí)行python的電腦漾岳、NAS、軟路由器都行粉寞,黑白群暉是肯定沒問題尼荆。
阿里云的API本身不難用,難就難在簽名上唧垦,下面的代碼只要改動(dòng)下面幾個(gè)關(guān)鍵參數(shù)就可以用了耀找。
AccessKeyId = "你的AccessKeyId"
AccessKeySecret = "你的AccessKeySecret"
RR = "你的RR"
DomainName = "你的域名"
AccessKeyId和AccessKeySecret在你的阿里云賬號(hào)里可以直接申請(qǐng),具體操作請(qǐng)找度娘业崖。關(guān)于RR野芒、DomainName和A記錄,剛接觸的人可能有點(diǎn)暈双炕。這里舉個(gè)例子狞悲,你的域名全稱是my.abc.com
,那么RR就是my
,DomainName就是abc.com
,A記錄就是域名對(duì)應(yīng)的IP地址妇斤。
把代碼保存為.py文件摇锋,然后計(jì)劃任務(wù)定時(shí)執(zhí)行,執(zhí)行頻率不要太頻繁站超,設(shè)定10分鐘左右就行荸恕,太頻繁可能會(huì)被服務(wù)器限制。
代碼在python3.8下測試通過
# coding=utf-8
'''
本例全部使用python3自帶庫死相,無需額外安裝第三方庫融求,方便小白使用
'''
import base64 # 編碼
import datetime # 日期時(shí)間
import hmac # 哈希算法
import re # 正則表達(dá)式
import time # 時(shí)間
import urllib.parse # url地址解析
import urllib.request # url請(qǐng)求處理
import xml.dom.minidom # XML處理
from uuid import uuid1 # 通用唯一識(shí)別碼
AccessKeyId = "你的AccessKeyId"
AccessKeySecret = "你的AccessKeySecret"
APIServer = "http://alidns.aliyuncs.com" # API服務(wù)器地址,不需要改動(dòng)
RR = "你的RR" # 對(duì)應(yīng)的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}
# 公共參數(shù)設(shè)置算撮,每個(gè)DDNS API都需要的參數(shù)生宛,不需要改動(dòng)县昂。
def perencode(strs): # 字符編碼函數(shù)
res = urllib.parse.quote(strs.encode("utf_8")) # 對(duì)字符串進(jìn)行 utf-8 編碼,然后進(jìn)行URL格式編碼
res = res.replace("+", "%20").replace("*", "%2A").replace("%7E", "~") # 按簽名要求進(jìn)行替換字符
return res
def signature(p_para, c_para, AccessKeySecret): # 簽名函數(shù)
reqstr = sorted({**p_para, **c_para}.items(), key=lambda x: x[0]) # 按關(guān)鍵字對(duì)合并后的字典進(jìn)行排序
signaturestr = ""
for i in reqstr:
signaturestr = signaturestr + perencode(i[0]) + "=" + perencode(i[1]) + "&"
# 對(duì)排序后的字典進(jìn)行轉(zhuǎn)碼并重新拼接:a=a&b=b&c=c 格式
signaturestr = signaturestr.strip("&") # 去除首尾的 &號(hào)
signaturestr = "GET&%2F&" + perencode(signaturestr)
n = hmac.new((AccessKeySecret + "&").encode("utf-8"), signaturestr.encode("utf-8"), "sha1").digest() # 計(jì)算哈希值
signaturestr = base64.b64encode(n) # base64編碼陷舅,bytes類型
return signaturestr.decode() # 返回解碼后的字符串倒彰,str類型
def urlstr(c_para, p_para, APIServer): # 最終請(qǐng)求地址構(gòu)造函數(shù)
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))
# 對(duì)請(qǐng)求參數(shù)進(jìn)行拼接,形成最終URL地址莱睁,就可以在瀏覽器直接訪問了
return urlstr
def get_ip(): # 獲取當(dāng)前公網(wǎng)IP地址
getip_url = "http://www.net.cn/static/customercare/yourip.asp" # 通過萬網(wǎng)獲取
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): # 取得對(duì)應(yīng)A記錄的IP地址
# DomainName 你想獲取記錄的域名
# RR你想返回域名的哪個(gè)記錄
# 域名結(jié)構(gòu):RR.DomainName待讳,如域名為:www.abc.com ;RR=www,DomainName=abc.com
p_para = {"Action": "DescribeDomainRecords", "DomainName": DomainName}
# DescribeDomainRecords動(dòng)作的私有參數(shù)設(shè)置
url = urlstr(c_para, p_para, APIServer)
try: # 捕獲可能出現(xiàn)的錯(cuò)誤
reqs = urllib.request.Request(url)
resp = urllib.request.urlopen(reqs)
except Exception as e: # 對(duì)應(yīng)錯(cuò)誤的處理方式
return "Error", e.reason # 返回"Error"(Error是自己定義的錯(cuò)誤代碼)仰剿,并返回錯(cuò)誤原因
else: # 沒有錯(cuò)誤執(zhí)行
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): # 更新應(yīng)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], "當(dāng)前IP地址:", myip)
p_para = {"Action": "UpdateDomainRecord", "RR": RR, "RecordId": myrecord[0], "Type": "A", "Value": myip}
# UpdateDomainRecord動(dòng)作的私有參數(shù)設(shè)置
url = urlstr(c_para, p_para, APIServer)
try: # 捕獲可能出現(xiàn)的錯(cuò)誤
reqs = urllib.request.Request(url)
resp = urllib.request.urlopen(reqs)
except Exception as e: # 錯(cuò)誤的處理方式
print("更新地址失敗:", e.reason)
return
else: # 沒有錯(cuò)誤執(zhí)行
print("更新地址成功辩昆!")
return
while update_record(RR, DomainName) == "Error":
time.sleep(5)
update_record(RR, DomainName)
count += 1
if count == 5:
print("更新失敗阅酪,請(qǐng)檢查看錯(cuò)誤信息旨袒!")
break
# 有時(shí)訪問API會(huì)出現(xiàn)無法預(yù)料的錯(cuò)誤造成更新失敗。出現(xiàn)錯(cuò)誤就再次嘗試更新术辐,每次延遲5秒砚尽,5次不成功就退出程序。