【Python爬虫】—— Selenium 的基本使用

为何使用 Selenium

在很多情况下,Ajax 请求的接口通常会包含加密的参数。由于接口的请求加上了参数,如果不深入分析并找到构造逻辑,是难以直接模拟这些 Ajax 请求的。

此时解决方法通常有两种,一种是深挖其中的逻辑,把其中的构造逻辑完全找出来,再用 Python 复现,构造 Ajax 请求;另外一种方法就是直接通过模拟浏览器的方式,绕过这个过程。

此处可以使用 Selenium 工具模拟浏览器爬取。

Selenium 是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作,同时还可以获取浏览器当前呈现的页面源代码,做到可见即可爬。对于一些使用 JavaScript 动态渲染的页面来说,此种抓取方式非常有效。


Selenium的用法

声明浏览器对象:
from selenium import webdriver
browser = webdriver.Chrome() 
browser = webdriver.Firefox() 
browser = webdriver.Edge() 
browser = webdriver.Safari()

这样就完成了浏览器对象的初始化并将其赋值为 browser 对象。接下来要做的就是调用 browser 对象,让其执行各个动作以模拟浏览器操作。


访问页面:

可以用 get 方法来请求网页,只需要把参数传入链接 URL 即可。比如,这里用 get 方法访问百度,然后打印出源代码,代码如下:

import time
from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
print(browser.page_source)
time.sleep(10)
browser.close()

运行代码后会弹出 Chrome 浏览器并且自动访问百度,然后控制台会输出百度页面的源代码,10秒后浏览器关闭。


查找节点

Selenium 可以驱动浏览器完成各种操作,比如填充表单、模拟点击等。当我们想要完成向某个输入框输入文字的操作时,首先需要知道这个输入框在哪,而 Selenium 提供了一系列查找节点的方法,可以用这些方法来获取想要的节点,以便执行下一步动作或者提取信息。

单个节点

当我们想要从百度页面中提取搜索框节点,首先要观察它的源代码:
在这里插入图片描述

可以发现,它的 id 是 kw,name 是 wd,此外还有许多其他属性。此时我们就可以用多种方式获取它了。比如,find_element_by_name 代表根据 name 值获取,find_element_by_id 则是根据 id 获取,另外,还有根据 XPath、CSS 选择器等获取的方式:

from selenium import webdriver

browser=webdriver.Chrome()
browser.get('https://www.baidu.com')
input_first = browser.find_element_by_id('kw')
input_second = browser.find_element_by_xpath('//*[@id="kw"]')
print(input_first, input_second)
browser.close()

Output:

<selenium.webdriver.remote.webelement.WebElement (session="d2f6a6403d125ae6df4055652621ac0e", element="27afc75f-3751-482e-b62b-2721b02c9e98")>
<selenium.webdriver.remote.webelement.WebElement (session="d2f6a6403d125ae6df4055652621ac0e", element="27afc75f-3751-482e-b62b-2721b02c9e98")>
这里列出所有获取单个节点的方法:
find_element_by_id 
find_element_by_name 
find_element_by_xpath 
find_element_by_link_text 
find_element_by_partial_link_text 
find_element_by_tag_name 
find_element_by_class_name 
find_element_by_css_selector

另外,Selenium 还提供了 find_element 这个通用方法,它需要传入两个参数:查找方式 By 和值:

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

browser = webdriver.Chrome()
browser.get('https://www.taobao.com')
input_ = browser.find_element(By.ID, 'q')
print(input_)
browser.close()

这种查找方式的功能和上面列举的查找函数完全一致,不过参数更加灵活。

多个节点

如果在网页中有多个节点需要查找,要用 find_elements 这样的方法,例如要获取百度热榜的内容:

import time
from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
lis=browser.find_elements_by_xpath("//div[1]/div[1]/div[5]/div/div/div[3]/ul/li")
print(lis)
time.sleep(5)
browser.close()

Output:

[<selenium.webdriver.remote.webelement.WebElement (session="e72283ba0c520929c93f1a9075ebdd8f", element="d48cc9a8-c10b-4762-a8c3-b333d16c0050")>, 
<selenium.webdriver.remote.webelement.WebElement (session="e72283ba0c520929c93f1a9075ebdd8f", element="b130131c-fdd7-4e3f-af04-20a4b9007d5d")>, 
<selenium.webdriver.remote.webelement.WebElement (session="e72283ba0c520929c93f1a9075ebdd8f", element="b2251221-348d-4f28-ba07-46f22e14de1f")>, 
<selenium.webdriver.remote.webelement.WebElement (session="e72283ba0c520929c93f1a9075ebdd8f", element="98b63893-bdc6-473a-bcad-68301ad5e5de")>, 
<selenium.webdriver.remote.webelement.WebElement (session="e72283ba0c520929c93f1a9075ebdd8f", element="aa0d225e-fd9a-4b94-8900-7cad59849755")>, 
<selenium.webdriver.remote.webelement.WebElement (session="e72283ba0c520929c93f1a9075ebdd8f", element="667d8cb3-8500-4d7f-868b-38c0d90af961")>]

可以看到,得到的内容变成了列表类型,列表中的每个节点都是 WebElement 类型。

如果用 find_element 方法,只能获取匹配的第一个节点,结果是 WebElement 类型。
如果用 find_elements 方法,则结果是列表类型,列表中的每个节点是 WebElement 类型。

这里列出所有获取多个节点的方法:
find_elements_by_id 
find_elements_by_name 
find_elements_by_xpath 
find_elements_by_link_text 
find_elements_by_partial_link_text 
find_elements_by_tag_name 
find_elements_by_class_name 
find_elements_by_css_selector

节点交互

Selenium 可以驱动浏览器来执行一些操作,或者说可以让浏览器模拟执行一些动作。比较常见的用法有:输入文字时用 send_keys 方法,清空文字时用 clear 方法,点击按钮时用 click 方法,举个例子:

from selenium import webdriver
import time

browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
input_ = browser.find_element_by_xpath('//div[1]/div[1]/div[5]/div/div/form/span[1]/input')
input_.send_keys('Python')
time.sleep(2)
input_.clear()
input_.send_keys('许继元')
button = browser.find_element_by_xpath('//div[1]/div[1]/div[5]/div/div/form/span[2]/input')
button.click()
time.sleep(5)
browser.close()

动作链

除了针对某个节点执行的一些交互动作,还有另外一些操作,它们没有特定的执行对象,比如鼠标拖拽、键盘按键等,这些动作用另一种方式来执行,那就是动作链。举个例子,现在要实现将某个节点从一处拖拽到另外一处:

from selenium import webdriver
from selenium.webdriver import ActionChains

browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')
source = browser.find_element_by_xpath('//*[@id="draggable"]')
target = browser.find_element_by_xpath('//*[@id="droppable"]')
actions = ActionChains(browser)
actions.drag_and_drop(source, target)
actions.perform()

执行 JavaScript

Selenium API 并没有提供实现某些操作的方法,比如下拉进度条。但它可以直接模拟运行 JavaScript,此时使用 execute_script 方法即可实现:

from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')

这里利用 execute_script 方法将进度条下拉到最底部,然后弹出 alert 提示框。

有了这个方法,基本上 API 没有提供的所有功能都可以用执行 JavaScript 的方式来实现了。


获取节点信息

通过 page_source 属性可以获取网页的源代码,接着使用解析库提取信息。但 Selenium 提供了选择节点的方法,并且返回的是 WebElement 类型,则它也有相关的方法和属性来直接提取节点信息,如属性、文本等。这样就可以不用通过解析源代码来提取信息了。

获取属性

先选中节点,再使用 get_attribute 方法来获取节点的属性:

from selenium import webdriver

browser = webdriver.Chrome()
url = 'https://www.baidu.com/'
browser.get(url)
logo = browser.find_element_by_class_name('s-hotsearch-content')
print(logo)
print(logo.get_attribute('id'))

Output:

<selenium.webdriver.remote.webelement.WebElement (session="93869ad40b93b047111cb97606815423", element="8b32fd1f-b592-4c4f-a651-63f19caaa524")>
hotsearch-content-wrapper
获取文本值

每个 WebElement 节点都有 text 属性,直接调用这个属性就可以得到节点内部的文本信息:

from selenium import webdriver

browser = webdriver.Chrome()
url = 'https://www.baidu.com/'
browser.get(url)
input_ = browser.find_element_by_class_name('hot-title')
print(input_.text)

Output:

百度热榜

获取 ID、位置、标签名、大小

WebElement 节点还有其他属性,比如 id 属性可以获取节点 id,location 属性可以获取该节点在页面中的相对位置,tag_name 属性可以获取标签名称,size 属性可以获取节点的大小,也就是宽高,例如:

from selenium import webdriver

browser = webdriver.Chrome()
url = 'https://www.bilibili.com/'
browser.get(url)
input_ = browser.find_element_by_class_name('primary-menu-itnl')
print(input_.id)
print(input_.location)
print(input_.tag_name)
print(input_.size)

Output:

7ad2640f-b1cf-4635-8178-eddce14e73b4
{'x': 10, 'y': 155}
div
{'height': 108, 'width': 999}

切换 Frame

网页中有一种节点叫作 iframe,也就是子 Frame,相当于页面的子页面,它的结构和外部网页的结构完全一致。Selenium 打开页面后,默认是在父级 Frame 里面操作,而此时如果页面中还有子 Frame,Selenium 是不能获取到子 Frame 里面的节点的。这时就需要使用 switch_to.frame 方法来切换 Frame:

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException

browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
browser.switch_to.frame('iframeResult')
try:
    logo = browser.find_element_by_class_name('logo')
except NoSuchElementException:
    print('NO LOGO')
browser.switch_to.parent_frame()
logo = browser.find_element_by_class_name('logo')
print(logo)
print(logo.text)

首先通过 switch_to.frame 方法切换到子 Frame 里面,然后尝试获取子 Frame 里的 logo 节点(实际上是不能找到的),若找不到,就会抛出 NoSuchElementException 异常。接下来切换回父级 Frame,然后重新获取节点,则可以成功获取。

Output:

NO LOGO
<selenium.webdriver.remote.webelement.WebElement (session="70eb5ef47851fceefb1b7cd9a0c5026e", element="a4000bc2-1bb7-4e88-960d-a61a7b69eb7c")>
RUNOOB.COM

当页面中包含子 Frame 时,如果想获取子 Frame 中的节点,需要先调用 switch_to.frame 方法切换到对应的 Frame,然后再进行操作。


延时等待

在 Selenium 中,get 方法会在网页框架加载结束后结束执行,此时如果获取 page_source,可能并不是浏览器完全加载完成的页面,如果某些页面有额外的 Ajax 请求,我们在网页源代码中不一定能成功获取到。所以,这里需要延时等待一定时间,确保节点已经加载出来。

这里等待的方式有两种:一种是隐式等待,一种是显式等待。

隐式等待

当使用隐式等待执行测试的时候,如果 Selenium 没有在 DOM 中找到节点,将继续等待,超出设定时间后,则抛出找不到节点的异常。换句话说,隐式等待可以在我们查找节点而节点并没有立即出现的时候,等待一段时间再查找 DOM,默认的时间是 0。例如:

from selenium import webdriver

browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.bilibili.com/')
input_ = browser.find_element_by_class_name('logo-img')
print(input_)

Output:

<selenium.webdriver.remote.webelement.WebElement (session="f8a1bdd5fca3e2e3f1d1081bdf8dd94a", element="dd78cc2c-bab0-4a8e-8215-d9d0dcdab8eb")>
显式等待

隐式等待的效果并没有那么好,因为只规定了一个固定时间,而页面的加载时间会受到网络条件的影响。

有一种更合适的显式等待方法,它指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就返回查找的节点;如果到了规定时间依然没有加载出该节点,则抛出超时异常。例如:

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

browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
wait = WebDriverWait(browser, 10)
input_ = wait.until(EC.presence_of_element_located((By.ID, 'q')))
button =  wait.until(EC.element_to_be_clickable((By.XPATH, '/html/body/div[2]/div/div/div[2]/div/div[1]/div[2]/form/div[1]/button')))
print(input_)
print(button)

首先引入 WebDriverWait 对象,指定最长等待时间,然后调用它的 until() 方法,传入要等待条件 expected_conditions。此处传入了 presence_of_element_located 条件,代表节点出现,其参数是节点的定位元组,也就是 ID 为 q 的节点搜索框。

因此,在 10 秒内如果 ID 为 q 的节点(即搜索框)成功加载出来,就返回该节点;如果超过 10 秒还没有加载出来,就抛出异常。

对于按钮,可以更改等待条件为 element_to_be_clickable(可点击),所以查找按钮时先查找 Xpath 为 /html/body/div[2]/div/div/div[2]/div/div[1]/div[2]/form/div[1]/button 的按钮,如果 10 秒内它是可点击的,就代表它成功加载出来了,则返回这个按钮节点;如果超过 10 秒还不可点击,也就是没有加载出来,就抛出异常。

Output:

<selenium.webdriver.remote.webelement.WebElement (session="1dd8bab4f5f4e1d26dc29e6fd9e33275", element="305397ec-fc99-4c4b-9762-cd1d4c94090c")> 
<selenium.webdriver.remote.webelement.WebElement (session="1dd8bab4f5f4e1d26dc29e6fd9e33275", element="d4118061-c3b4-44b0-8fea-0745d73c58fc")>

如果 10 秒内没有成功加载,则抛出 TimeoutException 异常。

等待条件:
等待条件含义
title_is标题是某内容
title_contains标题包含某内容
presence_of_element_located节点加载出,传入定位元组,如(By.ID, ‘p’)
visibility_of_element_located节点可见,传入定位元组
visibility_of可见,传入节点对象
presence_of_all_elements_located所有节点加载出
text_to_be_present_in_element某个节点文本包含某文字
text_to_be_present_in_element_value某个节点值包含某文字
frame_to_be_available_and_switch_to_it frame加载并切换
invisibility_of_element_located节点不可见
element_to_be_clickable节点可点击
staleness_of判断一个节点是否仍在DOM,可判断页面是否已经刷新
element_to_be_selected节点可选择,传节点对象
element_located_to_be_selected节点可选择,传入定位元组
element_selection_state_to_be传入节点对象以及状态,相等返回True,否则返回False
element_located_selection_state_to_be传入定位元组以及状态,相等返回True,否则返回False
alert_is_present是否出现Alert
invisibility_of_element_located节点不可见

前进后退

Selenium 使用 back 方法后退,使用 forward 方法前进:

import time
from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
browser.get('https://www.taobao.com/')
browser.get('https://www.python.org/')
browser.back()
time.sleep(2)
browser.forward()
time.sleep(2)
browser.close()

Cookies

Selenium 可以方便地对 Cookies 进行操作,例如获取、添加、删除 Cookies 等:

from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())

Output:

[{'domain': '.zhihu.com', 'expiry': 1594790583, 'httpOnly': False, 'name': '_gid', 'path': '/', 'secure': False, 'value': 'GA1.2.1462052348.1594704184'}, {'domain': '.zhihu.com', 'expiry': 1657776183, 'httpOnly': False, 'name': '_ga', 'path': '/', 'secure': False, 'value': 'GA1.2.1542833660.1594704184'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'secure': False, 'value': '031b5396d5ab406499e2ac6fe1bb1a43|1594704183|1594704182'}, {'domain': '.zhihu.com', 'expiry': 1626240183, 'httpOnly': False, 'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1594704184'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1594704184'}, {'domain': '.zhihu.com', 'expiry': 1594704243, 'httpOnly': False, 'name': '_gat_gtag_UA_149949619_1', 'path': '/', 'secure': False, 'value': '1'}, {'domain': '.zhihu.com', 'expiry': 1689312182, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '"ABBSJ1oFkxGPTirgBoF55isXJsyZ7AHpPfI=|1594704182"'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'SESSIONID', 'path': '/', 'secure': False, 'value': '9dviPRsE7McwBJnMo9mpFtJwci26vXThpMAk4aguPv2'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': '99a2430a-941e-49f1-bc70-132ac12adda6'}, {'domain': '.zhihu.com', 'expiry': 1657776182, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': '44078cd7-5d9b-4f68-a673-6dd9532aad61'}]
[{'domain': '.www.zhihu.com', 'httpOnly': False, 'name': 'name', 'path': '/', 'secure': True, 'value': 'germey'}, {'domain': '.zhihu.com', 'expiry': 1594790583, 'httpOnly': False, 'name': '_gid', 'path': '/', 'secure': False, 'value': 'GA1.2.1462052348.1594704184'}, {'domain': '.zhihu.com', 'expiry': 1657776183, 'httpOnly': False, 'name': '_ga', 'path': '/', 'secure': False, 'value': 'GA1.2.1542833660.1594704184'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'secure': False, 'value': '031b5396d5ab406499e2ac6fe1bb1a43|1594704183|1594704182'}, {'domain': '.zhihu.com', 'expiry': 1626240183, 'httpOnly': False, 'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1594704184'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'secure': False, 'value': '1594704184'}, {'domain': '.zhihu.com', 'expiry': 1594704243, 'httpOnly': False, 'name': '_gat_gtag_UA_149949619_1', 'path': '/', 'secure': False, 'value': '1'}, {'domain': '.zhihu.com', 'expiry': 1689312182, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '"ABBSJ1oFkxGPTirgBoF55isXJsyZ7AHpPfI=|1594704182"'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'SESSIONID', 'path': '/', 'secure': False, 'value': '9dviPRsE7McwBJnMo9mpFtJwci26vXThpMAk4aguPv2'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': '99a2430a-941e-49f1-bc70-132ac12adda6'}, {'domain': '.zhihu.com', 'expiry': 1657776182, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': '44078cd7-5d9b-4f68-a673-6dd9532aad61'}]
[]

选项卡管理

在访问网页的时候,我们通常会开启多个选项卡。而在 Selenium 中也可以对选项卡进行操作:

import time
from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.execute_script('window.open()')
print(browser.window_handles)
browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(3)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://python.org')

Output:

['CDwindow-9833CBA347F30F83AA57A71A2D98ECC7', 'CDwindow-4542CB4D2539341C9EF511F46A46EE6A']

首先访问百度,然后调用 execute_script 方法,传入 JavaScript 语句 window.open,新开启一个选项卡,调用 window_handles 属性获取当前开启的所有选项卡的代号列表。调用 switch_to.window 方法即可切换选项卡,参数是选项卡的代号。此处将第 2 个选项卡代号传入,即跳转到第 2 个选项卡,接下来在第 2 个选项卡下访问淘宝,然后重新调用 switch_to.window 方法返回第 1 个选项卡,再访问 Python 官网。


异常处理

在使用 Selenium 的过程中,难免会遇到一些异常,例如超时、节点未找到等错误,一旦出现此类错误,程序便不会继续运行了。这里我们可以使用 try except 语句来捕获各种异常。

首先,演示一下节点未找到的异常:

from selenium import webdriver

browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.find_element_by_id('hello')

Output:

NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"[id="hello"]"}
  (Session info: chrome=83.0.4103.116)

此处抛出了 NoSuchElementException 异常,通常代表节点未找到。

为了防止程序遇到异常而中断,我们需要捕获异常:

from selenium import webdriver
from selenium.common.exceptions import TimeoutException, NoSuchElementException

browser = webdriver.Chrome()
try:
    browser.get('https://www.baidu.com')
except TimeoutException:
    print('Time Out')
try:
    browser.find_element_by_id('hello')
except NoSuchElementException:
    print('No Element')
finally:
    browser.close()

Output:

No Element

反屏蔽

如今很多网站都加上了对 Selenium 的检测,来防止一些爬虫的恶意爬取。检测的基本原理是检测当前浏览器窗口下的 window.navigator 对象是否包含 webdriver 属性。在正常使用浏览器的情况下,这个属性是 undefined,一旦我们使用了 Selenium,Selenium 会给 window.navigator 设置 webdriver 属性。很多网站就通过 JavaScript 判断,如果 webdriver 属性存在,则直接屏蔽。

有一个典型的案例网站:https://antispider1.scrape.cuiqingcai.com/,它使用了上述原理实现了 WebDriver 的检测,如果使用 Selenium 直接爬取的话,则返回如下页面:
在这里插入图片描述
或许可以使用 JavaScript,通过调用 execute_script 方法把 webdriver 属性置空:

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

该 JavaScript 代码的确可以把 webdriver 属性置空,但 execute_script 调用该 JavaScript 语句实际上是在页面加载完毕之后才执行的,这太迟了,网站早在最初页面渲染之前就已经对 webdriver 属性进行了检测,所以上述方法不能达到效果。

然而在 Selenium 中可以使用 CDP(Chrome Devtools-Protocol,Chrome 开发工具协议)来解决该问题,通过 CDP 可以实现在每个页面刚加载时执行 JavaScript 代码,执行的 CDP 方法为 Page.addScriptToEvaluateOnNewDocument,然后传入上述的 JavaScript 代码即可在每次页面加载之前将 webdriver 属性置空。另外可以加入几个选项来隐藏 WebDriver 提示条和自动化扩展信息:

from selenium import webdriver
from selenium.webdriver import ChromeOptions

option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_experimental_option('useAutomationExtension', False)
browser = webdriver.Chrome(options=option)
browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
   'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'
})
browser.get('https://antispider1.scrape.cuiqingcai.com/')

Output:
在这里插入图片描述
对于大多数的情况,以上方法均可以实现 Selenium 反屏蔽。但对于一些特殊的网站,如果其有更多的 WebDriver 特征检测,则需具体排查。


无头模式

上述案例运行时,总会弹出一个浏览器窗口,虽然有助于观察页面爬取状况,但有时窗口弹来弹去也会形成干扰。

Chrome 浏览器从 60 版本已经支持了无头模式,即 Headless。无头模式在运行时不会弹出浏览器窗口,减少了干扰和一些资源(如图片等)的加载,所以在一定程度上节省了资源加载时间和网络带宽。

我们可以借助 ChromeOptions 来开启 Chrome Headless 模式:

from selenium import webdriver
from selenium.webdriver import ChromeOptions

option = ChromeOptions()
option.add_argument('--headless')
browser = webdriver.Chrome(options=option)
browser.set_window_size(1366, 768)
browser.get('https://www.baidu.com')
browser.get_screenshot_as_file('preview.png')

此处通过 ChromeOptions 的 add_argument 方法添加了参数 --headless,开启了无头模式。在无头模式下,最好设置窗口大小,接着打开页面,最后调用 get_screenshot_as_file 方法输出页面截图。

运行代码,Chrome 窗口没有弹出来,而代码依然正常运行,最后输出页面截图 preview.png
在这里插入图片描述
这样就在无头模式下完成了页面的抓取和截图操作。

以上就是 Selenium 的基本使用,有了 Selenium,处理 JavaScript 渲染的页面就不难了。


Reference:https://kaiwu.lagou.com/course/courseInfo.htm?courseId=46#/detail/pc?id=1675


版权声明:本文为weixin_45961774原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。