美文网首页
阿里云域名+DDNS API实现动态域名

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

作者: 颏訫 | 来源:发表于2019-06-10 21:47 被阅读0次

    由于家用宽带都是动态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实现动态域名

          本文链接:https://www.haomeiwen.com/subject/zhxxfctx.html