Python - 爬虫实现网页自动登录、点击与页面的跳转

Hands, Can do everything

近段时间,因熊孩子沉迷于电视而致命学习成绩下降,遂有了在不影响家里老人正常观看电视的情况下对电视进行限制的需求,经过思索后准备利用爬虫技术来实现,即在熊孩子放学时自动登录网管交换机来对机顶盒进行限速,熊孩子上学后再解除针对机顶盒的限速。

English Version

现在的熊孩子岁数不大却非常聪明,可以自己开电视并换台寻找喜爱的电视节目,之前采用拨网线的暴力方法来禁止熊孩子无休止的观看电视,后因经常拨网线后忘了重新插上而导致老人无法看电视,遂此方法废弃了………

好在经过思索,发现家里的 IPTV 连在了 Netgear 网管交换机上,我们可以在交换机上对 IPTV 端口进行限速,以使电视无法正常观看。

说起爬虫,首先想到的就是 Python,经过一翻google,确定了使用SeleniumFirefox/Chrome来实现爬虫功能。

什么是Selenium

Github-Selenium

Selenium 是一个用于 Web 应用程序的测试工具。Selenium 直接调用浏览器来进行测试,就像真正的用户在操作一样。它支持 IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera,HtmlUnit,phantomjs,Android(需安装 Selendroid 或 appium),IOS(需安装ios-driver 或 appium)等。

Selenium 支持 C# / JavaScript / Java / Python / Ruby 开发语言。它使用 WebDriver 来操作浏览器进行网页测试。

在爬虫中 Selenium 主要用来解决 JavaScript 的渲染问题。

什么是 WebDriver

Webdriver 是一个用来和浏览器进行交互的编程接口,通过它可以操作浏览器打开或关闭、发送鼠标点击、模拟键盘输入等等。

W3C 定义了 WebDriver 规范。现在最流行的 WebDrver 为开源软件 Selenium WebDriver

WebDriver 包含多个模块:

  1. 支持多编程语言
  2. 自动化框架,提供网页的元素查找、点击、输入等自动化功能,减少重复编码。
  3. JSON 协议,自动化框架与浏览器驱动的中间层,它提供了跨平台跨语言的能力。
  4. 浏览器驱动,通过它来调用浏览器。
  5. 浏览器,对网页进行渲染。

安装

Selenium

1
2
:~$ apt-get install python3 python3-pip
:~$ pip3 install selenium

WebDriver

  • ChromeDriver
    1
    :`$ apt-get install chromium-driver
  • Firefox,自github-geckodriver下载。
    1
    2
    3
    :~$ wget https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz
    :~$ tar -xvf geckodriver-v0.26.0-linux64.tar.gz
    :~$ mv geckodriver /usr/local/bin/

Selenium 的使用方法

这里我们使用 Python 语言。

浏览网页

先来一段简单爬虫界的Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
# coding=utf-8

import time
from selenium import webdriver

print("初始化 ChromeDriver,并打开 Chrome")
driver = webdriver.Chrome()
print("打开 shixuen.com 网址")
driver.get("https://www.shixuen.com")
time.sleep(5)
print("关闭浏览器")
driver.close()

print("初始化 geckodriver 并打开 Firefox 浏览器")
driver = webdriver.Firefox()
driver.get("https://www.shixuen.com")
time.sleep(5)
driver.close()
  1. 声明浏览器
    1
    2
    3
    4
    from selenium import webdriver

    driver = webdriver.Chrome()
    driver = webdriver.Firefox()
    支持多平台
  2. 使用driver.get( “https://www.shixuen.com“ )来打开网站
    www.shixuen.com
  3. 关闭浏览器,driver.close()

模拟鼠标点击

我们再添加点新功能,打开https://www.shixuen.com后,点击文章VIM Plugin - YouCompleteMe

依旧先看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
# coding=utf-8

import time
from selenium import webdriver

print("初始化 ChromeDriver,并打开 Chrome")
driver = webdriver.Chrome()
print("打开 shixuen.com 网址")
driver.get("https://www.shixuen.com")
print("搜索指定文本的链接")
article = driver.find_element_by_link_text("VIM Plugin - YouCompleteMe")
print("点击此链接")
article.click()
time.sleep(5)
print("关闭浏览器")
driver.close()

关键代码为driver.find_element_by_link_text( “VIM Plugin - YouCompleteMe” ),搜索文字内容为VIM Plugin - YouCompleteMe的链接,找到后返回此节点的对象。

搜索指定元素

如网页节点的代码:
<a id="btn_apply" class="btn_class">Apply</a>

  • ID进行搜索,driver.find_element_by_id( “btn_apply” )
  • 链接的文本内容进行搜索,driver.find_element_by_link_text( “Apply” )
  • class进行搜索,driver.find_element_by_class_name( “btn_class” )
  • xpath进行搜索,driver.find_element_by_xpath( “//a[@id=’btn_apply’ and @class=’btn_class’]” )
  • /:从根节点开始进行搜索
  • //:搜索所有节点
  • ./:搜索本节点下的子节点

点击此节点

代码 article.click() 来执行鼠标点击操作。

VIM Plugin - YouCompleteMe

查找网页节点代码的方法

  1. 首先使用浏览器打开页面
  2. [F12]调出开发者工具
  3. 点击工具左上角的按钮对元素进行定位
    开发者工具

上面的代码算是爬虫界的Hello World吧。

登录并配置 Netgear 网管交换机

下面,进入本文的正题,登录并配置 Netgear 网管交换机。依旧不废话,先上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/env python3
# coding=utf-8

import time, sys, getopt, os
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select

_gs105e_rate_limit = {
"unlimit": 1,
"limit": 3
}
gs105e_conf = {
"url": "http://192.168.1.2",
"password": "0123456789",
"rate": _gs105e_rate_limit["limit"],
"port": "port2"
}

def browser( driver ):
print( "打开交换机网页" )
driver.get( gs105e_conf["url"] )

print( "输入密码" )
passwd_input = WebDriverWait( driver, 10 ).until( EC.presence_of_element_located( (By.ID,"password") ) )
passwd_input.send_keys(gs105e_conf["password"])

print("点击登录")
btn_login = driver.find_element_by_id("loginBtn")
bt_login.click()

print("点击 <QoS> 菜单")
menu_qos = WebDriverWait(driver,10).until( EC.presence_of_element_located( (By.ID, "QoS") ) )
menu_qos.click()

print("点击 <Qos 速率限制> 菜单")
menu_qos_ratelimit = driver.find_element_by_id("QoS_RateLimit")
menu_qos_ratelimit.click()

print("等待子页面加载完成")
time.sleep(4)

print("跳转至子页面")
iframe = driver.find_element_by_xpath("//iframe[@id='maincontent']")
driver.switch_to.frame( iframe )

print("点击端口对应的 <checkbox>")
WebDriverWait(driver,10).until( EC.presence_of_element_located( (By.NAME,gs105e_conf["port"]) ) ).click()

print("选择入站速率")
btn_select = driver.find_element_by_name("IngressRate")
Select( btn_select ).select_by_index( gs105e_conf["rate"] )

print("选择出站速率")
btn_select = driver.find_element_by_name("EgressRate")
Select( btn_select ).select_by_index( gs105e_conf["rate"] )

print("由子页面返回主页面")
driver.switch_to.default_content()

print("点击 <应用> 按钮")
btn_apply = driver.find_element_by_id("btn_Apply")
btn_apply.click()

print("点击 <退出> 按钮")
btn_logout = WebDriverWait(driver,10).until( EC.presence_of_element_located( (By.ID,"logout") ) )
btn_logout.click()

def main():
try:
driver = webdriver.Firefox()
browser(driver)
except:
print("error in script!")
finally:
print("Close Brwoser!")
driver.close()

if __name__ == "__main__":
main()

上面的代码已经可以实现登录 Netgear网管交换机并自动对 TV 端口进行限速了。每一步都写得非常清楚,只不过多了几个函数和页面间的跳转而已。

下面对代码进行解析:

  1. 使用driver.get( “url” )打开 Netgear 网管交换机的登录界面。
  2. 输入密码并登录。在这里使用了WebDriverWait( driver,10 ).until( EC.presence_of_element_located( (By.ID,”password”) ) ),它的意思就是在10秒内使用driver来获取IDpassword的元素,如果获取成功则返回元素对象,如果超时则报错。
    这里需要注意ByECWebDriverWait需要提前importBy.ID即以ID进行查找,同理还有By.NAMEBy.XPATHBy.CLASS_NAMEBy.LINK_TEXT等等。
    这里为什么要使用它呢?因为我们的网页需要加载时间,如果页面还未加载ID为 password 的元素,我们代码就会报错。所以上面那句代码也可以改为
    1
    2
    3
    print ("等待页面加载完成后,获取 ID 为 password 的元素")
    time.sleep(10)
    passwd_input = driver.find_element_by_id("password")
    passwd_input.send_keys( gs105e_conf[“password”] )则是模拟键盘输入字符
  3. 按顺序点击菜单 QoS --> 速率限制,见下图。
    Netgear 页面
  4. 因为QoS 速率限制是以iframe方式加载,所以我们需要先将driver的当前页面跳转至iframe上,使用driver.switch_to.frame( iframe )来进行跳转。见下图。
    跳转至子页面
  5. 点击CheckBox以选中端口,然后对修改入站速率与出站速率,这里因为出入站速率为下拉列表,所以这里需要使用selenium.webdriver.support.select类来进行选择,Select( btn_select ).select_by_index( 3 )为选择从上数第四选项,第一选项索引为0.
    端口限速
  6. iframe返回主页面并点击应用
    点击应用
  7. 应用完成后点击退出登录
    点击退出

完整代码

上面的代码必须在图形界面运行,因为其会弹出浏览器窗口。而我们的服务器因为没有图形界面运行时会报错,所以这里我们使用--headless选项来禁止浏览器加载图形界面,使其可以在终端里运行。

下面是最终代码,对上面的代码进行了修饰,并添加了选项功能,可以对 Netgear 网管交换机的某端口进行限速,也可以解除某端口的限速。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/env python3
# coding=utf-8

#####################################################
# > File Name: automanagetv.py
# > Author: haven200
# > Mail: haven200@gmail.com
# > Created Time: Saturday, November 16, 2019 AM10:14:30 HKT
#####################################################

import time, sys, getopt, os
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select


_gs105e_dict = {
"unlimit":1,
"limit":3,
"huawei":"port2",
"phicomm":"port5"
}
gs105e_conf = {
"url":"http://192.168.1.2",
"password":"0123456789",
"rate":_gs105e_dict["unlimit"],
"port":_gs105e_dict["huawei"],
"browser":""
}

def browser(driver):
print("open gs105 webpage")
driver.get(gs105e_conf["url"])
print("login")
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID,"password"))
).send_keys(gs105e_conf["password"])
driver.find_element_by_id("loginBtn").click()
print("goto Qos page")
WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID, "QoS"))).click()
driver.find_element_by_id("QoS_RateLimit").click()
time.sleep(4)
driver.switch_to.frame(driver.find_element_by_xpath("//iframe[@id='maincontent']"))
print("modify the rate")
WebDriverWait(driver,10).until(EC.presence_of_element_located((By.NAME,gs105e_conf["port"]))).click()
Select(driver.find_element_by_name("IngressRate")).select_by_index(gs105e_conf["rate"])
Select(driver.find_element_by_name("EgressRate")).select_by_index(gs105e_conf["rate"])
print("click Apply button")
driver.switch_to.default_content()
driver.find_element_by_id("btn_Apply").click()
WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID,"logout"))).click()

def main():
try:
if gs105e_conf["browser"] == "firefox":
print("Open [geckodriver]")
firefox_options = webdriver.FirefoxOptions()
firefox_options.add_argument('--headless')
firefox_options.add_argument("user-agent='Mozilla/5.0 (X11; Linux i686; rv:67.0) Gecko/20100101 Firefox/67.0'")
driver = webdriver.Firefox(options=firefox_options)
elif gs105e_conf["browser"] == "chrome" :
print("Open [chromedriver]")
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument("user-agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3831.6 Safari/537.36'")
driver = webdriver.Chrome(options=chrome_options)
else:
show_help()

browser(driver)
except:
print("error in script!")
finally:
print("Close Brwoser!")
driver.close()

def show_help():
print("automanagetv.py -r <unlimit|limit> -p [tv|phicomm] -b [firefox|chrome]")
print("made by haven200@gmail.com, version 0.0.1\n")
print(" -r, --rate limit: limit the network speed to 1Mb/s")
print(" unlimit: release network speed limit")
print(" -p, --port huawei: Huawei Set_Top_Box")
print(" phicomm: phicomm_n1 Set_Top_Box")
print(" -b, --browser firefox: use firefox")
print(" chrome: use chrome")
sys.exit()

if __name__ == "__main__":

if len(sys.argv) == 1: show_help()

if len(os.popen("whereis geckodriver | awk '{print $2}'").read()) > 5:
gs105e_conf["browser"] = "firefox"
elif len(os.popen("whereis chromedriver | awk '{print $2}'").read()) > 5:
gs105e_conf["browser"] = "chrome"

try:
opts, args = getopt.getopt(sys.argv[1:], "hr:p:b:", ["rate=", "port=", "browser="])
except getopt.GetoptError:
show_help()

for opt, arg in opts:
if opt == '-h':
show_help()
elif opt in ("-r", "--rate"):
if arg == "limit" or arg == "unlimit": gs105e_conf["rate"] = _gs105e_dict[arg]
elif opt in ("-p", "--port"):
if arg == "huawei" or arg == "phicomm": gs105e_conf["port"] = _gs105e_dict[arg]
elif opt in ("-b", "--browser"):
if arg == "firefox" or arg == "chrome": gs105e_conf["browser"] = arg
main()

脚本的使用方法:

1
2
3
4
# 对 tv 端口的速率限速
:~$ python3 automanagetv.py -r limit -p tv
# 恢复 TV 端口的速率
:~$ python3 automanagetv.py -r unlimit -p tv

最后,我们在Cron里添加定时运行即可。

1
2
3
4
5
:~$ crontab -e
00 12 * * 1-5 /etc/init.d/automanagetv.py -r limit -p tv
00 13 * * 1-5 /etc/init.d/automanagetv.py -r unlimit -p tv
00 19 * * 1-5 /etc/init.d/automanagetv.py -r limit -p tv
40 20 * * 1-5 /etc/init.d/automanagetv.py -r unlimit -p tv

周一至周五,中午12点至13点进行限速,晚上19:00至20:40进行限速。而周六、周日电视就属于熊孩子的了。


References:

  • selenium.org
  • testproject
  • hongkiat
  • csdn