数据, 术→技巧, 研发

Python爬虫工具之Selenium

钱魏Way · · 1 次浏览

Selenium简介

Selenium是浏览器的自动化测试工具,与浏览器进行交互,实现对web应用的自动化测试,Selenium包括Selenium IDE, Selenium Webdriver 和 Selenium Grid三个工具。

  • Selenium IDE (Integrated Development Environment)是一个浏览器插件,提供脚本录制、脚本生成和回放功能,初次使用selenium的新手可以用它来做一些简单的测试,
  • Selenium Webdriver 是一个浏览器自动化框架,接受脚本命令并发送到浏览器(通过浏览器驱动来实现),支持多种语言(包括Java, Ruby, Python, PHP, JavaScript, C#等)和多种浏览器,并且支持windows,Linux,macOS等操作系统。
  • Selenium Grid实现在多个机器上并行运行 selenium,也就是同时在多个机器上执行测试,并且可以是不同的浏览器和操作系统(跨平台)。

这里主要介绍Webdriver,主要原因是我使用Selenium的主要目的是做数据的抓取。

Selenium Webdriver简介

Selenium Webdriver API实现脚本语言与浏览器之间的通信,Selenium Webdriver 架构包括四个基本组件:

  • Selenium Language Bindings/Selenium Client Library:Selenium语言绑定/客户端库
  • JSON Wire Protocol:JSON有线协议
  • Browser Driver:浏览器驱动
  • Browser:浏览器

Selenium支持多种语言,包括Ruby、Java、Python、C#、JavaScript、GO、Haskell、JavaScript、Perl、PHP、R和Dart。执行测试用例时,selenium 代码将被转换为JSON格式,发送给浏览器驱动。

开始使用 Selenium 前,需要了解一下一个自动化测试过程涉及的几个主要组成部分。它们是 WebDriver(Selenium 提供的针对各个语言的浏览器操作库)、Driver(浏览器驱动)和 Browser(浏览器)。

这三个部分的交互过程如下图所示:

可以看到,WebDriver 通过 Driver 来与 Browser 进行双向通信。即 WebDriver 通过 Driver 传递指令给 Browser;然后 WebDriver 再由 Driver 接收 Browser 的响应信息。

需要说明的是:该图展示的情形中, WebDriver 与 Browser(及 Driver) 位于同一主机。但使用 Selenium Grid 后,WebDriver 可与 Browser(及 Driver)位于不同的主机。

Python + Selenium 爬虫

环境搭建

安装Selenium

pip install selenium

下载驱动

由于Selenium需要对浏览器进行操作,因此除了Selenium,我们还需要下载浏览器驱动。需要说明的是,部分Selenium教程中使用的是PhantomJS,然而由于PhantomJS已于2018年后停止维护,因此不推荐使用。

Selenium支持大多数主流浏览器,对应的驱动可以在如下地址中下载:

浏览器的驱动与浏览器版本相关,因此需要下载对应版本的驱动。驱动下载后,需要将其加入环境变量中。

手动创建一个存放浏览器驱动的目录,如: E:\BrowserDriver , 将下载的浏览器驱动文件解压后,如chromedriver.exe、geckodriver.exe丢到该目录下,接着设置环境变量。将“E:\BrowserDriver”目录添加到Path的值中。

测试是否安装成功:

from selenium import webdriver

browser = webdriver.Chrome()
browser.get("https://www.baidu.com/")

Selenium快速体验

以下代码是使用Seleniu抓取豆瓣TOP250电影相关信息的示例:

from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import csv


# 创建Chrome浏览器对象
browser = webdriver.Chrome()

# 打开豆瓣电影TOP250页面
browser.get("https://movie.douban.com/top250")

# 创建一个列表来保存所有电影数据
movies = []

# 循环遍历所有页
while True:
    # 获取当前页面的电影信息
    movie_items = browser.find_elements(By.CSS_SELECTOR, ".item")
    for movie in movie_items:
        # 获取电影信息
        title = movie.find_element(By.CSS_SELECTOR, ".title").text
        rating = movie.find_element(By.CSS_SELECTOR, ".rating_num").text
        # 将电影信息添加到列表中
        movies.append([title, rating])

    # 查找是否有下一页按钮
    next_page = browser.find_elements(By.CSS_SELECTOR, ".next a")
    if next_page:
        # 如果有,点击下一页按钮
        next_page[0].click()
        # 等待页面加载完成
        time.sleep(2)
    else:
        # 如果没有下一页按钮,退出循环
        break

# 关闭浏览器窗口
browser.quit()

# 创建一个CSV文件并写入电影数据
with open("movies.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["Title", "Rating"])
    writer.writerows(movies)

Selenium使用说明

WebDriver 基础使用

WebDriver 的基础使用,我们主要利用官网提供的Web Form 示例页面,该页面包含文本输入框、下拉框、文件上传框、日期选择框等,来讲解如何使用 WebDriver 创建浏览器对象、打开页面、定位元素、输入内容和点击按钮等操作。

from unittest import TestCase
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select


class TestSeleniumForm(TestCase):
    def setUp(self) -> None:
        # 无痕模式的 Chrome
        options = webdriver.ChromeOptions()
        options.add_argument('--incognito')

        self.browser = webdriver.Chrome(options=options)
        self.addCleanup(self.browser.quit)

    def test_web_form(self) -> None:
        # 打开表单页面
        self.browser.get('https://www.selenium.dev/selenium/web/web-form.html')
        self.assertEqual(self.browser.title, 'Web form')

        # Text 输入
        text_input = self.browser.find_element(By.ID, 'my-text-id')
        text_input.send_keys('Selenium')

        # Password 输入
        password = self.browser.find_element(By.NAME, 'my-password')
        password.send_keys('Selenium')

        # Dropdown 选择 Two
        dropdown = Select(self.browser.find_element(By.NAME, 'my-select'))
        dropdown.select_by_value('2')

        # 选择文件
        file_input = self.browser.find_element(By.CSS_SELECTOR, 'input[name="my-file"]')
        file_input.send_keys('D:\\CodeHub\\SeleniumSpider\\movies.csv')

        # 日期选择
        date_input = self.browser.find_element(By.XPATH, '//input[@name="my-date"]')
        date_input.send_keys('04/21/2023')

        # 点击 Submit 按钮
        submit_button = self.browser.find_element(By.XPATH, '//button[@type="submit"]')
        submit_button.click()

        # 等待进入已提交页面
        WebDriverWait(self.browser, 10).until(EC.title_is('Web form - target page'))

        # 断言
        message = self.browser.find_element(By.ID, 'message').text
        self.assertEqual(message, 'Received!')

可以看到,我们使用 WebDriver 实现了对页面表单的自动化输入与提交。

下面总结一下,我们所使用 WebDriver 的几个关键方法。

browser 实例的创建

可以指定参数来创建一个特定浏览器实例。

self.browser = webdriver.Chrome(options=options)

页面打开

可以使用get方法来打开一个 URL。

self.browser.get('https://www.selenium.dev/selenium/web/web-form.html')

元素定位

可以使用 ID、NAME、CSS 选择器、XPATH 等多种方式定位页面元素。

self.browser.find_element(By.ID, 'my-text-id')
self.browser.find_element(By.NAME, 'my-password')
self.browser.find_element(By.CSS_SELECTOR, 'input[name="my-file"]')
self.browser.find_element(By.XPATH, '//input[@name="my-date"]')

对元素进行操作

可以对元素进行输入或点击操作。

text_input.send_keys('Selenium')
submit_button.click()

等待页面元素出现

可以使用WebDriverWait来等待页面的某个元素出现。

WebDriverWait(self.browser, 10).until(EC.title_is('Web form - target page'))

浏览器对象的销毁

最后需要调用quit方法来关闭浏览器窗口,释放资源。

self.browser.quit()

页面加载策略

Selenium WebDriver 的浏览器选项有三种页面加载策略可供选择,它们是:normal、eager和none。了解它们代表什么之前,先介绍一下加载及渲染一个 Web 页面大概有哪些阶段。

按事件分的话,一个网页的生命周期主要有DOMContentLoaded、load、beforeunload和unload这几个阶段。

  • DOMContentLoaded。HTML 文档已加载完成,DOM 树已构建完成,但依赖的脚本、图片、样式表、iFrame 等外部资源可能还没有加载完成。
  • Load。不仅 HTML 文档已加载完成,依赖的脚本、图片、样式表、iFrame 等外部资源均已加载完成。
  • Beforeunload。用户离开前的前置事件。
  • Unload。用户已经离开。

按document.readyState分的话,只有loading、interactive和complete这三个阶段。

  • loading。HTML 文档仍在加载。
  • interactive。HTML 文档已加载并解析完成,但依赖的脚本、图片、样式表、iFrame 等外部资源可能还没有加载完成。
  • complete。HTML 文档以及依赖的脚本、图片、样式表、iFrame 等外部资源均已加载完成。

下图将这两种方式组合到一起来看一下一个网页的生命周期:

可以看到,事件里的DOMContentLoaded对应document.readyState里的interactive;事件里的load对应document.readyState里的complete。而 Selenium WebDriver 支持的三种加载策略与事件和document.readyState的对应关系如下表所示:

Selenium 页面加载策略 对应的事件 对应的document.readyState
normal(默认值) load complete
eager DOMContentLoaded interactive
none Any(任何状态都可以)

可以看到,当访问一个 URL 时,Selenium WebDriver 的默认策略是等待整个页面全部加载完成(除了使用JavaScript在load事件后再动态添加内容)。在编写自动化测试用例时,如果测试逻辑不依赖外部资源的加载,即可以将页面加载策略从默认选项normal改为eager或none来加速测试过程。

更改 Selenium WebDriver 页面加载策略的示例 Python 代码如下:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.page_load_strategy = 'eager'  # 'none', 'normal'
driver = webdriver.Chrome(options=options)
driver.get('https://www.baidu.com')
driver.quit()

等待策略

通俗点讲,WebDriver 是一个告诉浏览器做什么的库。因 Web 页面具有一定的异步特性,且 WebDriver 不会实时跟踪 DOM 的状态;所以,有些情况下,定位元素时,可能会出现「no such element」错误。下面看一段代码:

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get('https://www.baidu.com/')
text_input = driver.find_element(By.ID, 'kw')
text_input.send_keys('Selenium' + Keys.RETURN)

# 会抛出 NoSuchElementException
first_result_title = driver.find_element(By.XPATH, '//div[@id="content_left"]/div[1]/h3').text
print(first_result_title)

driver.quit()

这段代码打开了百度首页,然后键入关键字Selenium后回车进行搜索,接着即找第一个结果的标题进行打印。运行该代码时,会抛出NoSuchElementException,原因是定位元素的时候,搜索结果页面还没有完全打开,因此未找到对应的元素。

遇到这样的问题怎么办呢?可以通过 Selenium WebDriver 提供的显式等待或隐式等待功能来解决。

显式等待

显式等待,即程序暂停执行直至传递的条件满足。显式等待非常适合被用来做 WebDriver 与 DOM 的状态同步。上面抛出「no such element」错误的代码可使用显式等待的方式改造为:

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

driver = webdriver.Chrome()
driver.get('https://www.baidu.com/')
text_input = driver.find_element(By.ID, 'kw')
text_input.send_keys('Selenium' + Keys.RETURN)

# 等待搜索结果展示
WebDriverWait(driver, timeout=True, poll_frequency=10).until(EC.presence_of_element_located((By.ID, 'content_left')))

# 不会抛出异常
first_result_title = driver.find_element(By.XPATH, '//div[@id="content_left"]/div[1]/h3').text
print(first_result_title)

driver.quit()

可以看到,我们新建了一个WebDriverWait对象(指定了超时时间),并使用expected_conditions.presence_of_element_located()方法为其设置了跳出条件。除此方法外,expected_conditions包下常用的方法还有expected_conditions.url_contains()与expected_conditions.title_is()等。

由于显示等待是针对 条件 的判断,在日常使用中,我们的很多操作都需要同步 DOM,因此 Selenium 针对这些常用的操作,预定义了其对应的 预期条件(Expected conditions),对 Python 接口来说,具体包含如下内容:

预期条件 释义
alert_is_present 预期出现提示框
element_located_selection_state_to_be 预期节点元素选择状态
element_selection_state_to_be 预期节点元素选择状态
element_located_to_be_selected 预期节点元素为选择状态
element_to_be_clickable 预期元素可点击(可见+使能)
element_to_be_selected 预期元素处于选中状态
frame_to_be_available_and_switch_to_it 预期 frame 可用,同时切换到该 frame 中
visibility_of 预期节点可见(节点必须已经加载到当前 DOM 上)
visibility_of_element_located 预期节点可见
visibility_of_all_elements_located 预期指定的所有节点可见
visibility_of_any_elements_located 预期至少一个指定的节点可见
invisibility_of_element 预期节点元素不可见或不存在
invisibility_of_element_located 预期节点元素不可见或不存在
new_window_is_opened 预期新开窗口,同时窗口数量增加
number_of_windows_to_be 预期窗口数量
presence_of_all_elements_located 预期所有节点加载完成
presence_of_element_located 预期节点元素加载完成(无需为可见状态)
staleness_of 等待直到预期元素脱离 DOM
text_to_be_present_in_element 预期节点文本
text_to_be_present_in_element_value 预期节点元素 value 值
title_contains 预期标题包含相关子串(区分大小写)
title_is 预期标题(完全匹配)
url_changes 预期 URL 更改
url_contains 预期当前 URL 包含相关子串(区分大小写)
url_matches 预期当前 URL 匹配指定模式
url_to_be 预期当前 URL 内容(完全匹配)

expected_conditions()类:

  • element_located_selection_state_to_be():判断一个元素的状态是否是给定的选择状态
  • element_selection_state_to_be():判断给定的元素是否被选中
  • element_located_to_be_selected():期望某个元素处于被选中状态
  • element_to_be_selected():期望某个元素处于选中状态
  • element_to_be_clickable():判断元素是否可见并且能被单击,条件满足返回页面元素对象,否则返回Flase
  • frame_to_be_available_and_switch_to_it():判断frame是否可用
  • invisibility_of_element_located():希望某个元素不可见或者不存在DOM中,满足条件返回True,否则返回定位到的元素对象
  • visibility_of_element_located():希望某个元素出现在DOM中并且可见,满足条件返回该元素的页面元素对象
  • visibility_of():希望某个元素出现在页面的DOM中,并且可见,满足条件返回该元素的页面元素对象
  • visibility_of_any_elements_located():希望某个元素出现在DOM中并且可见
  • presence_of_all_elements_located():判断页面至少有一个如果元素出现,如果满足条件,返回所有满足定位表达式的压面元素
  • presence_of_element_located(locator):判断某个元素是否存在DOM中,不一定可见,存在返回该元素对象
  • staleness_of(webelement):判断一个元素是否仍在DOM中,如果在规定时间内已经移除返回True,否则返回Flase
  • text_to_be_present_in_element():判断文本内容test是否出现在某个元素中,判断的是元素的text
  • text_to_be_present_in_element_value():判断text是否出现在元素的value属性值中
  • title_contains():判断页面title标签的内容包含partial_title,只需要部分匹配即可,包含返回True,不包含返回Flase
  • title_is():判断页面title内容是与传入的title_text内容完全匹配,匹配返回True,否则返回Flase

隐式等待

隐式等待是告诉 WebDriver 在查找元素时,若不存在,即轮询 DOM 一段时间。其一般在新建 WebDriver 时设置,对整个会话有效。上面抛出「no such element」错误的代码可使用隐式等待的方式改造为:

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

# 设置隐式等待时间
driver.implicitly_wait(10)

driver.get('https://www.baidu.com/')
text_input = driver.find_element(By.ID, 'kw')
text_input.send_keys('Selenium' + Keys.RETURN)

# 不会抛出异常
first_result_title = driver.find_element(By.XPATH, '//div[@id="content_left"]/div[1]/h3').text
print(first_result_title)

driver.quit()

implicitly_wait(10) 如果在规定时间内网页加载完成,则执行下一步,否则一直等到时间结束。

真实的测试场景,一般只建议使用显式等待。

强制等待

强制让浏览器等待,不管当前操作是否完成。

import time
time.sleep(3)

获取文本值或属性

不管是在做功能测试还是自动化测试,经常需要获取定位元素的文本值或属性。Selenium提供了page_source, title, current_url,text和get_attribute方法来实现这个目的。

示例代码:

from selenium import webdriver
from time import sleep

driver = webdriver.Firefox(executable_path ="F:\GeckoDriver\geckodriver")
driver.get("https://www.baidu.com")

print('Before search================')

# 打印当前页面源码
source = driver.page_source
print(source)

# 打印当前页面title
title = driver.title
print(title)


# 打印当前页面URL
now_url = driver.current_url
print(now_url)

driver.find_element_by_id("kw").send_keys("selenium")
driver.find_element_by_id("su").click()
sleep(1)

print('After search================')

# 再次打印当前页面title
title = driver.title
print(title)

# 打印当前页面URL
now_url = driver.current_url
print(now_url)

# 获取结果数目
user = driver.find_element_by_class_name('nums').text
keywords = driver.find_element_by_id("kw").get_attribute("value")

print(user)
print(keywords)

#关闭所有窗口
driver.quit()

元素定位与操作

定位与操作 DOM 中的元素是使用 Selenium 编写自动化测试用例的主要工作。

元素定位

Selenium WebDriver 提供 8 种基本的元素定位方法。

定位方法 描述
id 查找 id 属性与搜索值匹配的元素
name 查找 name 属性与搜索值匹配的元素
class name 查找 class 名包含搜索值的元素
css selector 查找与 CSS 选择器匹配的元素
link text 查找其可见文本与搜索值匹配的锚元素
partial link text 查找其可见文本包含搜索值的锚元素。如有多个,则仅选择第一个元素。
tag name 查找 tag 名与搜索值匹配的元素
xpath 查找与 XPath 表达式匹配的元素

如下为百度搜索框 input 标签的 HTML 代码:

<input id="kw" name="wd" class="s_ipt" maxlength="255" autocomplete="off" />

可使用如下几种方式来定位到该 input 元素:

driver.find_element(By.ID, 'kw')
driver.find_element(By.CLASS_NAME, 's_ipt')
driver.find_element(By.NAME, 'wd')
driver.find_element(By.XPATH, '//input[@name="wd"]')

此外,Selenium 在版本 4 引入了相对定位器,即可使用空间相对位置来定位一个元素,其可在传统定位器无法描述时使用。

这两个输入框的 HTML 代码如下:

<input type="text" name="my-text" id="my-text-id" />
<input type="password" name="my-password" autocomplete="off" />

若Password输入框采用传统方法不好定位,则可使用相对定位器来定位:

password_locator = locate_with(By.TAG_NAME, 'input').below({By.ID: 'my-text-id'})
driver.find_element(password_locator)

元素操作

Selenium 提供 4 个基本的元素操作命令。它们是:ClickSend KeysClearSelect

下面使用一个例子来演示如何使用这几个命令。

如下为百度关键字输入框和「百度一下」搜索按钮的 HTML 代码:

<input id="kw" name="wd" class="s_ipt" maxlength="255" autocomplete="off" />
...
<input type="submit" id="su" value="百度一下" class="bg s_btn" />

可使用如下命令进行关键字清除、键入关键字和点击搜索按钮操作:

input_text = driver.find_element(By.ID, 'kw')
input_text.clear()
input_text.send_keys('Selenium')
driver.find_element(By.ID, 'su').click()

关于 Select 命令的使用,同样使用 Selenium 官网的「Web 表单示例页面」作示例。

该页面上的Dropdown (select)是一个单选框,其 HTML 代码如下:

<select class="form-select" name="my-select">
  <option selected="">Open this select menu</option>
  <option value="1">One</option>
  <option value="2">Two</option>
  <option value="3">Three</option>
</select>

使用Select对象选择下拉选项的 Python 代码如下:

dropdown = Select(driver.find_element(By.NAME, 'my-select'))
dropdown.select_by_value('2')

浏览器操作

导航操作

进行浏览器导航操作的 Python 代码如下:

# 打开网址
driver.get('https://selenium.dev')

# 点击向后按钮
driver.back()

# 点击向前按钮
driver.forward()

# 点击刷新按钮
driver.refresh()

原生弹窗操作

可使用 Selenium WebDriver 来与三种原生的消息弹窗(Alert、Confirm 和 Prompt)交互。

下面,先看一下用于演示这三种弹窗的 HTML 代码:

<!DOCTYPE html>
<html>
  <head>
    <title>Alerts, Prompts and Confirmations test</title>
    <script>
      function exampleAlert() {
        alert("This is an example alert");
      }

      function exampleConfirm() {
        let confirmed = confirm("Do you want to confirm?");
        document.getElementById("confirmed").innerText = confirmed;
      }

      function examplePrompt() {
        let favoriteSport = prompt(
          "What is your favorite sport?",
          "Basketball"
        );
        document.getElementById("favorite-sport").innerText = favoriteSport;
      }
    </script>
  </head>
  <body>
    <table border="1">
      <tr>
        <td><a onclick="exampleAlert()">Click to see an example alert</a></td>
        <td></td>
      </tr>
      <tr>
        <td>
          <a onclick="exampleConfirm()">Click to see an example confirm</a>
        </td>
        <td><p id="confirmed"></p></td>
      </tr>
      <tr>
        <td><a onclick="examplePrompt()">Click to see an example prompt</a></td>
        <td><p id="favorite-sport"></p></td>
      </tr>
    </table>
  </body>
</html>

接着,看一下测试如上 HTML 页面三种弹窗的 Python 代码:

from unittest import TestCase
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class TestAlerts(TestCase):
    def setUp(self) -> None:
        self.driver = webdriver.Chrome()
        self.addCleanup(self.driver.quit)

    def test_alert(self) -> None:
        # 打开 Alerts 示例页面
        self.driver.get('file:///Users/larry/Desktop/alerts-test.html')

        # 点击超链接 "Click to see an example alert"
        self.driver.find_element(By.LINK_TEXT, 'Click to see an example alert').click()

        # 等待窗口弹出,获取 Alert 信息,点击 OK
        alert = WebDriverWait(self.driver, 10).until(EC.alert_is_present())
        alert_message = alert.text
        alert.accept()

        # 断言
        self.assertEqual(alert_message, 'This is an example alert')

    def test_confirm(self) -> None:
        # 打开 Alerts 示例页面
        self.driver.get('file:///Users/larry/Desktop/alerts-test.html')

        # 点击超链接 "Click to see an example confirm"
        self.driver.find_element(By.LINK_TEXT, 'Click to see an example confirm').click()

        # 等待窗口弹出,点击 OK
        alert = WebDriverWait(self.driver, 10).until(EC.alert_is_present())
        alert.accept()

        # 获取 `#confirmed` 文本
        confirmed = self.driver.find_element(By.ID, 'confirmed').text

        # 断言
        self.assertEqual(confirmed, 'true')

    def test_prompt(self) -> None:
        # 打开 Alerts 示例页面
        self.driver.get('file:///Users/larry/Desktop/alerts-test.html')

        # 点击超链接 "Click to see an example prompt"
        self.driver.find_element(By.LINK_TEXT, 'Click to see an example prompt').click()

        # 等待窗口弹出,输入信息,点击 OK
        alert = WebDriverWait(self.driver, 10).until(EC.alert_is_present())
        alert.send_keys('Football')
        alert.accept()

        # 获取 `#favorite-sport` 文本
        favorite_sport = self.driver.find_element(By.ID, 'favorite-sport').text

        # 断言
        self.assertEqual(favorite_sport, 'Football')

窗口与选项卡操作

可使用 Selenium WebDriver 来打开、关闭和切换窗口或选项卡。

操作窗口或选项卡的示例 Python 代码如下:

# 获取所有的窗口或选项卡句柄
driver.window_handles

# 获取当前窗口或选项卡的句柄
driver.current_window_handle

# 切换窗口或选项卡
driver.switch_to.window(handle)

# 新建窗口
driver.switch_to.new_window('window')

# 新建选项卡
driver.switch_to.new_window('tab')

# 关闭当前窗口或选项卡
driver.close()

Cookie 操作

有时候我们需要验证浏览器中cookie是否正确,因为基于真实cookie的测试是无法通过白盒和集成测试进行的。WebDriver提供了操作Cookie的相关方法,可以读取、添加和删除cookie信息。

查询、添加和删除 Cookie 的示例 Python 代码如下:

from selenium import webdriver

driver = webdriver.Chrome()

# 打开 URL
driver.get('https://www.baidu.com')

# 将 Cookie 添加到当前浏览器
driver.add_cookie({'name': 'foo', 'value': 'bar'})

# 获取所有的 Cookie
print(driver.get_cookies())

# 获取名为 foo 的 Cookie 信息
print(driver.get_cookie('foo'))

# 删除名为 foo 的 Cookie 信息
driver.delete_cookie('foo')

# 删除所有的 Cookie
driver.delete_all_cookies()

driver.quit()

复用 Cookie

如果我们使用 Selenium 模拟登录操作,当然是可行的,但是有些登录操作比较复杂,并且现在网站有相当多的登录验证都得人工进行操作才可以(比如图片识别…),用 Selenium 模拟登录通常来说是一个费力不讨好的事情,因为无论多复杂的登录操作,目的就是为了获取得到相应的 Cookie,而 Selenium 是有提供 Cookie 操作的 API 哦,那其实我们完全可以手动进行登录,然后直接从浏览器开发者工具抓取到需要的 Cookie 字符串,设置到 Selenium 中即可。具体代码如下所示:

# 切割字符串,获取每条 Cookie 键值
def str2Cookie(cookieStr):
    def getCookieInfo(cookie):
        return cookie.split('=', maxsplit=1)

    for cookie in cookieStr.split(';'):
        name, value = getCookieInfo(cookie.strip())
        yield {'name': name, 'value': value}

def imitateLogin(driver):
    # 手动抓取的 Cookie 字符串
    cookieStr = r''
    # 必须先访问页面,然后才能操作 Cookie
    driver.get('https://www.jianshu.com/')
    # 清空 Cookie
    driver.delete_all_cookies()
    # 解析 cookieStr,并添加到 selenium 当前会话的的 Cookie 中
    for cookie in str2Cookie(cookieStr):
        driver.add_cookie(cookie)
    # 刷新当前页面,使 Cookie 生效
    driver.refresh()
        
if __name__ == '__main__':
    driver = webdriver.Chrome()
    imitateLogin(driver)

巧用 Cookie

持久化 Cookie:上面内容我们是直接手动获取 Cookie,这种做法可能存在 Cookie 抓取不完全,导致某些页面无法访问。其实更好地做法是对 Cookie 进行持久化,我们只需使用 Selenium 模拟一个登录,然后持久化此时的 Cookie,下次再次登录时,直接加载这些 Cookie,无需进行真实登录操作。具体持久化方法如下所示:

# 持久化 Cookie
def saveCookies(cookies,filename='cookies.json'):
    with open(filename,mode='w',encoding='utf-8') as file:
        import json
        file.write(json.dumps(cookies))

# 加载 Cookie
def loadCookies(filename='cookies.json'):
    cookies = None
    try:
        with open(filename,mode='r',encoding='utf-8') as file:
            import json
            cookies = json.loads(file.read())
    except FileNotFoundError:
        cookies = None
    except PermissionError:
        cookies = None
    return cookies

if __name__ == '__main__':
    driver = webdriver.Chrome()
    # 需要先访问下网址
    driver.get('https://www.jianshu.com/')

    # 先获取持久化的 Cookie
    cookies = loadCookies()
    # Cookie 存在
    if cookies is not None:
        # 清空重置 Cookie
        driver.delete_all_cookies()
        # 添加 Cookie
        for cookie in cookies:
            driver.add_cookie(cookie)
        # 刷新一下
        driver.refresh()
    # Cookie 不存在,则进行真实登录
    else:
        # 此处进行真正的登录操作
        # 登录成功后,持久化此时的 Cookie
        saveCookies(driver.get_cookies())
        
    # 现在就是已登录状态

鼠标操作

Action Chains类常用于模拟鼠标的行为,比如单击,双击,拖拽等行为:

from selenium.webdriver.common.action_chains import ActionChains

常用方法:

  • click() 鼠标单击
  • click_and_hold() 鼠标单击长按
  • context_click() 右击
  • double_click() 双击
  • drag_and_drop_by_offset(source, xoffset, yoffset)
    • source: The element to mouse down.
    • xoffset: X offset to move to.
    • yoffset: Y offset to move to.
  • key_down() 按下某个键
  • key_up()放开某键

键盘操作

from selenium.webdriver.common.keys import Keys

常用按键(其他可以查看keys.py文件)

组合

  • send_keys(Keys.CONTROL,’a’) # 全选(Ctrl+A)
  • send_keys(Keys.CONTROL,’c’) # 复制(Ctrl+C)
  • send_keys(Keys.CONTROL,’x’) # 剪切(Ctrl+X)
  • send_keys(Keys.CONTROL,’v’) # 粘贴(Ctrl+V)

非组合

  • 回车键:Keys.ENTER
  • 删除键:Keys.BACK_SPACE
  • 空格键:Keys.SPACE
  • 制表键:Keys.TAB
  • 回退键:Keys.ESCAPE
  • 刷新键:Keys.F5

执行JavaScript代码

Selenium 虽然提供了很多 API 供我们操作浏览器,但是还是无法覆盖所有内容。因此,Selenium 提供了一项本人认为是杀手级功能的特性:执行 JavaScript 脚本

这项功能其实赋予了我们几乎可以完全操控浏览器的功能,Selenium WebDriver 提供的接口为:WebDriver.execute_script(script, *args)

示例:加载网页后,让其垂直滚动到最底部。

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://baidu.com')
# window.scrollTo(x,y)
driver.execute_script(f'window.scrollTo(0,{driver.get_window_size()["height"]});')

Chrome options参数

Chrome options 是配置chrome启动时属性的类。

设置 chrome 二进制文件位置 (binary_location)

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.binary_location = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
path = "C:\\Program Files (x86)\\chromedriver_win32\\chromedriver.exe"

driver = webdriver.Chrome(options = options, executable_path=path)

添加启动参数 (add_argument)

# 设置ua
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument('user-agent="Mozilla/5.0 (X11; Linux x86_64)   AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36"')
driver = webdriver.Chrome(chrome_options = options)
# 设置ip代理
from selenium import webdriver
options = webdriver.ChromeOptions ()
chromeOptions = webdriver.ChromeOptions()
chromeOptions.add_argument('--proxy-server=http://ip:port') # 设置ip还有端口  
driver = webdriver.Chrome(chrome_options=chromeOptions)
# 其他常用
chrome_options.add_argument('--user-agent=""')  # 设置请求头的User-Agent
chrome_options.add_argument('--window-size=1280x1024')  # 设置浏览器分辨率(窗口大小)
chrome_options.add_argument('--start-maximized')  # 最大化运行(全屏窗口),不设置,取元素会报错
chrome_options.add_argument('--disable-infobars')  # 禁用浏览器正在被自动化程序控制的提示
chrome_options.add_argument('--incognito')  # 隐身模式(无痕模式)
chrome_options.add_argument('--hide-scrollbars')  # 隐藏滚动条, 应对一些特殊页面
chrome_options.add_argument('--disable-javascript')  # 禁用javascript
chrome_options.add_argument('--blink-settings=imagesEnabled=false')  # 不加载图片, 提升速度
chrome_options.add_argument('--headless')  # 浏览器不提供可视化页面
chrome_options.add_argument('--ignore-certificate-errors')  # 禁用扩展插件并实现窗口最大化
chrome_options.add_argument('--disable-gpu')  # 禁用GPU加速
chrome_options.add_argument('–disable-software-rasterizer')
chrome_options.add_argument('--disable-extensions')  # 禁用扩展
chrome_options.add_argument('--start-maximized')  # 最大化

添加扩展应用 (add_extension, add_encoded_extension)

# 添加插件
extension_path = '插件路径'
chrome_options.add_extension(extension_path)

添加实验性质的设置参数 (add_experimental_option)

# 禁止图片加载
from selenium import webdriver
chrome_options = webdriver.ChromeOptions()
prefs = {"profile.managed_default_content_settings.images": 2}
chrome_options.add_experimental_option("prefs", prefs)
# 禁用保存密码
prefs = {"": ""}
prefs["credentials_enable_service"] = False
prefs["profile.password_manager_enabled"] = False
chrome_options.add_experimental_option ("prefs", prefs)
# 手机模式
option = webdriver.ChromeOptions()
option.add_argument('disable-infobars')
mobile_emulation = {"deviceName": "iPhone 6"}
option.add_experimental_option('mobileEmulation', mobile_emulation)
driver = webdriver.Chrome(chrome_options=option)

禁用弹窗

# 禁用浏览器弹窗
prefs = {  
'profile.default_content_setting_values' :  {  
    'notifications' : 2  
 }  
}  
chrome_options.add_experimental_option('prefs',prefs) 

截屏

通过搜索文档,可以发现,Selenium 提供了以下两种类型的截屏功能:

[WebElement.screenshot(filename)][WebElement.screenshot]:该方法可以对元素进行截屏,如下代码所示:

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 import ChromeOptions

options = ChromeOptions()
options.add_argument('headless')

with webdriver.Chrome(options=options) as driver:
    driver.get('https://www.jianshu.com')
    wait = WebDriverWait(driver,10)
    # 文章区域
    el = wait.until(EC.presence_of_element_located(
        (By.CSS_SELECTOR,'#list-container')
    ))
    # 浏览器窗口设置为元素大小,以保证能完全截取元素区域
    driver.set_window_size(el.size['width'],el.size['height'])
    el.screenshot('D:\\jianshu.png')

WebDriver.save_screenshot(filename):截取浏览器当前页面。具体代码如下所示:

options = ChromeOptions()
options.add_argument('headless')

with webdriver.Chrome(options=options) as driver:
    driver.get('https://www.jianshu.com')
    wait = WebDriverWait(driver,10)
    # 文章区域
    wait.until(EC.presence_of_element_located(
        (By.CSS_SELECTOR,'#list-container')
    ))

    width = driver.execute_script("return document.documentElement.scrollWidth")
    height = driver.execute_script("return document.documentElement.scrollHeight")
    print(f'page scroll size: {width} x {height}')
    # 将窗口设置为页面滚动宽高
    driver.set_window_size(width, height)
    print('screenshot done') if driver.save_screenshot('D:\\jianshu.png') else print('screenshot failed!!')

注:截屏时我们需要将窗口的宽高设置为元素/页面滚动宽高,这样就可以完整截取整个元素/页面内容,但一个前提是必须使用 Headless 模式,否则窃取的只是当前视口高度内容。

Selenium反爬措施

由于使用 Selenium 可以很方便对网页进行自动化操作,这同时也表示说 Selenium 是一个非常好用的爬虫处理器。

也因此,有一些网站就增加了对 Selenium 的检测,以禁止 Selenium 进行爬虫操作。

目前使用最多的检测 Selenium 爬取的方式为:通过检测浏览器当前页面的window.navigator,如果其包含有webdriver这个属性,那就表示使用了 Selenium 进行爬取,反之,如果检测到webdriver为undefined,则表示正常操作。

如果我们直接使用 Selenium 进行爬取,如下所示:

driver = webdriver.Chrome()
driver.get('https://antispider1.scrape.cuiqingcai.com/')
print(driver.page_source)

运行上述代码后,可以看到:Webddriver Forbidden。

而如果想解决这个问题,依据先前我们介绍的原理可知,只要我们能将window.navigator.webdriver设置为undefined即可,但是我们不能简单地调用如下代码进行设置:

driver.execute_script('Object.defineProperty(navigator, "webdriver", {get: () => undefined})')

因为WebDriver.execute_script(..)是在页面加载完成后才执行,在时序上慢于网页检测。

因此,我们的变量修改必须尽早地在页面加载完成时进行设置。

而 Selenium 中提供的相应机制为:CDP(即 Chrome Devtools-Protocol,Chrome 开发工具协议)

对应的接口为:WebDriver.execute_cdp_cmd(cmd, cmd_args)

对应于上述示例,如果我们想要尽早在页面加载完成时执行我们的 JavaScript 脚本,需要使用的 CDP 命令为:Page.addScriptToEvaluateOnNewDocument,具体的代码如下所示:

options = ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)

browser=webdriver.Chrome(options=options)
browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
    'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'
})
browser.get('https://antispider1.scrape.cuiqingcai.com/')

参考链接:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注