选择元素的基本方法
对于百度搜索页面,如果我们想输入 起风了 ,怎么做呢?
这就是在网页中,操控界面元素。
web界面自动化,要操控元素,首先要 选择 界面元素,或者说 定位 界面元素。
就是先告诉浏览器,你要操作 哪个 界面元素,让它找到你要操作的界面元素。
我们必须让浏览器先 找到元素 ,然后,才能操作元素
选择元素的方法
对于web自动化来说,就是要告诉浏览器,你要操作的界面元素是什么。
那么,怎么告诉浏览器呢?
方法就是:告诉浏览器,你要操作的这个web元素的 特征 。
元素特征怎么查看?
可以使用浏览器的 开发者工具栏 帮我们查看,选择web元素。
请大家安装最新版的Chrome浏览器,如果没有安装,可以去我之前的文章里找到下载地址 点击这里跳转
用chrome浏览器访问百度,按F12后,点击下图箭头处的Elements标签,即可查看
根据 id属性 选择元素
点击这里,可以打开一个示例页面
页面上有个输入股票名称的输入框,使用鼠标右键菜单查看该iuput元素,会发现它有一个属性叫id,
我们可以把id想象成元素的编号,是用来在html中标记该元素的。
根据规范,如果元素有id属性,这个id必须是当前html中唯一的。
所以如果元素有id,根据id选择元素是最简单高效的方式。
这里,股票名称输入框元素的id为 kw
下面的代码,可以自动化在浏览器中访问我们的示例网站,并且在输入框中输入搜索 通讯.
大家可以运行一下看看
from selenium import webdriver
from selenium.webdriver.common.by import By
# 创建WebDriver 对象,指明使用chrome浏览器驱动
wd = webdriver.Chrome()
# 调用WebDriver 对象的get方法,可以让浏览器打开指定网址
wd.get('https://www.byhy.net/_files/stock1.html')
# 根据id选择元素,返回的就是该元素对应的WebElement对象
element = wd.find_element(By.ID, 'kw')
# 通过该WebElement对象,就可以对页面元素进行操作了
# 比如输入字符串到这个输入框里
element.send_keys('通讯\n')
其中
wd = webdriver.Chrome()
前面讲过,wd赋值等等是WebDriver类型的对象,我们可以通过这个对象来操控浏览器,比如打开网址、选择界面元素等
下面的代码
wd.find_element(By.ID, 'kw')
使用了WebDriver对象的方法 find_element ,
这行代码运行时会发起一个请求,通过 浏览器驱动 转发给浏览器,告诉它,需要选择一个id为kw的元素。
浏览器找到id为kw的元素后,将结果通过 浏览器驱动 返回给自动化程序,所以 find_element 方法会返回一个 WebElement 类型的对象。
这个WebElement对象可以看成是对应 页面元素 的遥控器。
我们通过这个 WebElement对象,就可以 操控 对应的界面元素。
比如:
调用这个对象的 send_keys 方法就可以在对应的元素中输入字符串,
调用这个对象的 click 方法就可以 点击 该元素。
如果根据传入的ID,找不到这样的元素,find_element 方法就会抛出
selenium.common.exceptions.NoSuchElementException 异常
特别注意:Selenium 升级到版本4以后,下面这种 find_element_by* 方法都作为过期不赞成的写法
根据class属性、tag名 选择元素
web自动化的难点和重点之一,就是如何 选择 我们想要操作的web页面元素。
除了根据元素id,我们可以根据元素的class属性选择元素。
大家请访问这个网址 点击打开
这个网址对应的html内容 有如下的部分
<body>
<div class="plant"><span>土豆</span></div>
<div class="plant"><span>洋葱</span></div>
<div class="plant"><span>白菜</span></div>
<div class="animal"><span>狮子</span></div>
<div class="animal"><span>老虎</span></div>
<div class="animal"><span>山羊</span></div>
</body>
所有的植物元素都有个class属性 值为 plant。
所有的动物元素都有个class属性 值为 animal。
如果我们要选择 所有的 动物, 就像下面可以这样写,
wd.find_elements(By.CLASS_NAME, 'animal')
注意element后面多了个s
注意
find_elements 返回的是找到的符合条件的 所有
元素 (这里有3个元素), 放在一个 列表
中返回。
而如果我们使用 wd.find_element
(注意少了一个s) 方法, 就只会返回 第一个
元素。
大家可以运行如下代码看看。
from selenium import webdriver
from selenium.webdriver.common.by import By
# 创建 WebDriver 实例对象,指明使用chrome浏览器驱动
wd = webdriver.Chrome()
# WebDriver 实例对象的get方法 可以让浏览器打开指定网址
wd.get('https://cdn2.byhy.net/files/selenium/sample1.html')
# 根据 class name 选择元素,返回的是 一个列表
# 里面 都是class 属性值为 animal的元素对应的 WebElement对象
elements = wd.find_elements(By.CLASS_NAME, 'animal')
# 取出列表中的每个 WebElement对象,打印出其text属性的值
# text属性就是该 WebElement对象对应的元素在网页中的文本内容
for element in elements:
print(element.text)
首先,大家要注意: 通过 WebElement 对象的 text属性
可以获取该元素 在网页中的文本内容。
所以 下面的代码,可以打印出 element 对应 网页元素的 文本
print(element.text)
如果我们把
elements = wd.find_elements(By.CLASS_NAME, 'animal')
去掉一个s ,改为
element = wd.find_element(By.CLASS_NAME, 'animal')
print(element.text)
那么返回的就是第一个class 属性为 animal的元素, 也就是这个元素
<div class="animal"><span>狮子</span></div>
就像一个 学生张三 可以定义有 多个
类型: 中国人 和 学生 , 中国人 和 学生 都是 张三 的 类型。
元素也可以有 多个class类型
,多个class类型的值之间用 空格
隔开,比如
<span class="chinese student">张三</span>
注意,这里 span元素 有两个class属性,分别 是 chinese 和 student, 而不是一个 名为 chinese student
的属性。
我们要用代码选择这个元素,可以指定任意一个class 属性值,都可以选择到这个元素,如下
element = wd.find_elements(By.CLASS_NAME,'chinese')
或者
element = wd.find_elements(By.CLASS_NAME,'student')
而不能这样写
element = wd.find_elements(By.CLASS_NAME,'chinese student')
根据 tag 名 选择元素
类似的,我们可以通过指定 参数为 By.TAG_NAME
,选择所有的tag名为 div的元素,如下所示
from selenium import webdriver
from selenium.webdriver.common.by import By
wd = webdriver.Chrome()
wd.get('https://cdn2.byhy.net/files/selenium/sample1.html')
# 根据 tag name 选择元素,返回的是 一个列表
# 里面 都是 tag 名为 div 的元素对应的 WebElement对象
elements = wd.find_elements(By.TAG_NAME, 'div')
# 取出列表中的每个 WebElement对象,打印出其text属性的值
# text属性就是该 WebElement对象对应的元素在网页中的文本内容
for element in elements:
print(element.text)
find_element 和 find_elements 的区别
使用 find_elements
选择的是符合条件的 所有
元素, 如果没有符合条件的元素, 返回空列表
使用 find_element
选择的是符合条件的 第一个
元素, 如果没有符合条件的元素, 抛出 NoSuchElementException 异常
通过WebElement对象选择元素
不仅 WebDriver对象有 选择元素 的方法, WebElement对象 也有选择元素的方法。
WebElement对象 也可以调用 find_elements
, find_element
之类的方法
WebDriver 对象 选择元素的范围是 整个 web页面, 而
WebElement 对象 选择元素的范围是 该元素的内部。
from selenium import webdriver
from selenium.webdriver.common.by import By
wd = webdriver.Chrome()
wd.get('https://cdn2.byhy.net/files/selenium/sample1.html')
element = wd.find_element(By.ID,'container')
# 限制 选择元素的范围是 id 为 container 元素的内部。
spans = element.find_elements(By.TAG_NAME, 'span')
for span in spans:
print(span.text)
输出结果就只有
内层11
内层12
内层21
等待界面元素出现
在我们进行网页操作的时候, 有的元素内容不是可以立即出现的, 可能会等待一段时间。
比如 我们的股票搜索示例页面, 搜索一个股票名称, 我们点击搜索后, 浏览器需要把这个搜索请求发送给服务器, 服务器进行处理后,再把搜索结果返回给我们。
所以,从点击搜索到得到结果,需要一定的时间,
只是通常 服务器的处理比较快,我们感觉好像是立即出现了搜索结果。
搜索的每个结果 对应的界面元素 其ID 分别是数字 1, 2 ,3, 4 。。。
我们可以先用如下代码 将 第一个搜索结果里面的文本内容 打印出来
from selenium import webdriver
from selenium.webdriver.common.by import By
wd = webdriver.Chrome()
wd.get('https://www.byhy.net/_files/stock1.html')
element = wd.find_element(By.ID, 'kw')
element.send_keys('通讯\n')
# 返回页面 ID为1 的元素
element = wd.find_element(By.ID,'1')
# 打印该元素的文字内容
print(element.text)
如果大家去运行一下,就会发现有如下异常抛出
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="1"]"}
NoSuchElementException
的意思就是在当前的网页上 找不到该元素, 就是找不到 id 为 1 的元素。
为什么呢?
因为我们的代码执行的速度比 网站响应的速度 快。
网站还没有来得及 返回搜索结果,我们就执行了如下代码
element = wd.find_element(By.ID, '1')
在那短暂的瞬间, 网页上是没有用 id为1的元素的 (因为还没有搜索结果呢)。自然就会报告错误 id为1 的元素不存在了。
那么怎么解决这个问题呢?
很多聪明的读者可以想到, 点击搜索后, 用sleep 来 等待几秒钟, 等百度服务器返回结果后,再去选择 id 为1 的元素, 就像下面这样
from selenium import webdriver
from selenium.webdriver.common.by import By
wd = webdriver.Chrome()
wd.get('https://www.byhy.net/_files/stock1.html')
element = wd.find_element(By.ID, 'kw')
element.send_keys('通讯\n')
# 等待 1 秒
from time import sleep
sleep(1)
element = wd.find_element(By.ID,'1')
print(element.text)
大家可以运行一下,基本是可以的,不会再报错了。
但是这样的方法 有个很大的问题,就是:设置等待多长时间合适呢?
这次百度网站反应可能比较快,我们等了一秒钟就可以了。
但是谁知道下次他的反应是不是还这么快呢?百度也曾经出现过服务器瘫痪的事情。
可能有的读者说,我干脆sleep比较长的时间, 等待 20 秒, 总归可以了吧?
这样也有很大问题,假如一个自动化程序里面需要10次等待, 就要花费 200秒。 而可能大部分时间, 服务器反映都是很快的,根本不需要等20秒, 这样就造成了大量的时间浪费了。
Selenium提供了一个更合理的解决方案,是这样的:
当发现元素没有找到的时候, 并不立即返回 找不到元素的错误。
而是周期性(每隔半秒钟)重新寻找该元素,直到该元素找到,
或者超出指定最大等待时长,这时才 抛出异常(如果是 find_elements
之类的方法, 则是返回空列表)。
Selenium 的 Webdriver 对象 有个方法叫 implicitly_wait
,可以称之为 隐式等待
,或者 全局等待
。
该方法接受一个参数, 用来指定 最大等待时长。
如果我们 加入如下代码
wd.implicitly_wait(10)
那么后续所有的 find_element
或者 find_elements
之类的方法调用 都会采用上面的策略:
如果找不到元素, 每隔 半秒钟 再去界面上查看一次, 直到找到该元素, 或者 过了10秒 最大时长。
这样,我们的百度搜索的例子的最终代码如下
from selenium import webdriver
from selenium.webdriver.common.by import By
wd = webdriver.Chrome()
wd.implicitly_wait(10)
wd.get('https://www.byhy.net/_files/stock1.html')
element = wd.find_element(By.ID, 'kw')
element.send_keys('通讯\n')
# 返回页面 ID为1 的元素
element = wd.find_element(By.ID,'1')
print(element.text)
大家再运行一下,可以发现不会有错误了。
那么是不是有了 implicitwait
, 可以彻底不用sleep了呢?
不是的,有的时候我们等待元素出现,仍然需要sleep。
Selenium 4 find_element写法
注意:Selenium 4 以后, 下面这种 find_element_by*
方法都作为过期不赞成的写法
# 初始化代码 ....
wd.find_element_by_id('username').send_keys('byhy')
wd.find_element_by_class_name('password').send_keys('sdfsdf')
wd.find_element_by_tag_name('input').send_keys('sdfsdf')
wd.find_element_by_css_selector('button[type=submit]').click()
运行会有告警,都要写成下面这种格式
from selenium.webdriver.common.by import By
# 初始化代码 ....
wd.find_element(By.ID, 'username').send_keys('byhy')
wd.find_element(By.CLASS_NAME, 'password').send_keys('sdfsdf')
wd.find_element(By.TAG_NAME, 'input').send_keys('sdfsdf')
wd.find_element(By.CSS_SELECTOR,'button[type=submit]').click()