关于阿里域名解析,如何使用Python实现DDNS

闲话休提,我们先上拓扑:

Home

嗯~ o( ̄▽ ̄)o,画图是在线画的,地址分享一下,表现出我的厚道:

https://www.draw.io/

OK,分析一下我们需求:
1.我们需要一个公网IP地址,那么我的无线路由器需要PPPoE拨号
2.域名,目前国内买域名是越来越简单了,因为大厂各种收购,所以就在阿里买了个域名,10年79元很划算吧,自己用所以没买com啊net啊这种。
3.这个时候发现,PPPoE拨号上去的地址,有时候会变,那么我们就需要DDNS的功能,当然使用花生壳的一些域名或者群晖的域名,也是可以在路由器或NAS上完成DDNS功能的,可惜了你用的自己买的域名就需要自己搞一个程序给你定时去完成该功能,这也是为什么本次分享的意义。或许有更优解。请教我谢谢~~~~

第一个需求:

我们只需要购买电信的宽带,在师傅上门安装的时候,跟他说我要桥接,路由器PPPoE获取地址。 嗯~ o( ̄▽ ̄)o就可以了,他就会找后台小姐姐帮你去改模式。他要是不愿意,嗯,投诉他就行了。
看下面图,外网IP这边可以相应的获取到一个公网的IP,OK,完成第一个问题:

公网IP

第二个需求:

https://www.aliyun.com/

这部分就不多说了,上阿里云直接选域名,购买就行了


购买域名

这里分享一下购买的时候一个小细节,就是更多价格里有的10年的会特别便宜,购买的时候可以关注一下:


更多价格
第三个需求:

切入正题,那就是如何用python实现DDNS功能。
因为我家里有一个常开的NAS,我觉得这里可以用个树莓派也可以,常挂在路由器后面运行个python程序就行。
如何实现真个功能呢? 那就分析一下代码需要实现的功能:
1.获取当前公网IP地址
2.查询当前阿里账号下的域名
3.根据域名查询当前是否有域名解析记录
4.根据判断添加或者更新域名解析

那么下面进入代码(这里不过多赘述,具体看代码注解):

import urllib
from aliyunsdkcore.client import AcsClient
from aliyunsdkalidns.request.v20150109 import DescribeDomainsRequest, DescribeDomainRecordsRequest, \
    UpdateDomainRecordRequest, AddDomainRecordRequest
import time
import json
#获取公网IP地址
def get_internet_ip():
    with urllib.request.urlopen('http://www.3322.org/dyndns/getip') as response:
        html = response.read()
        ip = str(html, encoding='utf-8').replace("\n", "")
    return ip

#获取当前域名下的记录内容,得到的是一个json格式的信息,主要是看TotalCount和record的列表
def Describe_SubDomain_Records():
    request = DescribeDomainRecordsRequest.DescribeDomainRecordsRequest()
    request.set_accept_format('json')
    #request.set_Type(record_type)
    #request.set_SubDomain(subdomain)
    request.set_DomainName(list_domain())
    response = client.do_action_with_exception(request)
    response = str(response, encoding='utf-8')
    relsult = json.loads(response)
    print(list_domain() + ':' + json.dumps(relsult))
    return relsult

#获取你的账户下的域名列表
def list_domain():
    DomainList = DescribeDomainsRequest.DescribeDomainsRequest()
    DomainList.set_accept_format('json')
    DNSListJson = json.loads(client.do_action_with_exception(DomainList))
    for i in DNSListJson['Domains']['Domain']:
        print(i['DomainName'])
        return i['DomainName']
    print(DNSListJson)

#添加记录,不过如果以前就有配置过记录,这个方法应该用不到
def add_record(client, priority, ttl, record_type, value, rr, domainname):
    request = AddDomainRecordRequest.AddDomainRecordRequest()
    request.set_accept_format('json')

    request.set_Priority(priority)
    request.set_TTL(ttl)
    request.set_Value(value)
    request.set_Type(record_type)
    request.set_RR(rr)
    request.set_DomainName(domainname)

    response = client.do_action_with_exception(request)
    response = str(response, encoding='utf-8')
    relsult = json.loads(response)
    return relsult

#更新记录,这里就是把新IP更新进去
def update_record(client, priority, ttl, record_type, value, rr, record_id):
    request = UpdateDomainRecordRequest.UpdateDomainRecordRequest()
    request.set_accept_format('json')

    request.set_Priority(priority)
    request.set_TTL(ttl)
    request.set_Value(value)
    request.set_Type(record_type)
    request.set_RR(rr)
    request.set_RecordId(record_id)

    response = client.do_action_with_exception(request)
    response = str(response, encoding='utf-8')
    return response


def main():
    ip = get_internet_ip()
    print('now IP: ' + ip)
    with open("ip", 'r') as f:
        old_ip = f.read()
    if ip == old_ip:
        print("no update" + "\nnew_ip:" + ip + "\nold_ip:" + old_ip)
    else:
        try:
            # print("update"+"\nnew_ip:"+ip+"\nold_ip:"+old_ip)
            with open("ip", 'w') as f_w:
                f_w.write(ip)
            des_relsult = Describe_SubDomain_Records()
            #print(des_relsult)
            # 判断子域名解析记录查询结果,TotalCount为0表示不存在这个子域名的解析记录,需要新增一个
            if des_relsult["TotalCount"] == 0:
                add_relsult = add_record(client, "5", "600", "A", ip, "*", "programers.ltd")
                record_id = add_relsult["RecordId"]
                print("域名解析新增成功!")
            # 判断子域名解析记录查询结果,TotalCount为1表示存在这个子域名的解析记录,需要更新解析记录,更新记录需要用到RecordId,这个在查询函数中有返回des_relsult["DomainRecords"]["Record"][0]["RecordId"]
            elif des_relsult["TotalCount"] == 1:
                #这里很清楚,就是更新第一条记录否则这里就是["0"]了
                record_id = des_relsult["DomainRecords"]["Record"][0]["RecordId"]
                update_record(client, "5", "600", "A", ip, "*", record_id)
                print("域名解析更新成功!")
            else:
                record_id = 0
                print("存在两个子域名解析记录值,请核查删除后再操作!")
            #记录下更新好的recordid,对比是否冲突
            path = 'RecordId'
            with open(path, 'w') as f_w1:
                f_w1.write(record_id)
        except Exception as result:
            print('错误: %s' % result)
            # 这种写法可以将报错原因显示出来,并且程序不会终止,会安安稳稳的运行
            pass


if __name__ == '__main__':
    while True:
        client = AcsClient('<accessKeyId>', '<accessSecret>', 'cn-hangzhou')
        #这里你肯定要问这个key哪里来的,请看参考链接中的创建RAM用户,这里会有方法,创建好用户后,一定要先把两个值记录下来,后面好像看不到了,并且请赋予"DNS云解析的权限"
        main()
        print(time.strftime('%Y-%m-%d %X', time.localtime()))
        time.sleep(180)

接下来就是在NAS或者树莓派部署服务了:
1.安装Python3
https://www.python.org/ 自行下载安装,官网下的贼慢。
2.安装pip

$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py   # 下载安装脚本
$ sudo python3 get-pip.py    # 运行安装脚本

3.安装setuptools
这里安装有些不一样的是NAS的pip3默认路径有点问题,所以在它的root的环境变量中没有pip3的路径,所以我这里是需要写全的。
平常的Linux可以直接执行pip3 install ****的。

root@DS218plus:~# /volume1/@appstore/py3k/usr/local/bin/pip3 install setuptools
Collecting setuptools
  Downloading setuptools-47.3.1-py3-none-any.whl (582 kB)
     |████████████████████████████████| 582 kB 34 kB/s 
Installing collected packages: setuptools
  WARNING: The scripts easy_install and easy_install-3.5 are installed in '/volume1/@appstore/py3k/usr/local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed setuptools-47.3.1

4.安装ali的SDK
这里就不要再用自带源了,真心慢,说不定还报错。建议咱们下阿里的SDK就用阿里的源。
额。。。有点像绕口令……
可以参考下面链接中的pip安装源修改

root@DS218plus:~# /volume1/@appstore/py3k/usr/local/bin/pip3 install aliyun-python-sdk-core  -i https://mirrors.aliyun.com/pypi/simple/ 
Looking in indexes: https://mirrors.aliyun.com/pypi/simple/
Collecting aliyun-python-sdk-core
  Downloading https://mirrors.aliyun.com/pypi/packages/85/f3/a4d18d44d037f2c570fc0f0cb5f954785bf1763861c5bbffb1e14aab2bb8/aliyun-python-sdk-core-2.13.19.tar.gz (433 kB)
     |████████████████████████████████| 433 kB 12.1 MB/s 
Collecting jmespath<1.0.0,>=0.9.3
  Downloading https://mirrors.aliyun.com/pypi/packages/07/cb/5f001272b6faeb23c1c9e0acc04d48eaaf5c862c17709d20e3469c6e0139/jmespath-0.10.0-py2.py3-none-any.whl (24 kB)
Collecting pycryptodome>=3.4.7
  Downloading https://mirrors.aliyun.com/pypi/packages/bf/7d/6b6494d91534b470f8045b9be013f1ba63eb84333da27d44b4efd4ae3e34/pycryptodome-3.9.8-cp35-cp35m-manylinux1_x86_64.whl (13.7 MB)
     |████████████████████████████████| 13.7 MB 32.4 MB/s 
Building wheels for collected packages: aliyun-python-sdk-core
  Building wheel for aliyun-python-sdk-core (setup.py) ... done
  Created wheel for aliyun-python-sdk-core: filename=aliyun_python_sdk_core-2.13.19-py3-none-any.whl size=526717 sha256=af1460e5fa6d4f286cdd07691b03af553b09008a5a8c3e1f4262d4068ebf3167
  Stored in directory: /root/.cache/pip/wheels/71/9e/25/16ceb9310c0a85893d5965a5ba245382159e04b880b25218ad
Successfully built aliyun-python-sdk-core
Installing collected packages: jmespath, pycryptodome, aliyun-python-sdk-core
Successfully installed aliyun-python-sdk-core-2.13.19 jmespath-0.10.0 pycryptodome-3.9.8
root@DS218plus:~#
root@DS218plus:~# /volume1/@appstore/py3k/usr/local/bin/pip3 install aliyun-python-sdk-alidns  -i https://mirrors.aliyun.com/pypi/simple/ 
Looking in indexes: https://mirrors.aliyun.com/pypi/simple/
Collecting aliyun-python-sdk-alidns
  Downloading https://mirrors.aliyun.com/pypi/packages/c9/33/ccb936224ba07e5fceec7df491ff436218d4b569d748473f2294c45f1ac0/aliyun-python-sdk-alidns-2.0.18.tar.gz (12 kB)
Requirement already satisfied: aliyun-python-sdk-core>=2.11.5 in /volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages (from aliyun-python-sdk-alidns) (2.13.19)
Requirement already satisfied: pycryptodome>=3.4.7 in /volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages (from aliyun-python-sdk-core>=2.11.5->aliyun-python-sdk-alidns) (3.9.8)
Requirement already satisfied: jmespath<1.0.0,>=0.9.3 in /volume1/@appstore/py3k/usr/local/lib/python3.5/site-packages (from aliyun-python-sdk-core>=2.11.5->aliyun-python-sdk-alidns) (0.10.0)
Building wheels for collected packages: aliyun-python-sdk-alidns
  Building wheel for aliyun-python-sdk-alidns (setup.py) ... done
  Created wheel for aliyun-python-sdk-alidns: filename=aliyun_python_sdk_alidns-2.0.18-py3-none-any.whl size=97707 sha256=243ff06c102a7048b5646994bcc008f11285e8ec6c0ae945db4d0cd0076f555a
  Stored in directory: /root/.cache/pip/wheels/e6/71/7f/dab52549224c09df054463023bb72367651fd799fe10d5f8bf
Successfully built aliyun-python-sdk-alidns
Installing collected packages: aliyun-python-sdk-alidns
Successfully installed aliyun-python-sdk-alidns-2.0.18
root@DS218plus:~#

ok可以先测试一下好不好使了:
运行:python3 ddns.py
再把路由器拨号处,点击断开,再重新拨号,就可以获得一个新的地址

外网状态:拨号成功 断开
外网状态:已断开 立即连接

问题:
错误: the JSON object must be str, not 'bytes'
检查了一下代码,对不起各位
在list_domain()方法中
json.loads(client.do_action_with_exception(DomainList).decode())
这里需要加上decode(),这就是报错的原因。

root@DS218plus:~/ddns/DDNS4NAS# python3 ddns.py 
now IP: XX.XX.XX.XX
example.ltd
example.ltd
example.ltd:{"PageNumber": 1, "PageSize": 20, "TotalCount": 1, "RequestId": "E#####-94F7-4B25-A91D-A2#####4976", "DomainRecords": {"Record": [{"DomainName": "example.ltd", "RR": "*", "Remark": "218plus", "Locked": false, "TTL": 600, "Value": "XX.22.22.22", "Weight": 1, "Type": "A", "Status": "ENABLE", "Line": "default", "RecordId": "1##############"}]}}
域名解析更新成功!
2020-06-27 22:14:14
__________________________________________________
now IP: XX.XX.XX.XX
no update
new_ip:XX.XX.XX.XX
old_ip:XX.XX.XX.XX
2020-06-27 22:14:32
__________________________________________________

至此部署测试完成,
下面我们就需要在后台运行这个程序就可以了,这里建议使用nohup:
nohup python3 -u ddns.py > /var/services/homes/test/DDNS.log 2>&1 &
来吧,一条查看指令结束这篇文章:
tail -f /var/services/homes/test/DDNS.log




参考链接:
官方论坛提供的方案:https://yq.aliyun.com/articles/702552
Python的Ali SDK: https://develop.aliyun.com/tools/sdk?#/python
pip安装源修改: https://blog.csdn.net/unicorn_mitnick/article/details/89738544
创建RAM用户:https://help.aliyun.com/document_detail/47664.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352