利用Python+阿里云实现DDNS动态域名解析的方法


Posted in Python onApril 01, 2019

引子

我想大家应该都很熟悉DNS了,这回在DNS前面加了一个D又变成了什么呢?这个D就是Dynamic(动态),也就是说,按照传统,一个域名所对应的IP地址应该是定死的,而使用了DDNS后,域名所对应的IP是可以动态变化的。那这个有什么用呢?
比如,在家里的路由器上连着一个raspberry pi(树莓派),上面跑着几个网站,我应该如和在外网环境下访问网站、登陆树莓派的SSH呢?

还有,家里的NAS(全称Network Attach Storage 网络附属存储,可以理解为私有的百度网盘)上存储着大量的视频、照片,如何在外网环境下和朋友分享呢?

这时,就要靠DDNS了!它会动态侦运营商分配给你的IP变化,并映射到域名上,这时就可以用域名来访问家庭环境中的内容了~

哈!有了域名,走遍天下都不怕有木有

实现效果(因为我已经更新过了,所以它提示IP地址已存在,阿里云是不允许同一个IP重复更新的)

利用Python+阿里云实现DDNS动态域名解析的方法 

本地:

利用Python+阿里云实现DDNS动态域名解析的方法 

使用DDNS后,在外网环境下:

利用Python+阿里云实现DDNS动态域名解析的方法 

注:

这篇帖子适用于家庭宽带的IP是公网IP的小伙伴,但是注意,这种公网IP是临时的,会不定时进更改。判断方法很简单:先去百度搜索IP,查到自己的IP地址;接着本地开一个网站,比如在Windows下直接启动IIS,Linux下安装一个Apache或者Nginx启动,使用它们的默认页面;然后在路由器上设置好转发规则,公网IP的网络访问端口最好不要用80,80端口可能被运营商封了;最后利用前面查到的公网IP+端口号访问一下,看看能不能显示内网上的页面,如果可以,恭喜你!

本文涉及到的技术点会比较多,比如爬虫啊,设计模式啊,函数修饰符啊等等,可以算是一个综合运用了吧~

实现思路

前面引文已经说的很清楚了,就是探测家庭宽带公网IP的变化,然后利用我们的程序将这个IP更新到它所绑定的二级域名上~
综上,我的思路是这样的:
1、利用Python去网上爬取自己真实的IP地址
2、利用阿里云所提供的接口更新IP

前期准备

1、一个域名(国内需要备案,港澳台和国外听说是不要的,我也没尝试过)
2、将域名的解析设置到阿里云的云解析上
3、为我们的DDNS创建一个二级域名(例如 ddns.expamle.com)
4、安装阿里云Python SDK(具体教程可以去阿里云上找
5、建议先去阅读一下Python SDK的使用示例
6、约定:所有的API请求都返回JSON格式,所以要使用Python的JSON模块进行解析

环境版本

1、Python 3.6
2、网页解析利用BeautifulSoup 4
3、阿里的云解析API和Python SDK直接使用官方最新版本即可

实现步骤

项目结构

利用Python+阿里云实现DDNS动态域名解析的方法 

注:

AcsClientSingleton.py => 阿里云AcsClient单实例类

CommonRequestSingleton.py => 阿里云CommonRequest的单实例类,获取阿里云Common Request请求类

DDNS.py => 主程序

IpGetter.py =>获取家庭宽带实际的公网IP

Utils.py => 工具类

爬IP

首当其冲的就是要获得我们实际的IP地址,推荐ip138.com

你看到的页面是这样的:

利用Python+阿里云实现DDNS动态域名解析的方法 

画红框的部分是一个iframe

利用Python+阿里云实现DDNS动态域名解析的方法 

其中的URL是一直会变化的,所以第一步是要获取这个URL,我这里用到的解析框架是BeautifulSoup,感觉用Scrapy有点大材小用了

#获得IP检测的网页URL
def getIpPage():
 url = "http://www.ip138.com/"
 response = urllib.request.urlopen(url)
 html = response.read().decode("gb2312")
 soup = BeautifulSoup(html, "lxml")
 _iframe = soup.body.iframe
 return _iframe["src"]

获取到检测IP地址的URL后,我们可以观察一下网页结构

利用Python+阿里云实现DDNS动态域名解析的方法 

发现,我们只需要获取到center标签的内容,然后用正则提取出IP即可

#获取IP地址
def getRealIp(url):
 response = urllib.request.urlopen(url)
 html = response.read().decode("gb2312")
 pattern = r"(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)"
 matchs = re.search(pattern,html)
 ip_addr = ""
 for i in range(1,5):
  ip_addr += matchs.group(i) + "."
 return ip_addr[:-1]

然后我们爬的工作就完成了,可以将这个获取IP的过程进行封装,放进工具类里

查文档

阿里云云解析API文档

我们需要用到的是UpdateDomainRecord这个Action。

可以观察一下它的请求参数

利用Python+阿里云实现DDNS动态域名解析的方法 

在阿里的请求中,有一个公共参数(上面没有提及),里面有一个签名,这个签名虽然官方提供了签名生成的算法,不过如果自己实现很容易出错,所以我们使用它的Python SDK。在签名中,有一个至关重要的是AccessKey,AccessKey的生成可以在管理控制台的AccessKeys模块获取

利用Python+阿里云实现DDNS动态域名解析的方法 

生成之后一定要保管好这个密钥哦!!!!!

由于云解析官方并没有提供对应的SDK模块,只提供了API,不过我们可以利用SDK中的CommonRequest对象来进行API操作。不知道各位有木有发现在更新域名解析记录的请求参数中有一个RecordId,这个RecordId要利用DescribeDomainRecords这个Action来获取。

如果每次请求都要使用CommonRequest对象,这样难免会造成一定的内存浪费,所以使用面向对象设计模式中的单例模式进行优化。

class CommonRequestSing:
 #私有类变量
 __request = None

 #该修饰符将实例方法变成类方法
 #,因为类方法无法操作私有的类变量,所以使用实例方法进行操作,再进行转换为类方法
 @classmethod
 def getInstance(self):
  if self.__request is None:
   self.__request = CommonRequest()
  return self.__request

同时,在构造请求式,也会用到AcsClient对象,也可使用单例模式优化

class AcsClientSing:
 __client = None
 @classmethod
 def getInstance(self):
  if self.__client is None:
   self.__client = AcsClient('Your_AccessKeyId', 'Your_AccessKeySecret', 'cn-hangzhou')
  return self.__client

这里用到了函数修饰符@classmethod,主要功能是将实例方法转换为类方法。

这两个单实例都可封装进工具类中,直接调用工具类获取实例就可以了,代码会更美观一些。

获取RecordID

利用DescribeDomainRecords 这个Action来获得。

#获取二级域名的RecordId
 def getRecordId(domain):
  client = Utils.getAcsClient()
  request = Utils.getCommonRequest()
  request.set_domain('alidns.aliyuncs.com')
  request.set_version('2015-01-09')
  request.set_action_name('DescribeDomainRecords')
  request.add_query_param('DomainName', 'Your_DomainName eg.example.com')
  response = client.do_action_with_exception(request)
  jsonObj = json.loads(response.decode("UTF-8"))
  records = jsonObj["DomainRecords"]["Record"]
  for each in records:
   if each["RR"] == domain:
    return each["RecordId"]

更新解析记录IP,DDNS逻辑核心

def DDNS():
 client = Utils.getAcsClient()
 recordId = Utils.getRecordId('ddns')
 ip = Utils.getRealIP()
 request = Utils.getCommonRequest()
 request.set_domain('alidns.aliyuncs.com')
 request.set_version('2015-01-09')
 request.set_action_name('UpdateDomainRecord')
 request.add_query_param('RecordId', recordId)
 request.add_query_param('RR', 'ddns')
 request.add_query_param('Type', 'A')
 request.add_query_param('Value', ip)
 response = client.do_action_with_exception(request)
 return response

if __name__ == "__main__":
 try:
  result = DDNS()
  print("成功!")
 except (ServerException,ClientException) as reason:
  print("失败!原因为")
  print(reason.get_error_msg())

至此结束~然后设置好路由器端口映射,这时候你就可以使用ddns.example.com:XXX来进行访问设置在家庭网络中的资源了~
然后可以将这个Python代码设置为定时任务,比如每天执行一次,或者根据运营商的IP变化策略调整~

源码(最新):https://github.com/mgsky1/DDNS

源码(结构与文章一样的):点击这里

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python多进程通信Queue、Pipe、Value、Array实例
Nov 21 Python
利用Python破解验证码实例详解
Dec 08 Python
Python 实现在文件中的每一行添加一个逗号
Apr 29 Python
Python实现字典(dict)的迭代操作示例
Jun 05 Python
老生常谈python中的重载
Nov 11 Python
一文了解Python并发编程的工程实现方法
May 31 Python
详解python深浅拷贝区别
Jun 24 Python
Pytorch .pth权重文件的使用解析
Feb 14 Python
python GUI编程(Tkinter) 创建子窗口及在窗口上用图片绘图实例
Mar 04 Python
Python web如何在IIS发布应用过程解析
May 27 Python
浅谈Python中文件夹和python package包的区别
Jun 01 Python
Python与C/C++的相互调用案例
Mar 04 Python
PythonWeb项目Django部署在Ubuntu18.04腾讯云主机上
Apr 01 #Python
python使用Plotly绘图工具绘制柱状图
Apr 01 #Python
python使用Plotly绘图工具绘制水平条形图
Mar 25 #Python
Python进阶之@property动态属性的实现
Apr 01 #Python
彻底理解Python中的yield关键字
Apr 01 #Python
python抓取搜狗微信公众号文章
Apr 01 #Python
Python使用os.listdir()和os.walk()获取文件路径与文件下所有目录的方法
Apr 01 #Python
You might like
php zip文件解压类代码
2009/12/02 PHP
php下安装配置fckeditor编辑器的方法
2011/03/02 PHP
php数组函数序列之array_keys() - 获取数组键名
2011/10/30 PHP
Php图像处理类代码分享
2012/01/19 PHP
浅谈PHP值mysql操作类
2016/06/29 PHP
PHP preg_match实现正则表达式匹配功能【输出是否匹配及匹配值】
2017/07/19 PHP
HTML复选框和单选框 checkbox和radio事件介绍
2012/12/12 Javascript
Javasipt:操作radio标签详解
2013/12/30 Javascript
详细分析JavaScript函数定义
2015/07/16 Javascript
JS遍历数组及打印数组实例分析
2016/01/21 Javascript
JavaScript实现设计模式中的单例模式的一些技巧总结
2016/05/17 Javascript
详解vue-Resource(与后端数据交互)
2017/01/16 Javascript
Swiper 4.x 使用方法(移动端网站的内容触摸滑动)
2018/05/17 Javascript
JavaScript实现表单注册、表单验证、运算符功能
2018/10/15 Javascript
Jquery动态列功能完整实例
2019/08/30 jQuery
layui对工具条进行选择性的显示方法
2019/09/19 Javascript
Vue父子组件传值的一些坑
2020/09/16 Javascript
python读取与写入csv格式文件的示例代码
2017/12/16 Python
Python实现带参数与不带参数的多重继承示例
2018/01/30 Python
Python3 log10()函数简单用法
2019/02/19 Python
linux安装python修改默认python版本方法
2019/03/31 Python
Python文件操作方法详解
2020/02/09 Python
pycharm设置python文件模板信息过程图解
2020/03/10 Python
Python3与fastdfs分布式文件系统如何实现交互
2020/06/23 Python
python3中TQDM库安装及使用详解
2020/11/18 Python
如何查看浏览器对html5的支持情况
2020/12/15 HTML / CSS
澳大利亚UGG工厂直销:Australian Ugg Boots
2017/10/14 全球购物
Spartoo瑞典:鞋子、包包和衣服
2018/09/15 全球购物
铣工实训报告
2014/11/05 职场文书
开学第一周值周总结
2015/07/16 职场文书
用人单位的规章制度,怎样制定才是有效的?
2019/07/09 职场文书
解决python存数据库速度太慢的问题
2021/04/23 Python
基于flask实现五子棋小游戏
2021/05/25 Python
浅谈如何提高PHP代码质量之单元测试
2021/05/28 PHP
Linux中如何安装并部署Redis
2022/04/18 Servers
配置nginx负载均衡
2022/05/06 Servers