python自动获取家里宽带的公网IP,并发送邮件

python自动获取家里宽带的公网IP,并发送邮件

前言

有时候需要在公网访问家里的NAS或者家里的服务器或者连接家里的远程桌面,但是没有公网IP无法访问家里的设备。
目前已知的解决方案:

  1. 使用第三方公司提供的内网穿透服务,如:花生壳等等,缺点:要钱
  2. 购买一台云服务器,获得一个公网IP;通过在用frp、ngrok、nps等等开源工具在本地和云服务上建立隧道,缺点:要钱、不稳定,网速受限于云服务器
  3. 使用ssh反向隧道,缺点:需要公网服务器
  4. 跟宽带运营商联系申请公网IP,缺点:申请过程漫长,受限于联通和电信宽带
  5. 本地定时自动获取公网IP,发送通知,如:邮件

由于本人没钱所以还是选择省钱的方式吧,本地定时自动获取公网IP发送通知邮件。所以。。开始搞吧

一、准备工作

准备条件:

  1. 已经安装宽带
  2. 1台路由器(最好是可以刷第三方固件,或者已经刷成了三方固件)
  3. 1台可以敲代码的电脑

二、获取公网IP并发送邮件(重头戏)

最简单的获取公网IP的方式:打开浏览器,打开百度,输入ip, ok 你就可以看到自己的公网IP了,如图:

baidu_ip.png

很显然这种方式并不合适。

那么第二种,打开路由器管理界面查看公网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,只是做演示,提供思路,不是通用的,如需尝试需要依据自己的路由情况

分析页面:
首先需要登录,需要找到登录按钮提交的链接:

luyouqi_login.png

查找方法:
第一种,在浏览器中按F12,查看form节点,其action属性值就是访问链接,或者也可以在浏览器中按下F12切换到Network选项卡中,点击一次提交,查看提交数据的URI; 所以提交登录的链接为:http://192.168.1.1/cgi-bin/luci

image.png

第二种,使用fiddler等等抓包工具,抓取提交登录的链接:

image.png

接下来就是使用代码访问这个链接自动登录

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。
如图:

image.png

根据源代码,注意看: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是动态的所以需要先获取该值,然后构造一个新的链接。

在浏览器中访问该链接,会首先让你登录,登录之后会返回如图信息:


image.png

接下来实现自动登录获取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.在路由器中添加定时任务,不做演示(需要使用刷了三方固件的路由器)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容