15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > 用python编写控制网络设备的自动化脚本8:网页

用python编写控制网络设备的自动化脚本8:网页

时间:2023-07-06 00:09:01 | 来源:网站运营

时间:2023-07-06 00:09:01 来源:网站运营

用python编写控制网络设备的自动化脚本8:网页:项目地址:

前言

对于初学网络的人来说,配置网络设备就是敲命令,包括我之前开发的网络设备脚本也是围绕命令行开发的。配置网络设备的方法不只有命令行,更常见的是利用网页来配置。在淘宝上搜“路由器”,大部分是家用级路由器。而这些路由器大部分只支持网页操作,不支持命令行。于是我写了一些代码,用来自动化地配置这些塑料设备。

准备工作

文章中的代码依赖selenium库,所以写代码运行代码之前先准备好运行环境。

第一步,安装selenium库,电脑联网的话直接用pip安装。

pip install selenium第二步,下载浏览器驱动,selenium要调用浏览器驱动才能使用。我比较喜欢用火狐,火狐浏览器驱动可以从GitHub下载(https://github.com/mozilla/geckodriver/releases),下载下来的压缩包解压后得到geckodriver.exe,随便扔到一个%PATH%目录中(C:/Program Files/Python38/Scripts)。

获取网页元素和操控网页的过程

在开始写网络设备脚本之前,先简单介绍一下如何用浏览器获取网页元素以及使用selenium操控网页内容。

这次我拿真机作为例子,一台放在家里的塑料路由器。

浏览器输入路由器地址,进入登录页面。

按F12,打开开发人员工具,找到密码框、登录按钮对应的元素,记下其中的id属性。

编写代码,让python打开浏览器,输入密码,然后点登录按钮。

import timeimport selenium.webdriver #seleniumv浏览器 = selenium.webdriver.Firefox()v浏览器.get("http://192.168.1.1")time.sleep(1)w密码 = v浏览器.find_element_by_id("lgPwd")w密码.send_keys("********")time.sleep(1)w确定 = v浏览器.find_element_by_id("loginSub")w确定.click()time.sleep(1)运行结果如图:

下面开始编写用来控制网络设备的代码。

三层架构

网络设备脚本按照代码组织结构分成3个不同的层:

三层架构

连接层:只有selenium

使用网络设备脚本的第一步是创建连接。实际上,为了方便使用,我会把selenium封装一下,简化创建连接的过程。

cflw代码库py/cflw网页连接.py

import selenium.webdriverdef f创建连接(a地址): v浏览器 = selenium.webdriver.Firefox() v浏览器.get(a地址) return v浏览器f创建连接 的功能是创建一个浏览器对象,并打开具体地址。这段代码只是一个简单例子,实际的 f创建连接 要处理不同浏览器的情况,代码更复杂。

设备层:网页设备(编程)接口

根据三层架构,设备层用来承上启下,必须对连接层进行封装。如果不封装,让模式层直接调用连接层,会形成高耦合,以后不管是排错还是重构都会非常麻烦。

一般来说,操纵网页其实就是操纵浏览器和里面的网页元素,所以只要写2个类就够了。一个是设备类,表示浏览器。一个是元素类,表示网页元素。

网络设备脚本/网页接口/设备.py

from ..基础接口 import 设备from selenium.common import exceptions #seleniumclass I设备(设备.I设备): def __init__(self, a连接): self.m连接 = a连接 def fs地址(self, a地址): self.m连接.get(a地址) def fg地址(self): return self.m连接.current_url def f查找(self, a找: str): try: v元素 = self.m连接.find_element_by_xpath(a找) return 元素.C元素(v元素) except exceptions.NoSuchElementException as e: return None网络设备脚本/网页接口/元素.py

class C元素: """对selenium网页元素的封装""" def __init__(self, a元素): self.m元素 = a元素 def f查找(self, a找): return C元素(self.m元素.find_element_by_xpath(a找)) def f点击(self): self.m元素.click() def f清除(self): self.m元素.clear() def f输入(self, a): self.m元素.send_keys(a) def fg文本(self): if self.m元素.tag_name in ("input",): #表单控件从值属性取文本 return self.m元素.get_attribute("value") return self.m元素.text def fg属性(self, a属性名): return self.m元素.get_attribute(a属性名)

根据连接创建设备

使用网络设备脚本的第二步是创建设备。从简单易用的角度考虑,用户不需要知道具体的设备类名,只需要知道连接、品牌、型号、版本就够了。f创建设备 就是用来隐藏具体细节的函数,根据传入的参数,创建合适的设备返回给用户。调用过程就像这样:

import cflw代码库py.cflw网页连接 as 网页连接import 网络设备脚本.普联 as 普联v连接 = 网页连接.f创建连接("http://192.168.1.1")v设备 = 普联.f创建设备(v连接, 普联.E型号.wdr5620)这里说一下如何判断一个连接是否是网页连接。一般python中判断一个类型是否和另外一个类型相同或存在继承关系可以用type(a) == Aisinstance(a, A)这样的方法。selenium里面浏览器类都继承自selenium.webdriver.remote.webdriver.WebDriver,写出来就变成isinstance(连接, selenium.webdriver.remote.webdriver.WebDriver),并且要在文件顶部写一句import selenium,代码如下:

import selenium.webdriver.remote.webdriverdef fi网页连接(a连接): return isinstance(a连接, selenium.webdriver.remote.webdriver.WebDriver)#好像没什么问题这段判断代码看起来好像没什么问题,如果传进去的是其他连接就返回False,看起来很合理。但是忽略了一种重要情况,用户没有安装selenium的话,第一行会抛出ModuleNotFoundError异常。所以这时候就要寻找一种不导入模块也能判断网页连接的办法。

一个方法是使用__class__属性,一般 a连接.__class__ 会得到像 <class 'selenium.webdriver.firefox.webdriver.WebDriver'> 这样的一个类,只要揪着这个类名做判断就行。

网络设备脚本/基础接口/连接层.py

def fi网页(a连接): if "selenium" in str(a连接.__class__): #selenium return True return False至于判断型号判断版本没什么好说的,用一堆if...elif...else就行了。

最终的f创建设备长得像这样:

网络设备脚本/普联.py

from .基础接口 import 连接层def f创建设备(a连接, a型号, a版本 = 0): if 连接层.fi网页(a连接): from .普联网页 import 设备 return 设备.C设备(a连接, a型号, a版本) raise ValueError("不支持的连接")其中的C设备就是具体的设备类,其构造函数接收连接、型号、版本,供模式层使用。

网络设备脚本/普联网页/设备.py

from ..网页接口 import 设备class C设备(设备.I设备): def __init__(self, a连接, a型号, a版本): 设备.I设备.__init__(self, a连接) self.m型号 = a型号 self.m版本 = a版本

模式层

模式层负责接收用户传递的参数,整理成具体操作传给设备层。在实际网页中,一个页面能做的操作是有限的,模式接口提供的操作和一个网页页面提供的操作不一定能对的上。所以网页的模式层有必要分成2层,上层的模式接口供用户调用,并调度下面的网页模式,或者为了省事直接合并写成一个类。这种设计可以让模式接口适配各式各样的页面,并且根据需要在不同页面来回切换。

接口与实现
用户模式是前往其他模式的总入口。用户模式用来处理登录和提升权限等控制设备时必须经历的操作,其他操作则由其他模式来完成,比如查看操作由显示模式负责,配置操作由配置模式负责。这么设计的目的是为了尽可能地解耦,拆分文件,合理组织代码,避免出现庞然大类。

在脚本中实现登录过程

把三层架构理完了,开始最基础的第一步,实现登录设备的过程。

一般来说,这个登录过程应该在连接层或模式层完成,常见的在连接层登录的协议有安全外壳(SSH)、等,这些协议在建立连接过程中携带了登录信息,建立完连接不需要再登录。网页中更常见的是在打开的登录页面中输入用户名密码登录,我把这个过程归类到模式层。在脚本中,相关的登录过程代码在模式层完成。

接着上面的“根据连接创建设备”一节的代码,通过调用f创建设备得到设备对象后,接着获取一个用户模式,用户模式是网络设备正常运行情况下通往其他模式的入口,既然是入口,也就包含了登录操作。在脚本中,这个过程就像这样:

import cflw代码库py.cflw网页连接 as 网页连接import 网络设备脚本.普联 as 普联v连接 = 网页连接.f创建连接("http://192.168.0.1")v设备 = 普联.f创建设备(v连接, 普联.E型号.wdr5620)v用户模式 = v设备.f模式_用户()v用户模式.f登录(a密码 = "******")f模式_用户的内容很简单,直接创建一个用户模式对象并返回。

网络设备脚本/普联网页/设备.py

from ..网页接口 import 设备class C设备(设备.I设备): def f模式_用户(self): from . import 用户模式 return 用户模式.C用户模式(self)接着是用户模式。在网页中,一个网页模式总是保存设备对象,以实现对网页的操控。所以这个C用户模式有2个函数,一个构造函数,一个登录函数。

网络设备脚本/普联网页/用户模式.py

import timefrom ..基础接口 import 用户模式class C用户模式(用户模式.I用户模式): def __init__(self, a设备): self.m设备 = a设备 def f登录(self, a密码 = "", a用户名 = None): w密码框 = self.m设备.f查找("//input[@id='lgPwd']") w密码框.f输入(a密码) w确定 = self.m设备.f查找("//input[@id='loginSub']") w确定.f点击()其中的f登录就是把“获取网页元素和操控网页的过程”一节的代码拿过来改一改,就变成了封装过一遍的代码。

登录过程中处理验证码

有些设备会在登录时加一个验证码防脚本防机器人,验证码自然也把脚本挡住了,让脚本无法继续运行。

深信服防火墙的登录页面
处理验证码的过程很简单,脚本填写完用户名密码后,把输入焦点聚焦在验证码框,然后等待人输入验证码,脚本检测到输完验证码了就继续运行。(机器识别验证码?不存在的。)

f手动输入验证码用来把焦点聚集在输入框中,让用户手动输入验证码,并且判断验证码是否输入完毕。

网络设备脚本/网页接口/图片.py

import cflw代码库py.cflw时间 as 时间from . import 元素def f手动输入验证码(a元素, a长度): a元素.f聚焦() v循环阻塞 = 时间.C循环阻塞(60, a间隔 = 1) while v循环阻塞.f滴答(): if len(a元素.fg文本()) >= a长度: #有输入 return True else: #没有输入 return Falsef登录中,先获取网页元素,自动输入用户名密码,接着手动输入验证码,最后判断是否登录成功。

网络设备脚本/深信服防火墙网页/用户模式.py

import timefrom ..基础接口 import 用户模式from ..基础接口 import 异常from ..网页接口 import 图片class C用户模式(用户模式.I用户模式): def f登录(self, a用户名, a密码): w用户名 = self.m设备.f查找("//*[@id=/"user/"]") w用户名.f输入(a用户名) w密码 = self.m设备.f查找("//*[@id=/"password/"]") w密码.f输入(a密码) w验证码 = self.m设备.f查找("//*[@id=/"verify/"]") w登录 = self.m设备.f查找("//*[@id=/"button/"]") v验证码 = 图片.f手动输入验证码(w验证码, 4) if v验证码: w登录.f点击() time.sleep(1) # 结束 self.m设备.f查找_直到('//*[@id="ext-gen100"]') time.sleep(1)

切换模式,模式组

网页有一个特点,不管处于哪个模式,总能从最顶级模式一步步进入更深的模式。看一下路由器的主界面就能发现,一级菜单总是在任何模式中出现。

手动切换模式的过程
照着上面的思路,把每一个模式名称和相应按钮的id属性都记下来,按照进入顺序写成元祖。

网络设备脚本/普联网页/模式.py

class C模式wdr5620: c网络状态 = ("netStateMbtn",) c设备管理 = ("routeMgtMbtn",) c设备管理_主人网络 = ("routeMgtMbtn", "linkedEpt_rsMenu") c设备管理_已禁设备 = ("routeMgtMbtn", "limitedEpt_rsMenu") c应用管理 = ("appsMgtMbtn",) c路由设置 = ("routerSetMbtn",) c路由设置_tplinkid = ("routerSetMbtn", "cloudAnt_rsMenu") c路由设置_上网设置 = ("routerSetMbtn", "network_rsMenu") c路由设置_无线设置 = ("routerSetMbtn", "wireless2G_rsMenu") c路由设置_lan口设置 = ("routerSetMbtn", "lanSet_rsMenu") c路由设置_dhcp服务器设置 = ("routerSetMbtn", "dhcpServer_rsMenu") c路由设置_修改管理员密码 = ("routerSetMbtn", "changeWebPwd_rsMenu") c路由设置_备份和载入配置 = ("routerSetMbtn", "bakRrestore_rsMenu") c路由设置_重启和恢复出厂 = ("routerSetMbtn", "reBootSet_rsMenu") c路由设置_系统日志 = ("routerSetMbtn", "sysLog_rsMenu")然后在设备类中实现切换过程

网络设备脚本/普联网页/设备.py

class C设备: def __init__(self, a连接, a型号, a版本): #省略其他代码 self.ma模式 = [] def f切换模式(self, aa模式: tuple): if self.ma模式 == aa模式: return for v模式 in aa模式: v元素 = self.f查找(f"//*[@id='{v模式}']") v元素.f点击() self.ma模式 == aa模式其实这里还可以优化一下,从最顶级开始逐级判断是否不同模式,然后从那个不同的地方开始切换,这样可以减少点击次数,加快切换模式的速度。我懒得优化了,反正python很慢,网络也很慢,所以慢就慢了。

在网页中敲命令

在适配各种型号的过程中,发现一个很神奇的型号 思科c7200,它的网页界面非常简单,虽说提供网页操作,但是浏览器打开一看还是敲命令。

既然是敲命令,那么可以直接把命令行的代码拿来用。具体做法是继承命令行设备,重写输入输出函数,把命令行敲命令的过程转换成网页中敲命令。

网络设备脚本/思科网页/命令行设备.py

import selenium.webdriver #seleniumfrom ..网页接口 import 设备 as 网页设备from ..命令行接口 import 命令from ..命令行接口 import 设备 as 命令行设备from ..思科命令行.常量 import *from . import 命令行用户模式 as 实现用户模式class C设备(网页设备.I设备, 命令行设备.I设备): """适用于: c7200""" def __init__(self, a连接, a型号, a版本): 网页设备.I设备.__init__(self, a连接) 命令行设备.I设备.__init__(self, a连接) self.m型号 = a型号 self.m版本 = a版本 self.ma模式栈 = [] #网页 def fg命令框(self, a包装 = True): w命令框 = self.f查找("/html/body/pre/form/dt/input[2]", a包装) return w命令框 #适配 def f输出(self): w输出框 = self.f查找("/html/body/pre/form/dt/pre", False) return w输出框.text def f输入(self, a文本): w命令框 = self.fg命令框(False) w命令框.send_keys(str(a文本)) def f刷新(self): w命令框 = self.fg命令框(False) w命令框.clear() w命令框.send_keys(selenium.webdriver.common.keys.Keys.ENTER) #模式 def f模式_用户(self): return 实现用户模式.C用户模式(self) #命令 def f退出(self): self.f执行命令("exit") def f执行命令(self, a命令): w命令框 = self.fg命令框(False) w命令框.send_keys(str(a命令)) w命令框.send_keys(selenium.webdriver.common.keys.Keys.ENTER) time.sleep(0.5) return self.f输出()这里有一部分模式类也要重写,比如用户模式,思科c7200的登录过程在打开网页的过程中弹出的登录框中输入用户名密码登录。这个登录框由浏览器控制,不属于网页元素。所以用户模式的 f登录 要省略掉。

网络设备脚本/思科网页/命令行用户模式.py

import timefrom ..思科命令行 import 用户模式class C用户模式(用户模式.C用户模式): def __init__(self, a): 用户模式.C用户模式.__init__(self, a) def f登录(self): time.sleep(0.5) self.m设备.f输入_结束符() def f模式_全局配置(self): from . import 命令行全局配置 return 命令行全局配置.C全局配置(self)要重写的还有全局配置模式,思科网页中进入全局配置模式的命令是“configure http”,和命令行不一样,所以进入模式命令也得改:

网络设备脚本/思科网页/命令行全局配置.py

from ..思科命令行 import 全局配置class C全局配置(全局配置.C全局配置): def __init__(self, a): 全局配置.C全局配置.__init__(self, a) def fg进入命令(self): return "configure http"

应用示例:修改接口地址

这一节应用示例代码就是本篇文章的封面图,这段代码最早出自网络设备脚本的第一篇文章《用python编写控制网络设备的自动化脚本1:框架设计》的封面图,就是为了展示网络设备脚本的目标:以相同的代码以各种方式来控制不同的网络设备。这个项目从一开始到现在,代码内部架构已经发生了很多变化,把封面代码改一改还能用真是个奇迹。

我手头上没有合适的真机可以做实验,所以只能拿模拟器做实验。在模拟器中摆一台思科c7200路由器,把路由器连接到真实网络,相关配置加上。

ip http serverusername asdf privilege 15 password 123456interface fastethernet 0/0 ip address dhcpline vty 0 4 login local检查路由器获取到的地址,然后确认电脑是否能访问路由器。

写代码

import cflw代码库py.cflw网页连接 as 网页连接import 网络设备脚本 as 脚本import 网络设备脚本.思科 as 思科def main(): v连接 = 网页连接.f创建连接("http://asdf:123456@192.168.44.129") v设备 = 思科.f创建设备(v连接, 思科.E型号.c7200, 15.2) #用户 v用户模式 = v设备.f模式_用户() v用户模式.f登录() #全局配置 v全局配置 = v用户模式.f模式_全局配置() #接口配置l0 v接口配置 = v全局配置.f模式_接口("l0") v接口配置.fs网络地址4("1.1.1.1/32") #接口配置f0/0 v接口配置 = v全局配置.f模式_接口("f0/1") v接口配置.fs网络地址4("12.0.0.1/24") #用户配置 v用户配置 = v全局配置.f模式_用户("asdf") v用户配置.fs密码("123456")if __name__ == "__main__": main()运行结果:



关键词:自动化,脚本,设备,编写,控制,网络

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭