利用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 相关文章推荐
vc6编写python扩展的方法分享
Jan 17 Python
网站渗透常用Python小脚本查询同ip网站
May 08 Python
Python模块结构与布局操作方法实例分析
Jul 24 Python
浅谈Python中range和xrange的区别
Dec 20 Python
python3安装speech语音模块的方法
Dec 24 Python
PyQt5创建一个新窗口的实例
Jun 20 Python
Django 路由控制的实现
Jul 17 Python
Keras中 ImageDataGenerator函数的参数用法
Jul 03 Python
基于python实现操作redis及消息队列
Aug 27 Python
python之语音识别speech模块
Sep 09 Python
python实现简单贪吃蛇游戏
Sep 29 Python
对象析构函数__del__在Python中何时使用
Mar 22 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&&mysql)六
2006/10/09 PHP
某大型网络公司应聘时的笔试题目附答案
2008/03/27 PHP
全世界最小的php网页木马一枚 附PHP木马的防范方法
2009/10/09 PHP
php图片的裁剪与缩放生成符合需求的缩略图
2013/01/11 PHP
php设计模式之命令模式的应用详解
2013/05/21 PHP
php获得网站访问统计信息类Compete API用法实例
2015/04/02 PHP
Thinkphp微信公众号支付接口
2016/08/04 PHP
Laravel实现表单提交
2017/05/07 PHP
PHP获取文件扩展名的方法实例总结
2017/06/10 PHP
jQuery 版本的文本输入框检查器Input Check
2009/07/09 Javascript
JQuery jsonp 使用示例代码
2009/08/12 Javascript
js字符串转成JSON
2013/11/07 Javascript
Extjs中RowExpander控件的默认展开问题示例探讨
2014/01/24 Javascript
让javascript加载速度倍增的方法(解决JS加载速度慢的问题)
2014/12/12 Javascript
jQuery中siblings()方法用法实例
2015/01/08 Javascript
纯CSS3代码实现滑动开关效果
2015/08/19 Javascript
javascript正则表达式之分组概念与用法实例
2016/06/16 Javascript
JavaScript中子对象访问父对象的方式详解
2016/09/01 Javascript
angularjs定时任务的设置与清除示例
2017/06/02 Javascript
对vue里函数的调用顺序介绍
2018/03/17 Javascript
如何实现小程序tab栏下划线动画效果
2019/05/18 Javascript
vue实现条件叠加搜索的解决方法
2019/05/28 Javascript
详解express使用vue-router的history踩坑
2019/06/05 Javascript
Angular6项目打包优化的实现方法
2019/12/15 Javascript
基于 Vue 的 Electron 项目搭建过程图文详解
2020/07/22 Javascript
鸿蒙系统中的 JS 开发框架
2020/09/18 Javascript
python实现的一个p2p文件传输实例
2014/06/04 Python
python中将函数赋值给变量时需要注意的一些问题
2017/08/18 Python
基于Django filter中用contains和icontains的区别(详解)
2017/12/12 Python
Python实现的合并两个有序数组算法示例
2019/03/04 Python
python plt可视化——打印特殊符号和制作图例代码
2020/04/17 Python
使用Python绘制台风轨迹图的示例代码
2020/09/21 Python
CSS3 实现飘动的云朵动画
2020/12/01 HTML / CSS
2014年办公室工作总结范文
2014/11/12 职场文书
2015年办税服务厅工作总结
2015/07/23 职场文书
jQuery实现广告显示和隐藏动画
2021/07/04 jQuery