利用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 域名分析工具实现代码
Jul 15 Python
Python中字符编码简介、方法及使用建议
Jan 08 Python
python版简单工厂模式
Oct 16 Python
Numpy掩码式数组详解
Apr 17 Python
python在文本开头插入一行的实例
May 02 Python
python实现嵌套列表平铺的两种方法
Nov 08 Python
python matplotlib中的subplot函数使用详解
Jan 19 Python
python requests.get带header
May 05 Python
浅谈numpy中np.array()与np.asarray的区别以及.tolist
Jun 03 Python
pyCharm 实现关闭代码检查
Jun 09 Python
Python sqlalchemy时间戳及密码管理实现代码详解
Aug 01 Python
Python爬虫之Selenium实现关闭浏览器
Dec 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
adodb与adodb_lite之比较
2006/12/31 PHP
探讨各种PHP字符串函数的总结分析
2013/06/05 PHP
php导出word文档与excel电子表格的简单示例代码
2014/03/08 PHP
Laravel中为什么不使用blpop取队列详析
2018/08/01 PHP
动态加载js和css(外部文件)
2013/04/17 Javascript
Jquery获取复选框被选中值的简单方法
2013/07/04 Javascript
jquery定时滑出可最小化的底部提示层特效代码
2013/10/02 Javascript
javascript结合Flexbox简单实现滑动拼图游戏
2016/02/18 Javascript
ES6的新特性概览
2016/03/10 Javascript
Jquery实现上下移动和排序代码
2016/10/17 Javascript
jQuery与js实现颜色渐变的方法
2016/12/30 Javascript
angularjs指令之绑定策略(@、=、&)
2017/04/13 Javascript
JavaScript生成指定范围的时间列表
2018/03/19 Javascript
解决echarts的多个折现数据出现坐标和值对不上的问题
2018/12/28 Javascript
vue quill editor 使用富文本添加上传音频功能
2020/01/14 Javascript
JS面向对象之多选框实现
2020/01/17 Javascript
微信小程序调用wx.getImageInfo遇到的坑解决
2020/05/31 Javascript
[00:32]2018DOTA2亚洲邀请赛Newbee出场
2018/04/03 DOTA
让python json encode datetime类型
2010/12/28 Python
python使用win32com库播放mp3文件的方法
2015/05/30 Python
详解Python3中yield生成器的用法
2015/08/20 Python
详解JavaScript编程中的window与window.screen对象
2015/10/26 Python
学习python之编写简单简单连接数据库并执行查询操作
2016/02/27 Python
Django分页功能的实现代码详解
2019/07/29 Python
Python参数传递及收集机制原理解析
2020/06/05 Python
Tensorflow与Keras自适应使用显存方式
2020/06/22 Python
浅析Python 多行匹配模式
2020/07/24 Python
浅析HTML5中的 History 模式
2017/06/22 HTML / CSS
html5 拖拽及用 js 实现拖拽功能的示例代码
2020/10/23 HTML / CSS
2014年学习雷锋活动总结
2014/03/01 职场文书
2015年安全教育月活动总结
2015/03/26 职场文书
黑暗中的舞者观后感
2015/06/18 职场文书
Python机器学习之KNN近邻算法
2021/05/14 Python
自从在 IDEA 中用了热部署神器 JRebel 之后,开发效率提升了 10(真棒)
2021/06/26 Java/Android
开机音效回归! Windows 11重新引入开机铃声
2021/11/21 数码科技
JS前端可视化canvas动画原理及其推导实现
2022/08/05 Javascript