python自動(dòng)獲取家里寬帶的公網(wǎng)IP凡伊,并發(fā)送郵件
前言
有時(shí)候需要在公網(wǎng)訪問家里的NAS或者家里的服務(wù)器或者連接家里的遠(yuǎn)程桌面窗声,但是沒有公網(wǎng)IP無法訪問家里的設(shè)備。
目前已知的解決方案:
- 使用第三方公司提供的內(nèi)網(wǎng)穿透服務(wù)拦耐,如:花生殼等等见剩,缺點(diǎn):要錢
- 購(gòu)買一臺(tái)云服務(wù)器,獲得一個(gè)公網(wǎng)IP固翰;通過在用frp、ngrok疗琉、nps等等開源工具在本地和云服務(wù)上建立隧道歉铝,缺點(diǎn):要錢、不穩(wěn)定柠贤,網(wǎng)速受限于云服務(wù)器
- 使用ssh反向隧道类缤,缺點(diǎn):需要公網(wǎng)服務(wù)器
- 跟寬帶運(yùn)營(yíng)商聯(lián)系申請(qǐng)公網(wǎng)IP餐弱,缺點(diǎn):申請(qǐng)過程漫長(zhǎng),受限于聯(lián)通和電信寬帶
- 本地定時(shí)自動(dòng)獲取公網(wǎng)IP,發(fā)送通知速缆,如:郵件
由于本人沒錢所以還是選擇省錢的方式吧,本地定時(shí)自動(dòng)獲取公網(wǎng)IP發(fā)送通知郵件剧董。所以破停。真慢。開始搞吧
一、準(zhǔn)備工作
準(zhǔn)備條件:
- 已經(jīng)安裝寬帶
- 1臺(tái)路由器(最好是可以刷第三方固件管嬉,或者已經(jīng)刷成了三方固件)
- 1臺(tái)可以敲代碼的電腦
二朗鸠、獲取公網(wǎng)IP并發(fā)送郵件(重頭戲)
最簡(jiǎn)單的獲取公網(wǎng)IP的方式:打開瀏覽器,打開百度胎挎,輸入ip
, ok 你就可以看到自己的公網(wǎng)IP了犹菇,如圖:
很顯然這種方式并不合適。
那么第二種浦辨,打開路由器管理界面查看公網(wǎng)IP 也不推薦
第三種通過代碼獲取流酬,這才是比較推薦的方式
那么我所了解的通過代碼獲取IP的方式歸納起來應(yīng)該有兩種:
一種是直接訪問提供查看公網(wǎng)IP功能的服務(wù)器列另,獲取訪問的IP
例如:
import requests
url='http://jsonip.com'
# 獲取IP地址
resp = requests.get(url)
info = resp.json()
public_ip = info.get('ip')
OK 5行代碼就搞定了獲取公網(wǎng)IP的問題。但是這方式有兩個(gè)缺點(diǎn):1.需要寄托于別人的服務(wù)器正常運(yùn)行的情況摊滔,如果出現(xiàn)服務(wù)器維護(hù)的時(shí)候那就獲取不到公網(wǎng)IP店乐,2.如果你在路由器中配置了代理眨八,那么你獲取到的公網(wǎng)IP會(huì)是你代理服務(wù)器的IP,然而通過代理IP是無法訪問家里的設(shè)備的页响,所以不推薦這種方式段誊。
另一種就是直接在路由器中獲取公網(wǎng)IP
基本思路:使用代碼訪問路由器管理界面,獲取公網(wǎng)IP没陡∷魃停可選的技術(shù):1.使用爬蟲的方式,2.使用selenium
首先使用爬蟲的方式:requests庫(kù)强岸,只是簡(jiǎn)單的獲取IP蝌箍,就沒有必要使用scrapy
我使用的是路由器刷了三方固件PandoraBox
,只是做演示,提供思路妓盲,不是通用的,如需嘗試需要依據(jù)自己的路由情況
分析頁面:
首先需要登錄弹沽,需要找到登錄按鈕提交的鏈接:
查找方法:
第一種策橘,在瀏覽器中按F12娜亿,查看form節(jié)點(diǎn),其action屬性值就是訪問鏈接沛婴,或者也可以在瀏覽器中按下F12切換到Network選項(xiàng)卡中督赤,點(diǎn)擊一次提交,查看提交數(shù)據(jù)的URI; 所以提交登錄的鏈接為:http://192.168.1.1/cgi-bin/luci
第二種丑婿,使用fiddler等等抓包工具,抓取提交登錄的鏈接:
接下來就是使用代碼訪問這個(gè)鏈接自動(dòng)登錄
import requests
login_info = {
'username': 'root',
'password': 'XXXXX'
}
url = 'http://192.168.1.1/cgi-bin/luci'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"
}
def get_ip():
info_dict = {}
session = requests.session()
resp = session.post(url, headers=headers, data=login_info)
resp_info= resp.text
然后分析resp.text
返回的內(nèi)容枯冈,發(fā)現(xiàn)其中并沒有我們要的IP數(shù)據(jù),通過查看網(wǎng)頁源代可以知道滩褥,公網(wǎng)IP等信息有獨(dú)立的url。
如圖:
根據(jù)源代碼铺然,注意看:XHR.poll(5, '/cgi-bin/luci/;stok=4d9ab104d86153a97b35619e7f89dad9', { status: 1 }, 這就是路由器后臺(tái)管理返回?cái)?shù)據(jù)的真實(shí)URL, 由此我們可以知道公網(wǎng)IP的真實(shí)鏈接為:http://192.168.1.1/cgi-bin/luci/;stok=b92111c0fa47d24429f29de8b974d6b8?status=1
由于鏈接中的stok是動(dòng)態(tài)的所以需要先獲取該值魄健,然后構(gòu)造一個(gè)新的鏈接插勤。
在瀏覽器中訪問該鏈接革骨,會(huì)首先讓你登錄良哲,登錄之后會(huì)返回如圖信息:
接下來實(shí)現(xiàn)自動(dòng)登錄獲取IP信息:
import requests
import re
login_info = {
'username': 'root',
'password': 'XXXXX'
}
url = 'http://192.168.1.1/cgi-bin/luci'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"
}
def get_ip():
info_dict = {}
session = requests.session()
resp = session.post(url, headers=headers, data=login_info)
# resp_info = resp.json()
patter = "XHR.poll\(5, '/cgi-bin/luci/;stok=(.*?)', { status: 1 },"
stock = re.compile(patter).findall(resp.text)
info_url = url + '/;stok=' + ''.join(stock) + '?status=1'
resp_info = session.get(info_url, headers=headers).json()
if not resp_info:
info_dict = {'msg': '獲取信息出錯(cuò)'}
wan_info = resp_info.get('wan')
leases_info = resp_info.get('leases')
# 由于返回的信息太多了筑凫,只需要獲取自己想要的數(shù)據(jù)
if wan_info and leases_info:
leases_str = ''.join([str(leases) for leases in leases_info])
info_dict = {
'wan信息': {
'類型': wan_info.get('proto'),
'IP地址': wan_info.get('ipaddr'),
'子網(wǎng)掩碼': wan_info.get('netmask'),
'網(wǎng)關(guān)': wan_info.get('gwaddr'),
'DNS': wan_info.get('dns'),
'已連接': '{}天'.format(wan_info.get('uptime') / 60 / 60 / 24),
},
'連接的設(shè)備數(shù)量': len(leases_info),
'DHCP分配設(shè)備信息': leases_str.replace('expires', '剩余租期').replace('macaddr', 'MAC地址').replace('ipaddr', 'IPV4地址').replace('hostname', '主機(jī)名')
}
return info_dict
最后再加上發(fā)送郵件的代碼
直接上代碼吧:
# coding: utf-8
import requests
import re
import smtplib
from email.mime.text import MIMEText
from email.header import Header
login_info = {
'username': 'XXXX',
'password': 'XXXXX'
}
url = 'http://192.168.1.1/cgi-bin/luci'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"
}
mail_info = {
'recv_address': 'XXXX@qq.com',
'sender_name': 'XXXX@qq.com',
'sender_pwd': 'XXXXXXX',
'smtp_server': 'smtp.qq.com',
'subject': '路由器IP信息已更新',
'content': '您的公網(wǎng)IP信息: {},其他相關(guān)信息如下:{}'
}
def get_ip():
info_dict = {}
session = requests.session()
resp = session.post(url, headers=headers, data=login_info)
# resp_info = resp.json()
patter = "XHR.poll\(5, '/cgi-bin/luci/;stok=(.*?)', { status: 1 },"
stock = re.compile(patter).findall(resp.text)
info_url = url + '/;stok=' + ''.join(stock) + '?status=1&_=0.3290479886974037'
resp_info = session.get(info_url, headers=headers).json()
if not resp_info:
info_dict = {'msg': '獲取信息出錯(cuò)'}
wan_info = resp_info.get('wan')
leases_info = resp_info.get('leases')
if wan_info and leases_info:
leases_str = ''.join([str(leases) for leases in leases_info])
info_dict = {
'wan信息': {
'類型': wan_info.get('proto'),
'IP地址': wan_info.get('ipaddr'),
'子網(wǎng)掩碼': wan_info.get('netmask'),
'網(wǎng)關(guān)': wan_info.get('gwaddr'),
'DNS': wan_info.get('dns'),
'已連接': '{}天'.format(wan_info.get('uptime') / 60 / 60 / 24),
},
'連接的設(shè)備數(shù)量': len(leases_info),
'DHCP分配設(shè)備信息': leases_str.replace('expires', '剩余租期').replace('macaddr', 'MAC地址').replace('ipaddr', 'IPV4地址').replace('hostname', '主機(jī)名')
}
return info_dict
def send_message(content):
# 設(shè)置發(fā)送郵件的內(nèi)容
msg = MIMEText(content, 'plain', 'utf-8')
msg['From'] = Header(mail_info.get('sender_name'))
msg['Subject'] = Header(mail_info.get('subject'), 'utf-8')
msg['To'] = Header(mail_info.get('recv_address'))
# 發(fā)送郵件
smtp = smtplib.SMTP()
smtp.connect(mail_info['smtp_server'])
smtp.login(mail_info['sender_name'], mail_info['sender_pwd'])
smtp.sendmail(mail_info['sender_name'], mail_info['recv_address'], msg.as_string())
info_dict = get_ip()
content = ''
if info_dict.get('msg'):
content = info_dict.get('msg')
else:
content = mail_info.get('content').format(info_dict.get('wan信息').get('IP地址'), str(info_dict))
send_message(content)
** 另外一種直接使用selenium
的方式,直接放代碼:
import platform
import time
import os
from selenium import webdriver
url = 'http://192.168.1.1/cgi-bin/luci'
username = 'xxxx'
password = 'xxxx'
def get_ip():
option = webdriver.ChromeOptions()
option.add_argument("--headless") # 通過ChromeOptions設(shè)置隱藏瀏覽器
option.add_argument('--no-sandbox') # 在Linux上禁用瀏覽器沙盒
driver_path = loading_file_path(
'chromedriver.exe') if platform.system() == 'Windows' else loading_file_path(
'chromedriver')
driver = webdriver.Chrome(executable_path=driver_path, options=option)
driver.get(url)
user = driver.find_element_by_xpath('//form/div[1]/fieldset/fieldset/div[1]/div/input')
user.clear()
user.send_keys(username)
pwd = driver.find_element_by_id('focus_password')
pwd.clear()
pwd.send_keys(password)
login_btn = driver.find_element_by_xpath('//*[@id="maincontent"]/form/div[2]/input[1]')
login_btn.click()
time.sleep(2)
ip_info = driver.find_element_by_id('wan4_s').text
return ip_info
def loading_file_path(filename):
# 獲取當(dāng)前文件路徑
current_path = os.path.abspath(__file__)
# 獲取當(dāng)前文件的父目錄
father_path = os.path.abspath(os.path.dirname(current_path) + os.path.sep + ".")
# chromedriver文件路徑,獲取當(dāng)前目錄的父目錄與chromedriver拼接
webdriver_path = os.path.join(father_path, filename)
return webdriver_path
wan_ip = get_ip()
是不是覺得這種方式很簡(jiǎn)單蔫浆,確實(shí)使用selenium可以很簡(jiǎn)單的獲取到公網(wǎng)IP 信息瓦盛,但是這種方式部署的時(shí)候不適合NAS或者路由器
三外潜、部署腳本
1.可以在nas中添加一個(gè)定時(shí)任務(wù),不做演示
2.在路由器中添加定時(shí)任務(wù)嘱吗,不做演示(需要使用刷了三方固件的路由器)