阿里云域名+DDNS API实现动态域名

由于家用宽带都是动态IP,所以想在外面访问家里的设备就需要动态域名,像花生壳这类的动态域名要么收费要么限制很多,用起来很不爽。下面介绍阿里云域名+DDNS API实现域名自动更新。

实现条件:

  1. 要有公网地址,电信用户打10000,联通用户打10010,移动用户洗洗睡吧,不要想太多
  2. 买个阿里云的域名,找个便宜的买,我们只需要自己访问,好记就行,十来块钱一年的多的是
  3. 能执行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次不成功就退出程序。

你可能感兴趣的:(阿里云域名+DDNS API实现动态域名)