selenium提供了8种定位方式,下表列出了各个定位方式和在Python中对应的方法:
selenium中各定位方式 | 对应的Python方法 |
---|---|
id | find_element_by_id() |
name | find_element_by_name() |
class name | find_element_by_class_name() |
tag name | find_element_by_tag_name() |
link text | find_element_by_link_text() |
partial link text | find_element_by_partial_link_text() |
xpath | find_element_by_xpath() |
css selector | find_element_by_css_selector() |
接下来,具体的示例来学习一下各定位方式是如何使用的。
通过id获取元素
打开Pythonav的注册页面。键盘F12
查看源码,定位到用户注册输入框(其他输入框都是这么玩的),如下图。
运行如下代码:
from selenium import webdriver
# 创建Chrome WebDriver实例
browser = webdriver.Chrome()
# 获取URL网页
browser.get('https://pythonav.com/register/')
# 根据id获取对应的元素
username = browser.find_element_by_id('id_username')
# 打印获取结果
print(username) # <selenium.webdriver.remote.webelement.WebElement (session="2e4891511f03cc1baaa12c0426dc7b05", element="0.5508062663208302-1")>
# 打印id
print(id(username)) # 61771472
# 打印类型
print(type(username)) # <class 'selenium.webdriver.remote.webelement.WebElement'>
# 关闭浏览器
browser.quit()
会看到,打开的浏览器,很快就关闭了,但我们成功的打印出结果,也就是获取到了想要的元素了。
为指定元素的属性赋值
现在,已经能定位到用户名的输入框,也就可以自动的填写该input框,来试试看:
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('http://www.pythonav.com/register/')
# 根据id获取对应的元素
username = browser.find_element_by_id('id_username')
# 为元素的value属性赋值
username.send_keys('张开')
# 睡3秒
time.sleep(3)
# 清空元素的value属性
username.clear()
# 再睡2秒
time.sleep(2)
# 关闭浏览器
browser.quit()
模拟短信验证操作
继续来模拟一下发送短信验证码的操作:
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("http://www.pythonav.com/register/")
# 根据id获取对应的元素
phone = browser.find_element_by_id('id_telephone')
# 回填手机号
phone.send_keys('18xxooxxoo98k') # 这里填写一个真实可用的手机号
# 睡一会儿
time.sleep(5)
# 获取发送短信的按钮id
ele = browser.find_element_by_id('smsBtn')
# 模拟按钮的点击事件
ele.click()
time.sleep(2)
browser.quit()
自动化注册Pythonav网站
继续完善自动用户注册的代码:
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("http://www.pythonav.com/register/")
# 填写用户名
browser.find_element_by_id('id_username').send_keys('张开11')
# 填写密码
browser.find_element_by_id('id_password').send_keys('zhangkai111')
# 确认密码
browser.find_element_by_id('id_confirm_password').send_keys('zhangkai111')
# 回填手机号
browser.find_element_by_id('id_telephone').send_keys('18xxooxx0098k') # 需要一个真实的手机号
# 点击获取短信验证码
browser.find_element_by_id('smsBtn').click()
# 睡它40秒,这里是为了等发送短信验证码,然后我们手动填进去
time.sleep(40)
# 最后点击注册按钮
browser.find_element_by_id('submit').click()
# browser.find_element_by_id('submit').submit()
time.sleep(100)
browser.quit()
如果顺利的收到验证码,并且在40秒内填进去了,然后就会发现,注册成功了!下图为证!
需要注意的是:按钮提交时,除了click之外,还有一个submit,区别是click就是单纯的点一下。而submit是完成了表单提交,需要携带表单信息进行提交的。如果是button按钮的话,只能使用click,而不能使用submit。
name
通过PythonAV来学习name
定位是什么鬼。
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://pythonav.com/login/')
# 通过标签的name属性
img = browser.find_element_by_name("username")
print(img.tag_name) # input
time.sleep(2)
browser.quit()
上例,通过获取用户名的inpu框来完成定位。
需要注意的是:browser.find_element_by_name("username")
这种定位方式只能通过name
属性来定位。
通过class name获取单个元素属性
通过路飞学城的登录页面来学习通过class name
定位。
由上图可以看到,页面中的记住密码前的复选框,是由span标签通过css样式实现的。我们用class name
定位来完成自动勾选任务。
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/signin')
# 通过class获取span标签
span = browser.find_element_by_class_name(name='no')
# 点击事件
span.click()
print(span.get_attribute(name='class')) # no yes
# 获取当前元素的内容
print(span.text) # ✓
time.sleep(3)
# 再次点击span标签
span.click()
time.sleep(5)
browser.quit()
实现相对简单,再来点有点难度的,我们通过class name
定位来获取路飞学城首页轮播图信息。
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/home')
# 根据class获取img元素
img = browser.find_element_by_class_name('banner')
# 获取元素的指定(src)属性
print(img.get_attribute('src')) # https://hcdn1.luffycity.com/static/frontend/index/home-banner4_1535545832.4715614.png
print(img.get_property('src')) # https://hcdn1.luffycity.com/static/frontend/index/home-banner4_1535545832.4715614.png
time.sleep(5)
browser.quit()
通过打印结果来看,我们已经成功的获取到了指定的元素属性。这里需要补充的是:get_attribute
方法获取元素的特性,类似js
中的setattribute("","")
,自定义属性设置,而get_property
方法也是获取当前元素的属性,但一般都是获取原生的属性,两种方法有一定的互通性。
通过class name获取多个元素属性
上例代码没有问题,但我们仅获取了一个图片的属性,那如何获取多个呢?
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/home')
# 根据class获取img元素
imgs = browser.find_elements_by_class_name('banner')
# 获取元素的指定(src)属性
print([i.get_attribute(name='src') for i in imgs])
time.sleep(5)
browser.quit()
'''打印结果
[
'https://hcdn1.luffycity.com/static/frontend/index/home-banner4_1535545832.4715614.png',
'https://hcdn1.luffycity.com/static/frontend/index/PCbanner_1548828967.892487.jpeg',
'https://hcdn1.luffycity.com/static/frontend/activity/banner1_1553586989.5872993.png',
'https://hcdn1.luffycity.com/static/frontend/index/banner1(4)_1539945492.0492468.png',
'https://hcdn1.luffycity.com/static/frontend/index/banner_1553684636.466433.png',
'https://hcdn1.luffycity.com/static/frontend/index/banner11_1538122470.2779157.png',
'https://hcdn1.luffycity.com/static/frontend/index/home-banner4_1535545832.4715614.png',
'https://hcdn1.luffycity.com/static/frontend/index/PCbanner_1548828967.892487.jpeg'
]
'''
上例中,获取单个元素使用browser.find_element_by_class_name('banner')
,而要想获取多个,就要使用browser.find_elements_by_class_name('banner')
,别忘了加s
!而当加了s
后,获取到的是一个列表,而列表的话,不能直接img.get_attribute('src')
,而是想方设法的取列表中的每一个元素,我们对每一个元素再使用i.get_attribute(name='src')
就没问题了。
see also:luffycity
限定span标签范围
还是通过路飞学城首页的菜单列表来完成tag name
演示,我们要使用selenium自动完成点击任务。
首先,要获取所有的span标签,但我们思考,这个页面中有很多个span标签,而只需要对这个几个span标签做点击事件,所以,我们首先要限定范围。经过一番观察,先获取所有span标签外部的nav标签。
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/home')
nav = browser.find_element_by_tag_name(name='nav')
print(nav.text)
time.sleep(3)
browser.quit()
'''打印结果
免费课
轻课
学位课
题库
'''
循环绑定click事件
有了范围就好办了,再从nav标签内部找所有的span标签,然后给每一个span标签绑定一个click事件,然后自动执行就完成了。
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/home')
# 获取span的外部nav标签
nav = browser.find_element_by_tag_name(name='nav')
# 然后再获取nav内部的所有span标签
span_list = nav.find_elements_by_tag_name(name='span')
for i in span_list:
i.click()
time.sleep(3)
time.sleep(3)
browser.quit()
'''
selenium.common.exceptions.WebDriverException: Message: unknown error: Element <span _v-0ec42e90="">...</span> is not clickable at point (197, 45). Other element would receive the click: <div class="mbox" _v-5f981f7a="" style="">...</div>
(Session info: chrome=73.0.3683.86)
(Driver info: chromedriver=2.46.628402 (536cd7adbad73a3783fdc2cab92ab2ba7ec361e1),platform=Windows NT 10.0.14393 x86_64)
'''
处理弹出框
上例代码逻辑没有问题!但是报错了,观察报错信息(说的好像看的懂似的!),每当打开路飞学城首页的时候,首先会弹出一个弹出框,不把它叉掉就没有办法干别的,肯定是它搞的鬼,必须先干掉他才能执行后续的点击事件!
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/home')
time.sleep(1)
# 首先关闭弹出框
browser.find_element_by_class_name('guan').click()
time.sleep(1)
# 获取span的外部nav标签
nav = browser.find_element_by_tag_name(name='nav')
# 然后再获取nav内部的所有span标签
span_list = nav.find_elements_by_tag_name(name='span')
for i in span_list:
i.click()
time.sleep(3)
time.sleep(3)
browser.quit()
上例代码,首先关闭弹出框,在为nav标签内部的所有span标签绑定事件,然后一切就会循环起来了。
通过link text定位
通过PythonAV来学习link text/partial link text
定位。
由上图可以看到,PythonAV和路飞学城在标签切换这里是不一样的,PythonAV是a标签(毕竟是av嘛)!而路飞学城是span标签。所以,针对PythonAV,采用另一个元素定位方式来完成。
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://pythonav.com/index/')
time.sleep(2)
# 获取超链接,并且点击
browser.find_element_by_link_text('免费视频').click()
time.sleep(3)
browser.quit()
上例,使用browser.find_element_by_link_text('免费视频').click()
来完成定位和点击任务。
通过partial link text定位
那partial link text
是什么鬼?
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://pythonav.com/index/')
browser.find_element_by_partial_link_text('免费').click()
time.sleep(2)
browser.find_element_by_partial_link_text('知识库').click()
time.sleep(2)
# browser.find_element_by_link_text('内部').click() # 报错
browser.find_element_by_link_text('内部资料').click() # 报错
time.sleep(2)
browser.quit()
由上例可以看到,使用partial link text
定位时,使用标签连接内容部分内容即可,而通过link text
定位时,链接内容必去是完全匹配才能成功。
小结:
link text/partial link text
都仅能用于超链接标签的定位,其他的标签都不可以。link text
是精确定位,也就是说超链接内的内容必须完全匹配才能定位成功。partial link text
是模糊定位,只要超链接的内容包含该指定内容既可以匹配到。根据不同的场景,需谨慎使用。
css selector
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/home')
time.sleep(1)
# 首先关闭弹出框
browser.find_element_by_class_name('guan').click()
# 通过css selector
res = browser.find_element_by_css_selector('.luffy-home .title p[_v-2f0761bc]')
print(res.tag_name) # p
time.sleep(2)
browser.quit()
除此之外,css selector还可以这么用:
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/home')
time.sleep(1)
# 首先关闭弹出框
browser.find_element_by_css_selector("img[class='guan']").click()
time.sleep(2)
browser.quit()
虽然css selector
定位不如id class tag
等定位方法直接,但是css selector
性能高,尤其是在IE浏览器中(老版IE浏览器对XPath支持不太好,尤其是IE11之前)。并且selenium也推荐使用css selector
定位。
see also:【Selenium专题】元素定位之CssSelector
XPath
选取节点
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
下表列举了常用的路径表达式:
表达式 | 描述 |
---|---|
node name | 选取此节点的所有子节点 |
/ | 从根节点选取 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
实例
下表列举了一些表达式的应用及描述:
路径表达式 | 描述 |
---|---|
bookstore | 选取bookstore元素的所有子节点 |
/bookstore | 选取根元素bookstore。如果路径起始于/ ,则此路径始终代表到某元素的绝对路径 |
bookstore/book | 选取属于bookstore元素下的所有book元素 |
//book | 选取book子元素,而不管它们在文档中的位置 |
bookstore//book | 选择属于bookstore元素的所有book元素,而不管它们位于bookstore之下的什么位置 |
//@book | 选取名为book的所有属性 |
谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点。谓语被嵌在方括号中。
下表列举了带有谓语的一些路径表达式,以及表达式结果:
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选取属于bookstore子元素的第一个book元素 |
/bookstore/book[last()] | 选取属于bookstore子元素的最后一个book元素 |
/bookstore/book[last()-1] | 选取属于bookstore子元素的倒数第二个book元素 |
/bookstore/book[position()<3] | 选取最前面的两个属于bookstore元素的子元素的book元素 |
//title[@book] | 选取所有拥有名为book的属性的title元素 |
//title[@book='lang'] | 选取所有book属性为lang的title元素 |
/bookstore/book[price>35.00] | 选取bookstore元素的所有book元素,并且其中price属性的值必须大于35.00 |
/bookstore/book[price>35.00]/title | 选取bookstore元素中的title元素,过滤条件是price元素值必须大于35.00 |
选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点 |
@* | 匹配任何属性节点 |
node() | 匹配任何类型的节点 |
实例
下表中列举了一些路径表达式及对应的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取bookstore元素的所有子元素 |
//* | 选取文档中的所有元素 |
//title[@*] | 选取所有带有属性的title元素 |
选取若干路径
通过在路径表达式中使用|
运算符,我们可以选取若干个路径。
实例
下表中列举了一些路径表达式及对应的结果:
路径表达式 | 结果 |
---|---|
//book/title | //book/price | 选取book元素下所有title和price元素 |
//title | //price | 选取文档中所有title和price元素 |
/bookstore/book/title | //price | 选取属于bookstore元素下的book元素中的所有title元素,以及文档中所有price元素 |
是时候写点代码了:
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.luffycity.com/home')
time.sleep(1)
# 首先关闭弹出框
browser.find_element_by_css_selector("img[class='guan']").click()
img = browser.find_element_by_xpath('//ul/li/img[@_v-2f0761bc]')
# img = browser.find_element_by_xpath('/ul/li/img[@_v-2f0761bc]') # 报错
print(img.get_attribute('src'))
time.sleep(2)
browser.quit()
上例中,报错的原因是在根据XPath找路径的时候,其实位置如果是单个/
,表示绝对路径,要从HTML的根节点开始查找,而双//
则是相对路径。
除此之外,Chrome浏览器还支持无脑获取标签的XPath,那就是万能的右键copy XPath
:
妥妥的准!
节点匹配
再介绍一些XPath中节点匹配的基本方法。
路径匹配
路径匹配与文件路径的表示相仿,比较好理解,有以下几个符号:
- 用
/
表示节点路径,如/A/B/C
表示节点A
的子节点B
的子节点C
,/
表示根节点。 - 用
//
表示所有路径以//
后指定的子路径结尾的元素,如//D
表示所有的D
元素;如果是//C/D
表示所有父节点为C
的D
元素。 - 用
*
表示路径的通配符,如/A/B/C/*
表示A
元素下的B
元素下的C
元素下的所有子元素。
位置匹配
对于每一个元素,它的各个子元素都是有序的:
/A/B/C[1]
表示A
元素下的B
元素下的C
元素下的第一个子元素。/A/B/C[last()]
表示A
元素下的B
元素下的C
元素下最后一个子元素。/A/B/C[position()>2]
表示A
元素下的B
元素下的C
元素下的位置号大于2的元素。
属性及属性值
在XPath中可以利用属性及属性值来匹配元素,需要注意的是:元素的属性名前要有@
前缀,例如:
//B[@id]
表示所有具有属性id的B元素。//B[@*]
表示所有具有属性的B元素。//B[not(@*)]
表示所有不具有属性的B元素。//B[@id="b1"]
表示id值为b1
的B元素。
see also: XPath 教程