利用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中实现三目运算的方法
Jun 21 Python
利用Python爬取微博数据生成词云图片实例代码
Aug 31 Python
Python获取昨天、今天、明天开始、结束时间戳的方法
Jun 01 Python
Django框架模板的使用方法示例
May 25 Python
Python3简单爬虫抓取网页图片代码实例
Aug 26 Python
对Tensorflow中Device实例的生成和管理详解
Feb 04 Python
pycharm快捷键汇总
Feb 14 Python
python3 xpath和requests应用详解
Mar 06 Python
Python基于pyecharts实现关联图绘制
Mar 27 Python
在 Pycharm 安装使用black的方法详解
Apr 02 Python
Pytorch实验常用代码段汇总
Nov 19 Python
基于PyQT5制作一个桌面摸鱼工具
Feb 15 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 CURL模拟GET及POST函数代码
2010/04/25 PHP
PHP json格式和js json格式 js跨域调用实现代码
2012/09/08 PHP
php实现的支持断点续传的文件下载类
2014/09/23 PHP
JS 强制设为首页的代码
2009/01/31 Javascript
jquery ajax 登录验证实现代码
2009/09/23 Javascript
jQuery的实现原理的模拟代码 -2 数据部分
2010/08/01 Javascript
文本框input聚焦失焦样式实现代码
2012/10/12 Javascript
鼠标拖拽移动子窗体的JS实现
2014/02/25 Javascript
JavaScript程序员应该知道的45个实用技巧
2014/03/04 Javascript
jQuery实现动态添加和删除一个div
2015/08/12 Javascript
window.location.hash知识汇总
2015/11/09 Javascript
给angular加上动画效遇到的问题总结
2016/02/17 Javascript
Javascript json object 与string 相互转换的简单实现
2016/09/27 Javascript
Bootstrap基本组件学习笔记之列表组(11)
2016/12/07 Javascript
bootstrap table之通用方法( 时间控件,导出,动态下拉框, 表单验证 ,选中与获取信息)代码分享
2017/01/24 Javascript
jQuery插件FusionCharts绘制的3D饼状图效果实例【附demo源码下载】
2017/03/03 Javascript
Vue 2.0+Vue-router构建一个简单的单页应用(附源码)
2017/03/14 Javascript
node.js文件上传重命名以及移动位置的示例代码
2018/01/19 Javascript
详解Vue SSR( Vue2 + Koa2 + Webpack4)配置指南
2018/11/13 Javascript
使用vuepress搭建静态博客的示例代码
2019/02/14 Javascript
基于node.js实现爬虫的讲解
2019/02/18 Javascript
[40:12]Liquid vs Chaos 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
[50:02]完美世界DOTA2联赛PWL S2 Magma vs FTD 第三场 11.29
2020/12/03 DOTA
[58:00]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Elephant BO3 第二场 2月7日
2021/03/11 DOTA
python 动态获取当前运行的类名和函数名的方法
2014/04/15 Python
python实现给数组按片赋值的方法
2015/07/28 Python
在Python中使用defaultdict初始化字典以及应用方法
2018/10/31 Python
windows python3安装Jupyter Notebooks教程
2020/04/13 Python
基于SpringBoot构造器注入循环依赖及解决方式
2020/04/26 Python
五分钟带你搞懂python 迭代器与生成器
2020/08/30 Python
python单例模式的应用场景实例讲解
2021/02/24 Python
银行学习十八大感想
2014/01/11 职场文书
纪念9.18事变演讲稿
2014/09/14 职场文书
优秀班主任先进事迹材料
2014/12/16 职场文书
小程序实现筛子抽奖
2021/05/26 Javascript
Python OpenCV之常用滤波器使用详解
2022/04/07 Python