python自动获取家里宽带的公网IP,并发送邮件
前言
有时候需要在公网访问家里的NAS或者家里的服务器或者连接家里的远程桌面,但是没有公网IP无法访问家里的设备。
目前已知的解决方案:
- 使用第三方公司提供的内网穿透服务,如:花生壳等等,缺点:要钱
- 购买一台云服务器,获得一个公网IP;通过在用frp、ngrok、nps等等开源工具在本地和云服务上建立隧道,缺点:要钱、不稳定,网速受限于云服务器
- 使用ssh反向隧道,缺点:需要公网服务器
- 跟宽带运营商联系申请公网IP,缺点:申请过程漫长,受限于联通和电信宽带
- 本地定时自动获取公网IP,发送通知,如:邮件
由于本人没钱所以还是选择省钱的方式吧,本地定时自动获取公网IP发送通知邮件。所以。。开始搞吧
一、准备工作
准备条件:
- 已经安装宽带
- 1台路由器(最好是可以刷第三方固件,或者已经刷成了三方固件)
- 1台可以敲代码的电脑
二、获取公网IP并发送邮件(重头戏)
最简单的获取公网IP的方式:打开浏览器,打开百度,输入ip
, ok 你就可以看到自己的公网IP了,如图:
很显然这种方式并不合适。
那么第二种,打开路由器管理界面查看公网IP 也不推荐
第三种通过代码获取,这才是比较推荐的方式
那么我所了解的通过代码获取IP的方式归纳起来应该有两种:
一种是直接访问提供查看公网IP功能的服务器,获取访问的IP
例如:
import requests
url='http://jsonip.com'
# 获取IP地址
resp = requests.get(url)
info = resp.json()
public_ip = info.get('ip')
OK 5行代码就搞定了获取公网IP的问题。但是这方式有两个缺点:1.需要寄托于别人的服务器正常运行的情况,如果出现服务器维护的时候那就获取不到公网IP,2.如果你在路由器中配置了代理,那么你获取到的公网IP会是你代理服务器的IP,然而通过代理IP是无法访问家里的设备的,所以不推荐这种方式。
另一种就是直接在路由器中获取公网IP
基本思路:使用代码访问路由器管理界面,获取公网IP。可选的技术:1.使用爬虫的方式,2.使用selenium
首先使用爬虫的方式:requests库,只是简单的获取IP,就没有必要使用scrapy
我使用的是路由器刷了三方固件PandoraBox
,只是做演示,提供思路,不是通用的,如需尝试需要依据自己的路由情况
分析页面:
首先需要登录,需要找到登录按钮提交的链接:
查找方法:
第一种,在浏览器中按F12,查看form节点,其action属性值就是访问链接,或者也可以在浏览器中按下F12切换到Network选项卡中,点击一次提交,查看提交数据的URI; 所以提交登录的链接为:http://192.168.1.1/cgi-bin/luci
第二种,使用fiddler等等抓包工具,抓取提交登录的链接:
接下来就是使用代码访问这个链接自动登录
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
返回的内容,发现其中并没有我们要的IP数据,通过查看网页源代可以知道,公网IP等信息有独立的url。
如图:
根据源代码,注意看:XHR.poll(5, '/cgi-bin/luci/;stok=4d9ab104d86153a97b35619e7f89dad9', { status: 1 }, 这就是路由器后台管理返回数据的真实URL, 由此我们可以知道公网IP的真实链接为:http://192.168.1.1/cgi-bin/luci/;stok=b92111c0fa47d24429f29de8b974d6b8?status=1
由于链接中的stok是动态的所以需要先获取该值,然后构造一个新的链接。
在浏览器中访问该链接,会首先让你登录,登录之后会返回如图信息:
接下来实现自动登录获取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': '获取信息出错'}
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'),
'子网掩码': wan_info.get('netmask'),
'网关': wan_info.get('gwaddr'),
'DNS': wan_info.get('dns'),
'已连接': '{}天'.format(wan_info.get('uptime') / 60 / 60 / 24),
},
'连接的设备数量': len(leases_info),
'DHCP分配设备信息': leases_str.replace('expires', '剩余租期').replace('macaddr', 'MAC地址').replace('ipaddr', 'IPV4地址').replace('hostname', '主机名')
}
return info_dict
最后再加上发送邮件的代码
直接上代码吧:
# 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': '您的公网IP信息: {},其他相关信息如下:{}'
}
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': '获取信息出错'}
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'),
'子网掩码': wan_info.get('netmask'),
'网关': wan_info.get('gwaddr'),
'DNS': wan_info.get('dns'),
'已连接': '{}天'.format(wan_info.get('uptime') / 60 / 60 / 24),
},
'连接的设备数量': len(leases_info),
'DHCP分配设备信息': leases_str.replace('expires', '剩余租期').replace('macaddr', 'MAC地址').replace('ipaddr', 'IPV4地址').replace('hostname', '主机名')
}
return info_dict
def send_message(content):
# 设置发送邮件的内容
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'))
# 发送邮件
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设置隐藏浏览器
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):
# 获取当前文件路径
current_path = os.path.abspath(__file__)
# 获取当前文件的父目录
father_path = os.path.abspath(os.path.dirname(current_path) + os.path.sep + ".")
# chromedriver文件路径,获取当前目录的父目录与chromedriver拼接
webdriver_path = os.path.join(father_path, filename)
return webdriver_path
wan_ip = get_ip()
是不是觉得这种方式很简单,确实使用selenium可以很简单的获取到公网IP 信息,但是这种方式部署的时候不适合NAS或者路由器
三、部署脚本
1.可以在nas中添加一个定时任务,不做演示
2.在路由器中添加定时任务,不做演示(需要使用刷了三方固件的路由器)