利用Python+阿里雲實現DDNS(動態域名解析)

引子

我想大家應該都很熟悉DNS了,這回在DNS前面加了一個D又變成了什麼呢?這個D就是Dynamic(動態),也就是說,按照傳統,一個域名所對應的IP地址應該是定死的,而使用了DDNS後,域名所對應的IP是可以動態變化的。那這個有什麼用呢?
比如,在家裏的路由器上連着一個raspberry pi(樹莓派),上面跑着幾個網站,我應該如和在外網環境下訪問網站、登陸樹莓派的SSH呢?
還有,家裏的NAS(全稱Network Attach Storage 網絡附屬存儲,可以理解爲私有的百度網盤)上存儲着大量的視頻、照片,如何在外網環境下和朋友分享呢?
這時,就要靠DDNS了!它會動態偵運營商分配給你的IP變化,並映射到域名上,這時就可以用域名來訪問家庭環境中的內容了~
哈!有了域名,走遍天下都不怕有木有
實現效果(因爲我已經更新過了,所以它提示IP地址已存在,阿里雲是不允許同一個IP重複更新的)

本地:

使用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直接使用官方最新版本即可

實現步驟

項目結構


注:

  • AcsClientSingleton.py => 阿里雲AcsClient單實例類
  • CommonRequestSingleton.py => 阿里雲CommonRequest的單實例類,獲取阿里雲Common Request請求類
  • DDNS.py => 主程序
  • IpGetter.py =>獲取家庭寬帶實際的公網IP
  • Utils.py => 工具類

爬IP

首當其衝的就是要獲得我們實際的IP地址,推薦ip138.com
你看到的頁面是這樣的:

畫紅框的部分是一個iframe

其中的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後,我們可以觀察一下網頁結構

發現,我們只需要獲取到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 SDK。在簽名中,有一個至關重要的是AccessKey,AccessKey的生成可以在管理控制檯的AccessKeys模塊獲取

生成之後一定要保管好這個密鑰哦!!!!!
由於雲解析官方並沒有提供對應的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章