Selenium学习笔记

在开始学习前,需要熟悉一下可能用到的工具和知识。要从复杂的 HTML 网页源码中找到对应的代码,就需要依赖浏览器自带的开发者模式(Chrome浏览器按F12就可以进行浏览器的开发者模式了),从网上找了一个开发者模式的使用教程,另外就是一些基本的 HTML 和 CSS 知识了,这个可以从 W3SCHOOL 上看看,至少知道 HTML 的一些tag,css 有哪些 selector。

1. Selenium安装

pip install Selenium

对应的 WebDriver 下载,ChromeDriver 下载,FirefoxDriver 下载,还有其他类型的 WebDriver,像 IE, PhantomJS 都可以从网上下载。选择对应的操作系统和版本,把可执行文件放到系统的执行路径(PATH)中,以免Selenium启动时找不到相应的 WebDriver。如果找不到的话,也可以指定 WebDriver 路径。

2. Selenium使用

一般Selenium可以用下面的代码来启动,这里以 ChromeDriver 浏览器为例。

from Selenium import webdriver  
from Selenium.webdriver.common.keys import Keys
from Selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
options.add_argument('--window-size=1920x1080')
driver = webdriver.Chrome(chrome_options=options)
driver = webdriver.CHrome('path/chromedriver', chrome_options=options)  # 设置具体的 WebDriver 路径

message.send_keys(Keys.BACKSPACE)    # 模拟键盘按键,发送按键信息

tabs = driver.window_handles
driver.switch_to_window(tab[1])

先是导入相应的模块,Options 是对应浏览器的启动参数,代码中添加了两个参数:--headless 无界面,--window-size=1920x1080 设置打开浏览器窗口大小。如果不添加 --headless 参数,运行 driver = webdriver.Chrome(chrome_options=options) 就会跳出一个浏览器窗口,此时窗口大小参数是没有效果的。如果没把相应的 ChromeDriver 放到对应的系统路径的话,这里应该手动指定相应的文件路径。

2.1 打开网页

启动浏览器的时候是打开了一个空白页,需要我们设置网址进行访问。

driver.get('https://www.zhihu.com/')

这里以知乎来作测试,打开知乎注册登录页面。

2.2 模拟键盘鼠标操作

打开的页面是一个提示注册的网页,可以模拟鼠标的点击操作,转到了登录页面,之后可以利用Selenium来模拟键盘输入,输入用户名和密码。

# 查找登录选项并点击
switch_to_login = driver.find_element_by_xpath("//span[contains(text(),'登录')]")
switch_to_login.click()
# 查找用户名和密码的输入框
input_user = driver.find_element_by_css_selector('input[name=username]')
input_pwd = driver.find_element_by_css_selector('input[name=password]')
input_user.send_keys('account_user')
input_pwd.send_keys('account_pwd')
# 查找登录选项并点击
button = driver.find_element_by_css_selector('button[type=submit]')  
button.click()

输入文字用 send_keys(),清空文字用 clear() 方法,如果想要输入回车等键可以使用 Keys

from selenium.webdriver.common.keys import Keys
element.send_keys(Keys.ENTER)        # 发送回车键
element.send_keys(Keys.CONTROL,'a')      # 发送组合键 ctrl + a

之后就可以看到正常的知乎内容了。要实现模拟登录的功能,主要是在要找到账户和密码的输入元件。

2.3 获取 Cookies

Selenium 经常用来模拟网页登录,之后就可以利用对应的 Cookies 进行后续的操作了。Cookies 简单来说是存储于你电脑上的变量,每次浏览器访问某个页面时,都会发送这个 Cookies,网站服务器就可以根据 Cookies 来识别用户。大多数网站需要登录的网站在用户验证成功之后都会设置一个 Cookies,只要这个 Cookies 存在,那用户就可以浏览访问网站的资源。所以可以借用 Selenium 中登录生成的 Cookies, 让 requests 来访问相关的网站。

import requests

headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Host': 'www.zhihu.com',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'}

cookeies = driver.get_cookies()
s = requests.Session()
s.headers = headers
for c in cookies:
    s.cookies.set(c['name'], c['value'])

rep = s.get('https://www.zhihu.com/')
print rep.status_code

借用前面 Selenium 中的 Cookies,reqeusts 就可以正常访问知乎首页了。

2.4 查找定位网页内容

driver 中提供了 2 种查找 element 的方法:find_elementfind_elements 。前者是返回找到的第一个 element,后者是返回所有条例条件的 element,保存在列表中。查找的定位方法也提供了许多种:

  • by_class_name, 按照 class 名查找
  • by_css_selector, 按照 CSS 选择器查找
  • by_id, 按源码中的 id 查找
  • by_link_text,根据元素中出现的文件内容查找,这里文本应该是完全一样
  • by_partial_link_text,和上面类似,不过这些是部分文本
  • by_name,根据 name 属性查找
  • by_tag_name,根据 tag 来查找,一般找到的会比较多
  • by_xpath,xpath 的查找方法,比较灵活,上手比较慢,可以参考这个XPath通俗教程

还有一个需要注意的是,查找到的 element 也包含相应的 find_element 方法,这样可以进行不同层级的查找,对于比较复杂的网页,可能一时找不到合适的方法,可以先定位到某个区域再进行缩小区域,找到目标 element。查找相关 element 这个比较花时间,需要进行尝试,还是推荐在 ipython 进行调试,各种合适的方法可以用 TAB 选择。

2.5 标签和frame切换

在查找元素时,会提示 NoSuchElementException 的错误提示,可是查看网页源码的时候发现,元素明明就在那里。这个时候可能是 frame 标签在搞鬼。这个时候就需要切入到 frame 中,再查找 element。

frame = driver.find_element_by_tag_name('frame')
driver.switch_to_frame(frame)
...
driver.switch_to_default_content()

要先找到适合的 frame ,再进行切换,操作完毕后,可以还可以用 switch_to_default_content 切换为原来的内容,更加详细的说明可以参考这篇博客Selenium之 定位以及切换frame(iframe)

有的时候点击网页的时候,会在新标签页打开,Selenium 中提供了标签切换的方法

tabs = driver.window_handles
driver.switch_to_window(tabs[1])
print driver.title

先是列出所有的标签页,然后切换到第 2 个标签页,最后把标签页的 title 打印出来。

2.5 等待页面加载

因为有些网页可能加载的时间比较长,在程序运行时,如果还没有加载后,就进行下一步可能就会出现问题。简单的话,可以使用 time 模块来设置等待时间,以使网页完全加载。不过因为网页加载会受到网速等其他因素影响,加载时间也会波动。Selenium 中提供了两种等待页面加载的功能- 隐式和显式。显式等待会让 WebDriver 等待满足一定的条件以后再进一步的执行。 而隐式等待让 Webdriver 等待一定的时间后再才是查找某元素。

显式等待

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver.get(url)
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
finally:
    driver.quit()

在抛出 TimeoutException 异常之前将等待 10 秒或者在 10 秒内发现了所查找的元素。 WebDriverWait 默认情况下会每 500 毫秒将查找一次直到结果成功返回。 expected_conditions 中包括了许多预期条件,可以根据实际情况选择合适的,这里一般会结合 By类来进行定位,定位方式同前面的查找 element 的方法,有 By.IDBy.CLASS_NAME 等:

  • title_is,检查网页的 title,需要输入文本进行匹配
  • title_contains,检查网页 title 中是否包含某个文本,需要输入文本
  • presence_of_element_located,某元素是否出现在DOM中,输入定位信息
  • presence_of_all_elements_located,某类元素全出现在DOM中,输入定位信息
  • visibility_of_all_element_located,某类元素都出现在DOM中并可见,输入定位信息
  • visibility_of,某个元素是否出现在DOM中并可见,输入某个 element 对象
  • visibility_of_any_elements_located,某类 element 至少有一个出现在DOM中并可见,输入定位信息
  • visibility_of_element_located,某元素是否出现在DOM中并可见,输入定位信息
  • invisibility_of_element_located,某个元素没出现在DOM或不可见,输入定位信息
  • text_to_be_present_in_element,某个元素文本包含某些文本,输入定位信息和某些文本
  • text_to_be_present_in_element_value,某个元素值包含某些文本,输入定位信息和某些文本
  • frame_to_be_available_and_switch_to_it,加载并切到到某 frame,输入定位信息
  • element_to_be_selected,某元素是否可选择,输入 element 对象
  • element_to_be_clickable,某元素可以被点击,输入定位信息
  • element_located_to_be_selected,某元素可以被选择,输入某 element 对象
  • element_selection_state_to_be,查看某元素的状态(是否被选择),输入 element 对象和判断的状态(True,False)
  • element_located_selection_state_to_be,同上,只是输入是定位信息和判断状态
  • alert_is_present,是否出现警告信息,没有输入参数
  • staleness_of,判断某元素是否在DOM中消失,传入 elemetn 对象

隐式等待

如果某些元素不是立即可用的,隐式等待是告诉WebDriver去等待一定的时间后去查找元素。 默认等待时间是0秒,一旦设置该值,隐式等待是设置该WebDriver的实例的生命周期。

from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(10) # seconds
driver.get(url)
myDynamicElement = driver.find_element_by_id("myDynamicElement")

2.6 网页截图

利用网页截图可以将网页保存下来,在验证码处理时也会用到。因为有些验证码,你当直接用 url 去获取时,验证码信息已经更新了,这个时候可以考虑用截图将验证码保存下来,用于后续的破解。如果还需要对图片进行处理时,可以结合 PIL 模块进行处理:

import base64
from PIL import Image

# 直接截图并保存
driver.get_screenshot_as_file('screenshot.png') 

# 将图片传给Image,并进行截图处理
screenshot = driver.get_screenshot_as_base64()
screenshot = Image.open(BytesIO(base64.b64decode(screenshot)))
im = screenshot.crop((100, 200, 300, 300))        # left, upper, right, lower进行截图
im.save('crop.png')

最后

我感觉 Selenium 对于网页操作的自动化确实很方便,不过因为需要打开一个浏览器进行操作,会比 requests 这种直接与服务器进行交互的慢,不过对于一些有复杂 Javascript 生成的动态网页确实很有优势。在操作时也需要对查找某个元素节点进行操作,可以不用管后面数据是如何与网站服务器如果传输的,所见即所得,但是如果有人对网页进行修改的话,那可能元素节点就需要进行重新定位了,感觉也很头痛。不过爬虫就这样,有时候你都已经写好了,才发现你要爬取的网站已经发生变化了

参考

Python 3网络爬虫开发实战
Selenium-Python中文文档