3.4 通过CSS选择器选择
W3C的CSS是定义HTML页面样式的语言。它决定页面元素显示的效果,比如一段文本的字体,大小,间距等等。当然,要修饰一个元素,首先它也要指定修饰的是哪个元素。所以CSS规范里面定义一种选择页面元素的语法。正好Selenium可以用它来定位页面元素。CSS选择器就是这种语法的使用。CSS选择器选择功能强大,不仅可以通过上述的属性选择,还可以根据元素的父子兄弟关系,子元素的顺序,其他的任意元素属性的有无或者属性值,输入焦点,等等。总之非常的强大,特别适用于上面方法都不好定位的时候使用。假如有如下的html片段。
- 根据 tag 名 选择
p {color: red;} #表示选择所有的 p 元素
- 根据 id ,前面加个#号
#food {color: blue;} #表示选择ID为 food的 元素
- 3.根据class 选择,前面加个“.”
.special {color: red;}#表示选择class为 special 的 元素 ,
注意有的元素有两个class 值:
<span class="vegetable good">黄瓜</span>
我们可以看到这个calss中间有一个空格,他不是整体是一个class而是说这个元素有两个class属性,是vegetable 和 good我们可以这样写:
.good {color: red;}#表示选择class为 good 的所有 元素
.vegetable {color: blue;} #表示选择所有的 class为 vegetable所有 的元素
如果要选择class属性同时具有vegetable 和 good 的,可以这样:
.vegetable.good
假如有如下的html片段:
<div id="food" style="margin-top:10px;color:red">
<span class="vegetable good">黄瓜</span>
<span class="meat">牛肉</span>
<p class="vegetable">南瓜</p>
<p class="vegetable">青菜</p>
</div>
<div id="food2" style="margin-top:10px">
<span class="vegetable">黄瓜2</span>
<span class="meat">牛肉3</span>
</div>
<select id="choose_car">
<option value="volvo">沃尔沃</option>
<option value="corolla">卡罗拉</option>
<option value="fiat">菲亚特</option>
<option value="audi">奥迪</option>
</select>
通过css选择的方式如下:
- 根据tag名,选中第一个span节点
find_element_by_css_selector('span')
选中所有的span节点
find_elements_by_css_selector('span')
- 根据id名
#选中所有id为food2的节点
find_elements_by_css_selector('#food2')
根据class名
#选中所有class属性为vegetable的节点
find_elements_by_css_selector('.vegetable')
讲到这里大家会想这些根据id,class,tag去找元素我们之前不是说过吗,比如find_elements_by_id()、find_elements_by_class_name()这只是我们已前知识的另一种用法而已,我们为什么要学它呢?这里就介绍它的强大之处,它有后代选择的能力。
比如大家看这个表达式:
#food p
中间有个空格,刚才有说到空格在css里面不能乱加有特殊的含义,这个空格就是你要找的节点,注意一定是找空格最后一个节点,就是p节点,空格前面是这个节点的上层节点的特性,这个表达式我们可以这样理解:我们要找一个标签名是p的,然后这个标签有一个限制,他是在id是food 元素的内部 ,我们要找id是 food 里面的所有p,空格就是后代的意思。
看一个例子:
div span
就是查找所有 div 元素 里面的 span 元素。
发现span是div的直接子元素, 就算不是直接子元素也可以,只要是内部的就一样可以。
3.4.1子元素选择
前面我们学习了后代选择器,比如后代选择器:#choose_car option。选择 id 为choose_car 的所有 option子元素,不管它们是否是直接子节点。如果您希望缩小范围,只选择某个元素的直接子节点元素,请使用子元素选择器(Child selector)。
- 子元(child)选择器
选择元素的子元素,和后代选择器不同(#food p )比如:
#choose_car > option 大于号表示你最终要选择的option 元素是前面的这个元素的直接子节点
- 可以是很多级
ul > ol > li > em
方法是可以混合使用的比如div li > #abc这个表达式意思就是div里面tag名为li,id为abc的直接子元素。
3.4.2 组(group)选择
组选择 同时选择多个元素,逗号隔开,语法:
语法 <s1>,<s2>
比如:
p,button 选择所有的p元素和所有的button元素
#food , .cheese 选择所有id是food的元素和class是cheese的元素。
选择 id为food的所有span子元素 和 所有的p(包括非food的子元素)逗号的优先级要比后代的优先级低,逗号是最后算的。
#food > span,p
选择 id为food的 所有span子元素 和 id为food的 所有的p元素:
#food > span ,#food > p
可以这样选择id为food的的所有子元素:
#food > *
*
代表所有元素的意思
p button 与 p ,button大家能区分清楚吗,p button意思是p元素里面所有的button元素,p ,button意思是所有的p和所有的button。
selenium也可以用这样的语法:
eles = driver.find_elements_by_css_selector('p, button')
3.4.3 兄弟节点选择
我们说过了 后代元素选择、子元素选择,现在我们说下相邻兄弟选择器。下面有这样一段html:
<div id="food2" style="margin-top:10px">
<span class="vegetable">黄瓜2</span>
<span class="meat">牛肉3</span>
</div>
<select id="choose_car">
<option value="volvo">沃尔沃</option>
<option value="corolla">卡罗拉</option>
<option value="fiat">菲亚特</option>
<option value="audi">奥迪</option>
</select>
选择紧接在另一个元素后的元素,而且二者有相同的父元素。注意:两个条件 紧接在后面、相同父元素 .这个表达式就是找到id为food2紧跟着的兄弟节点select节点:
#food2 + select
如果我们想选择的 只是在另一个元素后的兄弟元素,不一定要紧挨着,二者有相同的父元素 ,用下面的语法。
#food2 ~ select
大家注意“+”和“~”都是选择后面的元素,#food + div 表示id是food的紧跟着的div,而#food ~ div不一定要紧接着,我们可以联合其他语法使用,比如:
#many > div > p.special + p
选择 #many 的子元素 div 里面的 子元素 p (类型为special) 的后面的兄弟节点。最后选中的元素是:
3.4.4 属性选择
比如:选择所有具有style属性的元素
*[style]
又比如:选择P节点具有spec值为 len2 的元素,加引号一般用在 属性值中间有空格的情况,如果没有空格可以不加引号。这里有个特别要注意的地方,属性选择必须要完全一样
p[spec='len2']
看下面的截图,标红的部分是不会被选中的。
属性选择还有一种写法,如果要选择只要spec属性包含len2的就被选择那要怎么写呢,在“=”前面加个“*”
号就可以
p[spec*='len2']
或者
a[href*="baidu.com"]
以len2开头的:
p[spec^='len2']
以len2结尾的:
p[spec$='len2']
同时满足两种属性的:
p[class=special][name=p1]
CSS选择器有很多的语法, 详细的大家可以参考 这里http://www.w3school.com.cn/cssref/css_selectors.asp
我们css选择器还有一种常用的方法:nth-child(n)比如p:nth-child(2)意思是首先选择所有的p节点,冒号表示一个限定,就是p必须是父元素的第二个子元素,这就是nth-child(2),注意不是说第二个p类型的子元素而就是第二个p元素比我我们看个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>定位网页元素</title>
<style>
#choose_car option {color: blue;}
</style>
</head>
<body>
<div style="">
<h3>This is a heading</h3>
<p>This is a paragraph.</p>
</div>
<button name='button'>按钮1</button>
<button name='button'>按钮2</button>
<div class="cheese"><span>Cheddar</span></div>
<div class="cheese"><span>Gouda</span></div>
<a href="http://www.163.com" id="baidulink">转到百度</a>
<div id="food" style="margin-top:10px;color:red">
<span class="vegetable good">黄瓜</span>
<span class="meat">牛肉</span>
<p class="vegetable">南瓜</p>
<p class="vegetable">青菜</p>
</div>
<div id="food2" style="margin-top:10px">
<span class="vegetable">黄瓜2</span>
<span class="meat">牛肉3</span>
</div>
<select id="choose_car">
<option value="volvo">沃尔沃</option>
<option value="corolla">卡罗拉</option>
<option value="fiat">菲亚特</option>
<option value="audi">奥迪</option>
</select>
<footer>
<div>
<p>test1</p>
</div>
</footer>
<p>test2</p>
<div id="many">
<div>
<p class="special" name="p1">one</p>
<p>two</p>
<p class="special3">three</p>
</div>
</div>
<p spec="len">test3</p>
<p spec="len2">test4</p>
<p spec="len2 len3">test5</p>
</body>
</html>
id等于food的下面有几个p元素,是不是2个?
#food > p:nth-child(2)
这个表达式是什么意思呢?属于其父元素的第二个子元素,并且要是p ,我们这里第二个子元素是span,所以上面这个表达式是找不到的。
比如:
#food > p:nth-child(3)
这个表达式的意思是,属于其父元素的第3个子元素,并且要是p,这就可以找到。这是正数第3个我们还可以倒数nth-last-child(n),还有一种按照类型排序的方法nth-of-type(),p:nth-of-type()和p:nth-child()区别是:p:nth-child(2)必须是父节点的第二个子节点,p:nth-of-type(2)第二个p类型的子节点。
比如:属于其父元素的倒数第二个子元素,并且是p
#food > p:nth-last-child(2)
找到的是:
比如:属于其父元素的第二个p 类型的子元素
#food > p:nth-of-type(2)
找到的是:
比如:属于其父元素的倒数第二个p 类型的子元素
#food > p:nth-last-of-type(2)
找到的是:
还有一种选择元素的方式:not(p),选择非 <p> 元素的每个元素。
比如:
#food > :not(p)
找到的是:
3.4.5 利用浏览器开发工具获取 css seletor
对于不太好找的CSS selector的元素,我们可以通过浏览器开发工具帮助我们定位。在chrom浏览器里,按F12,打开开发工具窗口,点击element标签,然后,在网页窗口里面 点选我们要 选择的元素,开发工具窗口会高亮显示该元素对应的html tag代码。这时,我们可以 用鼠标右键 点击该代码,在弹出的对话框中,依次选择Copy --> Copy selector,如下图所示,这样,该元素的CSS selector就被拷贝到剪贴板了。
3.4.6 验证css表达式
当我们要写的自动化脚本比较复杂的时候,每次到python代码中调试CSS选择器(看看我们的css选择器是否能选中元素)会非常的麻烦。因为很慢,我们可以利用浏览器的开发工具,直接在浏览器中进行测试,看看我们写的CSS选择器是否能正确找到我们要的web元素。
有两种方法, 一种是chrome浏览器,按F12,打开开发窗口,点击元素标签(英文叫Element),按contrl + f,直接输入css选择器即可。选中的元素会高亮显示。
这种方法优点, 填入内容完全就是 css 选择器, 缺点: 也会做 字符匹配, 让人有点迷糊。
另一种是:
chrome浏览器,按F12,打开开发窗口,点击控制台标签(英文叫console),在里面执行$$(‘css selector’),其中css selector 就是css选择器字符串。chrom界面如下所示:
如果能找到对象,返回的结果就不是空数组,就表示能找到web元素。而且鼠标放在数组元素上,会高亮显示对应的web元素。这种方法 优点, 不会做 字符串匹配, 很清晰。缺点: 要多输入点内容。
下面是一次作业,可以用今天讲的知识做一下
登录 51job:http://www.51job.com
输入搜索关键词 "python", 地区选择 "杭州"(注意,如果所在地已经选中其他地区,要去掉),搜索最新发布的职位, 抓取页面信息。 得到如下的格式化信息:
Python开发工程师 | 杭州纳帕科技有限公司 | 杭州 | 0.8-1.6万/月 | 04-27
Python高级开发工程师 | 中浙信科技咨询有限公司 | 杭州 | 1-1.5万/月 | 04-27
高级Python开发工程师 | 杭州新思维计算机有限公司 | 杭州-西湖区 | 1-1.5万/月 | 04-27
解析:这里面就只有一个难点,怎么只保证选择 杭州 呢,我们可以定位一下选中的元素有什么特征,我们发现 class="on" 就是选中,如果不选中就没有class="on" 的值,确保只有杭州是选中的,这里要综合使用 python 和Selenium 的知识,我们可以先把所有的城市过一遍,如果他是杭州 class值不等于 on 我们就 click 一下,如果选中了就不动他,如果是其他城市是反之,选中了就 click 下没选中就不管它。
#从selenium里面导入webdriver
from selenium import webdriver
#指定chrom的驱动
#执行到这里的时候Selenium会到指定的路径将chrome driver程序运行起来
driver = webdriver.Chrome('E:\ChromDriver\chromdriver2.43\chromedriver.exe')
driver.implicitly_wait(10)
driver.get('https://www.51job.com/')
#根据id找到输入框,输入python
driver.find_element_by_id('kwdselectid').send_keys('python')
#点击工作地点
driver.find_element_by_id('work_position_input').click()
import time
time.sleep(2)
#用css方法找到所有的城市
cityEles = driver.find_elements_by_css_selector('#work_position_click_center_right_list_000000 em')
#遍历元素,一个个城市去找
for one in cityEles:
#城市名
cityname = one.text
#用之前学过的attribute方法获取class的值
cassvalue = one.get_attribute('class')
#
if cityname == '杭州':
if cassvalue != 'on':
one.click()
elif cityname != '杭州':
if cassvalue == 'on':
one.click()
#点击确定按钮
driver.find_element_by_id('work_position_click_bottom_save').click()
#点击搜索按钮
driver.find_element_by_css_selector('div.ush.top_wrap button').click()
#找职位
jobs = driver.find_elements_by_css_selector('#resultList div.el')
for job in jobs[1:]:
#span是一个列表
spans = job.find_elements_by_tag_name('span')
#列表生成式
fields = [span.text for span in spans]
print(fields)
#退出
driver.quit()
代码里为什么有 sleep?我们执行到 cityEles = driver.find_elements_by_css_selector('#work_position_click_center_right_list_000000 em') 的时候其实界面不是一下子呈现出来的,导致我们去找 em 的时候状态没来的急更新,有没有选中是动态更新的,他在定位城市的时候前端的 js 动态的获取了一些信息,然后再把这些城市有没有选中呈现出来,这样就导致了当前获取的东西并不是过了一段时间后更新的状态,就是不是稳定的状态,之前一个临时的状态,这里我们就得 sleep 等待一下,有人会有疑问不是有 implicitly_wait 吗,implicitly_wait 是找不到才会等待这里是可以找到 em ,只是这里过了一段时间才刷新的,大家可以好好看下这段代码。