文章目录
一、环境配置
1. Python & Selenium
本文是基于 Pytohn-3.7.4
以及 selenium-4.11.2
进行测试的,可以直接通过 pip
命令安装 selenium
:
pip install selenium
进入 python
交互界面,引入 selenium
包,检查安装结果:
import selenium
print(selenium.__version__) # 4.11.2
2. Webdriver 浏览器驱动
在准备好 Python 环境之后,还需要安装浏览器程序,selenium
通过浏览器驱动的可执行文件对浏览器进行操作。
Windows
可以直接上对应浏览器的官方网站下载安装浏览器即可
Linux
在 Linux
则需要安装对应浏览器的可执行程序以及驱动程序。
浏览器可执行程序可以通过 yum
命令来安装,例如谷歌浏览器:
yum -y install google-chrome
安装完成之后,检查安装结果:
google-chrome --version
然后,还需要下载对应浏览器版本的驱动,在 Selenium 官方文档的一篇文章 的下方,也提供了几款比较流行的浏览器驱动的下载方式:
需要注意,如果没有浏览器程序和驱动版本没有对应,启动时将会直接报错,提示驱动版本不匹配,此时只需要下载替换为对应版本的驱动即可。
二、打开浏览器
Windows 环境下可以直接通过 webdriver.Chrome()
创建浏览器驱动对象打开浏览器,在拥有图形化界面的操作系统上运行时,你可以直接看到浏览器进程启动,弹出浏览器窗口,并且后续对浏览器的一系列操作都可以实时在界面上看到:
而 Linux 环境下如果直接打开浏览器:
from selenium import webdriver
driver = webdriver.Chrome()
浏览器启动将会直接报错,抛出 selenium.common.exceptions.WebDriverException: Message: unkdown error: Chrome failed to start: exited abnormally
的异常,并且提示 google-chrome is no longer running
,此时 ChromeDriver
认为浏览器崩溃了。
在官方文档中有提到,在没有图形化界面的操作系统中打开浏览器,必须指定 --headless
参数启动:
from selenium import webdriver
from seleinum.webdriver.chrome.options import ChromeOptions
# initialize options
options = ChromeOptions()
options.add_argument('--headless')
# create chrome object
driver = webdriver.Chrome(options=options)
此时浏览器没有报错,可以正常启动。
另外,根据你的系统或者是 Selenium 的版本,Selenium 可能会要求你明确指定谷歌浏览器驱动的位置,否则会提示找不到对应的驱动:
在初始化浏览器的时候,通过 service
参数设置驱动的位置即可:
service = webdriver.ChromeService()
在 Linux 或者 CentOS 等非图形化界面的操作系统执行脚本的时候,很容易遇到各种各样的问题,很多在 Windows 上面可以正常完成的流程,在 Linux 往往行为会不太一致,
三、页面请求
Seleinum 中通过驱动对象的 get
方法来请求一个页面:
driver.get("https://www.baidu.com")
此时,如果是 Windows 操作系统,你可以看到你的浏览器页面跳转到了百度的搜索首页。而如果你是在 Linux 操作系统等无图形化几面的系统的话,可以尝试通过查找页面内的某个元素,或者打印页面的标题来确定自己是否顺利访问到目标网址。
而如果参数是一个下载链接,你也可以通过这个方法来下载文件,默认下载路径是当前登录用户的 download
目录:
driver.get("your-picture-url")
Selenium 的 webdriver
的驱动对象提供了一些属性可以获取当前页面的一些信息,例如:
# 当前页面的标题
print(driver.title) # 百度一下,你就知道
# 当前页面的路由
print(driver.current_url) # http://www.baidu.com
四、元素定位、操作以及内容获取
1. 元素信息
现在已经顺利打开了一个页面,接下来可以尝试对页面的元素进行操作,Selenium 提供 find_element
方法对页面的元素进行定位:
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
ele = driver.find_element(by=By.ID, value="kw")
print(type(ele))
其中,by
表示的是元素定位的依据,value
为检索值,上方的代码即表示在页面中查找 id="kw"
的元素。
除了 By.ID
之外,Selenium 还支持下面的检索依据:
By.CLASS_NAME
,即元素的class
By.TAG_NAME
,即元素的标签名称,例如div
,a
,p
等By.LINK_TEXT
,可以查找链接的文本,定位特定元素,往往用于链接的点击,或者链接地址的获取By.CSS_SELECTOR
,通过 CSS 筛选器定位元素,功能类似 JavaScript 的document.querySelector
方法,可以通过例如input[name=“password”]
这样的方式定位元素,表示找到带有name="password"
的input
标签By.NAME
,通常是用于查找表单项元素,即找到带有对应name
属性的input
,textarea
,button
等页面交互元素
定位到元素之后,可以看到打印出来的变量类型如下:
selenium.webdriver.remote.webelement.WebElement
这是 selenium
定义的页面元素对象,这个对象中已经为我们提供了一些常用的属性可以获取页面元素的信息,例如:
print(ele.text) # html 元素包含的文本,例如 <div> text </div> 中的 text
print(ele.tag_name) # 元素标签名称,前文获取到的是百度的输入框,标签名为 input
print(ele.id) # 这个区别于元素的 'id' 属性,是在 selenium 中元素对象的唯一标识
print(ele.size) # 元素在页面中最终计算得到的尺寸,这里返回的输入框的尺寸为 {'height': 44, 'width': 550}
WebElement
还提供了 get_attribute
的方法,可以获取到元素所有的属性,例如 name
,id
,class
,src
,href
等,还是以百度的搜索首页 https://www.baidu.com/ 为例:
print(ele.get_attribute("id")) # kw
print(ele.get_attribute("class")) # soutu-btn
还可以通过 value_of_css_property
获取到元素所有的 CSS 属性:
print(ele.value_of_css_property("background-color")) # 获取背景颜色
print(ele.value_of_css_property("color")) # 获取字体颜色
print(ele.value_of_css_property("font-size")) # 获取字体大小
另外 WebElement
还提供了一些方法可以帮助我们判断当前定位到的元素的状态:
print(ele.is_enabled()) # 是否可用,即与 disabled 属性相反
# 结果同 print(not ele.get_attribute("disabled"))
print(ele.is_selected()) # 是否处于选中状态
print(ele.is_displayed()) # 是否显示
通过上面的方法,我可以实现例如找到当前网页的 DOM 中的图片,并下载:
# 如果有多个匹配项,find_element 默认返回第一个
# find_elements 可以返回多个,可以利用这个方法下载页面中所有图片
img_ele = driver.find_element(by=By.TAG_NAME, value="img")
img_src = img_ele.get_attribute("src")
driver.get(img_src)
2. 元素操作
find_element(s) 定位元素并操作
通过 WebElement
封装的方法可以对页面元素进行操作,例如我们利用上面获取到的输入框元素,模拟搜索操作:
from selenium.webdriver.common.keys import Keys
ele = driver.find_element(by=By.ID, value="kw")
# 模拟键盘输入,并回车模拟搜索
ele.send_keys("python selenium" + Keys.ENTER)
也可以通过链式调用将元素定位和操作结合起来:
# 模拟键盘输入搜索内容
driver.find_element(by=By.ID, value="kw").send_keys("python selenium")
# 点击百度首页的搜索按钮 “百度一下”,执行搜索
driver.find_element(by=By.CSS_SELECTOR, value="input[id='su']").click()
当然,如果你不满足于这些功能,你也可以直接编写 javaScript
去执行任何当前浏览器支持的 javaScript
语法编写的脚本:
result = driver.execute_script("return window.location.origin")
print(result) # http://www.baidu.com
ActionChains 操作链链式调用,实现连续操作
使用 动作链(ActionChains) 的方式同样可以实现页面元素的操作,可以支持链式的操作方法调用:
from selenium import webdriver
from selenium.webdriver.common.by import By'
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
# locate element
input_ele = driver.find_element(by=By.ID, value="kw")
# chain of actions
webdriver.ActionChains(driver).move_to_element(input_ele).send_keys("python selenium" + Keys.ENTER)
selenium
目前支持的常用的操作环如下:
move_to_element
:聚焦到目标元素click
:点击当前聚焦元素send_keys
:发送键盘输入到当前聚焦元素drag_and_drop
:将当前元素拖拽到目标位置之后释放key_down
:让指定按钮处于按下状态,常用于模拟组合键操作key_up
:让指定按钮取消按下状态
3. Trouble shooting
3.1 元素覆盖问题
如果发现点击操作没有正常生效,首先检查 元素是否被其他元素覆盖,这有可能会导致点击无法穿透,例如 Linux 系统上,浏览器默认的尺寸可能与页面要求的尺寸不符,页面元素相互拥挤导致一些悬浮类型的元素或者一些固定位置尺寸的元素覆盖在目标按钮或者链接之上,最终导致点击被拦截。
你可以通过 get_window_size
的方法来查看当前浏览器的尺寸:
print(driver.get_window_size()
通过 set_window_size
方法设置合适的尺寸:
# 常见的 16:9 的 2K 分辨率的浏览器尺寸
driver.set_window_size(1920, 1080)
可以通过 get_screenshot_as_file
截图获取当前页面的情况:
driver.get_screenshot_as_file("/tmp/check_window_size.png")
可以通过将文件导出确认页面是否正常,也可以在设置尺寸之前通过截图先检查是什么元素导致点击失效。
另外,如果元素没有在当前页面中,也可能导致点击失效,同样通过调整浏览器页面,让 Selenium 能够定位到元素即可。
3.2 iframe 问题
如果元素在 iframe 中,那么将直接导致选择器无法定位到目标元素,此时需要让 Selenium 切换进入目标的 iframe 中,利用目标 iframe 的 id
实现:
driver.switch_to.frame("target-frame-id")
这时再次执行所需的定位或者动作,确认是否能够顺利定位执行。
五、页面导航 Navigate
1. window 切换
driver
提供了窗口对象以及 switch_to
来实现标签页切换:
# 获取当前浏览器所有的窗口
window_handles = driver.window_handles
# 获取当前所在窗口
current_window_handle = driver.current_window_handle
# 切换到最后一个标签页
driver.switch_to.window(window_handles[-1])
打开新的标签页或者窗口:
# 打开新的标签
driver.switch_to.new_window('tab')
# 打开新的浏览器窗口
driver.switch_to.new_window('window')
关闭标签页:
# 关闭当前所在标签页,即 current_window_handle
driver.close()
# 如果需要关闭其他标签页,需要切换至该标签页再调用 close 方法,例如关闭第一个标签页
driver.switch_to.window(driver.window_handles[0])
driver.close()
需要注意,当我们手动关闭了当前的标签页,或者因为页面的业务逻辑或者其他原因导致页面自动关闭,此时的 current_window_handle
并不会因为自己所指向的页面被关闭而默认切换到最近的标签页(即使你在 Windows 系统上面看到你的驱动所作用的浏览器上面的当前标签页已经关闭并且已经显示了另外一个标签页了)。
此时,任何对当前页面的操作,例如获取 current_window_handle
,获取当前标签页的标题 driver.title
,甚至是关闭动作都会报错,提示 Current window is already closed
!
这时,你需要先通过 switch_to.window
方法切换到一个新的已存在的页面,此时再次获取 current_window_handle
不会报错,其他操作也恢复正常。
2. iframe 切换
还可以通过 switch_to
切换到页面的 iframe 中,从而定位到 iframe 内的元素:
# 如果 iframe 定义了名称,可以根据目标名称跳转
driver.switch_to.frame("target-frame-name")
# 也可以通过类似 move_to_element 的方式,利用过滤器和 find_element 方法找到 iframe 元素跳转
target_iframe = driver.find_element(By.CSS_SELECTOR, "iframe[condition]")
driver.switch_to.frame(target_iframe)
# 如果 iframe 定义了 id 属性,可以利用 id 找到 iframe 元素后跳转
driver.switch_to.frame(driver.find_element_by_id("iframe-id"))
default_content
方法可以切换回页面的默认内容主体,也就是我们访问页面时最外层的 <html>
:
# 从 iframe 中返回原文档
driver.switch_to.default_content()