0%

爬虫简易教程(二)

书接上文,昨天写完爬虫简易教程(一)后,室友连夜完成了他的种子爬虫1号。然而,在爬取的过程中,他发现有些网页链接并没有显式地写在网页中,我进一步确认后发现,网页链接是通过与后端数据库交互来获取,这样的动态页面就不能通过requests库来获取到所有需要的内容。为了解决这个问题,助力室友完成他的种子爬虫2号,这篇文章将介绍自动化测试工具selenium库,通过模拟浏览器的点击等操作实现链接的跳转,从而爬取到需要的内容。

配置Chrome驱动器

这里仅介绍Chrome浏览器的配置方法,selenium库还支持包括Firefox浏览器、IE浏览器、Edge浏览器等主流浏览器,配置方法类似,不再赘述。

下载Chrome浏览器

可以直接前往官网进行下载。

下载Chrome驱动器

首先在Chrome浏览器的地址栏中输入chrome://version,查看Chrome浏览器的版本号,我的版本号是107.0.5304.88。

然后打开驱动器列表,找到适配浏览器版本号的最新的驱动器版本,我这里选择了107.0.5304.62版本。

将下载好的文件解压,得到chromedriver.exe文件,并将其放在与当前虚拟环境的Python解释器使用的python.exe文件相同的路径下。下面以Pycharm为例演示。

将鼠标悬浮在Pycharm窗口右下角解释器的位置。

可以显示解释器的路径,将chromedriver.exe文件放在同一路径下即可。

Selenium库的介绍

开始使用Selenium库

selenium库将浏览器自动化脚本进行封装,使其能够操纵浏览器实现启动、关闭、更改窗口大小、截屏、点击、悬浮、滚动等操作,就好像真的是用户在操作一样。

想要简单地使用selenium库实现网页爬取,可以参考以下代码。

1
2
3
4
5
6
7
8
from selenium import webdriver
from selenium.webdriver.common.by import By
browser = webdriver.Chrome() # 创建Chrome()实例
browser.get('https://www.baidu.com') # 获取https://www.baidu.com的网页窗口句柄
# 下面两行是selenium库在4.5.0版本的语法,旧版本语法可能有所不同
node1 = browser.find_element(By.CSS_SELECTOR, '#kw') # 使用css选择器筛选
node2 = browser.find_element(By.XPATH, '//*[@id="kw"]') # 使用xpath选择器筛选
browser.quit() # 删除Chrome()实例

如果想要获取并保存网页内容,可以使用如下代码。

1
2
3
content = browser.page_source
with open('page.html', 'w', encoding='utf-8') as f:
f.write(content)

需要注意的是,一个Chrome()实例可以对应多个网页窗口的句柄,而对实例的任何操作都视为对实例中当前窗口的操作,正如一个浏览器中可以包含多个窗口,在浏览器中点击就视为在当前窗口点击。由于selenium有这样的特性,在实际运用时,不仅可以获取到网页窗口的内容,还可以与窗口甚至浏览器本身产生交互,从而达到更加灵活的效果。

Selenium库与窗口的交互

在使用requests库时,只需要打开窗口就可以爬取内容,这也导致requests库只能用来爬取静态页面的内容。Selenium库相较于requests库的最大优势就在于能够模拟浏览器,与窗口中的各个元素交互,从而能够爬取到动态页面的内容。例如,当某些内容需要登录后才能查看时,就可以使用Selenium库模拟登录操作,”欺骗“服务器返回需要的内容。从理论上来说,Selenium库能够模拟真人对于浏览窗口的所有操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#-> 获取页面基本属性
print(browser.title) # 窗口的标题
print(browser.current_url) # 窗口的链接
print(browser.page_source) # 窗口的html源代码
#-> 窗口最大化
browser.maximize_window()
#-> 窗口最小化
browser.minimize_window()
#-> 全屏窗口,类似于按F11
browser.fullscreen_window()
#-> 设置窗口大小
browser.set_window_size(2000,1500)
#-> 获取窗口大小
print(browser.get_window_size())
#-> 设置窗口位置(浏览器左上角坐标)
browser.set_window_position(0,0)
#-> 获取窗口位置(浏览器左上角坐标)
browser.get_window_position()
#-> 窗口截图
browser.save_screenshot('screenshot.png')
#-> 控制页面滚动,以像素为单位,横向为x轴,纵向为y轴
browser.scrollTo(0,2000)
#-> 控制页面滚动到底部
browser.execute_script("window.scrollTo(0, document.body.scrollHeight)")

更多的Selenium模拟键盘、鼠标、触摸笔、滚轮的操作参见官方文档

定位元素并交互

定位元素可以直接使用以下代码,css语法和xpath语法详见爬虫简易教程(一),Selenium定位元素的语法糖详见官方文档

1
2
element = browser.find_element(By.CSS_SELECTOR, 'element')
element = browser.find_element(By.XPATH, 'element')

与元素交互有五种基本命令,下面给出简单的介绍,更详细的介绍参见官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#-> 点击操作(适用于任何元素)
element.click()
#-> 发送键位操作(适用于文本字段和内容可编辑元素)
element.send_keys('something')
# # send_keys操作不仅能够发送文本,对于文件类型的输入框元素,还能够发送文件
element.send_keys('example.jpg')
#-> 清除操作(适用于文本字段和内容可编辑元素)
element.clear()
#-> 提交操作(适用于表单元素,不建议使用,可以使用点击操作代替)
element.submit()
#-> 选择操作(适用于选择列表元素)
from selenium.webdriver.support.select import Select
select_list = Select(element)
select_list.select_by_index(0) # 通过索引选中选项,从0开始计算索引
select_list.select_by_value('value') # 通过value属性的值选中选项
select_list.select_by_visible_text('text') # 通过文本选中选项
print([x.accessible_name for x in select_list.options]) # 获取列表中的所有选项
print([x.accessible_name for x in select_list.all_selected_options]) # 获取列表中已选中的选项
print([x.accessible_name for x in select_list.first_selected_options]) # 获取列表中第一个选中的选项
select_list.deselect_all() # 取消选中所有选项
select_list.deselect_by_index(0) # 通过索引取消选中选项
select_list.deselect_by_value('value') # 通过value属性的值取消选中选项
select_list.deselect_by_visible_text('text') # 通过文本取消选中选项
iframe窗口

有时在定位元素时或与元素交互时会失败,这是由于存在iframe窗口。在这种情况下,需要先搜索并定位到iframe结点,切换到iframe窗口,再定位并与元素交互。

1
2
3
iframe_node = browser.find_element(By.CSS_SELECTOR, 'iframe')
browser.switch_to_frame(iframe_node)
# TODO:接下来就可以继续定位并与元素交互

如果想要切换出iframe窗口,可以使用以下代码。

1
2
browser.switch_to.parent_frame() # 切换回上一级窗口
browser.switch_to.default_content() # 切换回最顶级的窗口
显式等待与隐式等待

对于静态页面,在页面加载完成后,所有的页面元素就都可以定位;但是对于动态页面,由于存在懒加载等机制,需要定位的元素或是需要滚动到页面底部、或是需要等待一段时间才能够定位。因此,如果在打开一个页面后立即定位某元素,很有可能定位失败,需要设置等待时间使元素能够加载出来。

设置等待时间的方法一般有两种:显式等待和隐式等待。显式等待指的是无论如何都要等待一段时间,在这段时间之后再进行定位操作,隐式等待则设定一个超时阈值,在超时之前轮询元素,在超时之前找到元素或者达到超时阈值都会结束定位元素的操作。下面是示例代码。

1
2
3
4
# 显式等待,在需要等待的位置插入
time.sleep(1.5) # 睡眠1.5s
# 隐式等待,只需要初始设置即可
browser.implicitly_wait(10) # 超时阈值为10s

Selenium库与浏览器的交互

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
#-> 打开指定网页
browser.get('https://www.baidu.com')
#-> 刷新当前网页
browser.refresh()
#-> 在当前窗口打开新的网页,不创建新窗口
browser.get('https://www.zhihu.com')
#-> 在当前窗口回退到上一网页
browser.back()
#-> 在当前窗口前进到下一网页
browser.forward()
#-> 创建新窗口,在新窗口打开指定网页,默认窗口句柄仍指向原窗口
browser.execute_script("window.open('https://www.bilibili.com')")
#-> 切换默认窗口句柄
main_page_handle = browser.current_window_handle # 保存当前窗口的窗口句柄
handles = browser.window_handles # 获取浏览器打开的所有窗口的句柄,返回列表
browser.switch_to.window(handles[1]) # 将默认窗口句柄切换到最新打开的窗口
# # 需要注意的是,在连续打开多个窗口后,获取的句柄列表中各窗口句柄顺序与窗口打开顺序并不相同
# # 例如,首先使用get方法打开“网页1”,再使用execute_script方法依次打开“网页2”、“网页3”、“网页4”
# # 那么,在窗口句柄列表中的顺序是“网页1”、“网页4”、“网页3”、“网页2”
# # 因此,一般来说handle[1]代表了最新打开的窗口,而不是handle[-1]
# # 然而,如果需要在浏览器中进行更加复杂的切换窗口的行为,handle[1]就不一定代表最新打开的窗口
# # 例如,首先打开网页1、2、3、4,切换窗口,再打开网页5、6,再切换窗口,最后打开网页7、8
# # 那么,列表中的顺序是1、4、3、2、6、5、8、7,切换到不同的窗口对最终的顺序无影响
# # 上述表现是在Chrome环境下测试的,在其它浏览器中的表现可能不同
# # 如果认为上述规律过于复杂,也可以简单采用下述方法
# # 即每次只使用execute_script方法打开一个窗口,使用后立即关闭,就可以使用下述代码切换窗口
current_window_handle = browser.current_window_handle
handles = browser.window_handles
for handle in handles:
if handle != current_window_handle:
browser.switch_to.window(handle)
#-> 关闭窗口句柄指向的窗口,默认窗口句柄丢失,不再指向任何窗口,需要强制切换
browser.close() # 关闭当前窗口,此时无法使用browser再进行任何操作
browser.switch_to.window(main_page_handle) # 强制切换到主窗口,可以使用browser继续操作
#-> 删除浏览器实例,相当于关闭所有窗口
browser.quit()
无界面模式

使用上述代码编写的selenium自动化脚本在运行时模拟浏览器打开和关闭各种窗口,如果在运行时不希望显示这些窗口,可以使用selenium的无界面模式。一般来说,在调试时使用有界面模式,在运行正常后转为无界面模式。

1
2
3
4
5
6
7
# 创建无界面模式的选项
options = Options()
options.add_argument('--headless') # 第一种设置方法
options.headless = True # 第二种设置方法
options.set_headless() # 第三种设置方法
# 创建浏览器实例
browser = Chrome(options=options)
使用cookie登录

与requests库一样,Selenium库同样可以携带cookie信息访问服务器。

www.baidu.com为例,F12Network、刷新、点击www.baidu.comCookies,能够得到下述界面。

可以看到浏览器中cookie的表现形式,这里我们暂时只需要name和value。

同样地,使用爬虫简易教程(一)提到的方法提取cookie,能够得到下述界面。

可以发现两者是相互对应的。

Selenium库使用cookie的语法基于上述name和value,示例语法如下。

1
2
3
4
5
6
7
8
# 添加cookie
browser.add_cookie({'name':'BIDUPSID', 'value':''})
# 获取cookie
print(browser.get_cookie('BIDUPSID')) # 获取name的值为BIDUPSID的value值
print(browser.get_cookies()) # 获取所有cookie
# 删除cookie
browser.delete_cookie('BIDUPSID') # 获取name的值为BIDUPSID的cookie
browser.delete_all_cookies() # 删除所有cookie