3.4 通過CSS選擇器選擇
W3C的CSS是定義HTML頁面樣式的語言。它決定頁面元素顯示的效果,比如一段文本的字體,大小,間距等等。當(dāng)然,要修飾一個元素,首先它也要指定修飾的是哪個元素。所以CSS規(guī)范里面定義一種選擇頁面元素的語法。正好Selenium可以用它來定位頁面元素。CSS選擇器就是這種語法的使用。CSS選擇器選擇功能強(qiáng)大,不僅可以通過上述的屬性選擇,還可以根據(jù)元素的父子兄弟關(guān)系,子元素的順序,其他的任意元素屬性的有無或者屬性值,輸入焦點(diǎn),等等??傊浅5膹?qiáng)大,特別適用于上面方法都不好定位的時候使用。假如有如下的html片段。
- 根據(jù) tag 名 選擇
p {color: red;} #表示選擇所有的 p 元素
- 根據(jù) id ,前面加個#號
#food {color: blue;} #表示選擇ID為 food的 元素
- 3.根據(jù)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選擇的方式如下:
- 根據(jù)tag名,選中第一個span節(jié)點(diǎn)
find_element_by_css_selector('span')
選中所有的span節(jié)點(diǎn)
find_elements_by_css_selector('span')
- 根據(jù)id名
#選中所有id為food2的節(jié)點(diǎn)
find_elements_by_css_selector('#food2')
根據(jù)class名
#選中所有class屬性為vegetable的節(jié)點(diǎn)
find_elements_by_css_selector('.vegetable')
講到這里大家會想這些根據(jù)id,class,tag去找元素我們之前不是說過嗎,比如find_elements_by_id()、find_elements_by_class_name()這只是我們已前知識的另一種用法而已,我們?yōu)槭裁匆獙W(xué)它呢?這里就介紹它的強(qiáng)大之處,它有后代選擇的能力。
比如大家看這個表達(dá)式:
#food p
中間有個空格,剛才有說到空格在css里面不能亂加有特殊的含義,這個空格就是你要找的節(jié)點(diǎn),注意一定是找空格最后一個節(jié)點(diǎn),就是p節(jié)點(diǎn),空格前面是這個節(jié)點(diǎn)的上層節(jié)點(diǎn)的特性,這個表達(dá)式我們可以這樣理解:我們要找一個標(biāo)簽名是p的,然后這個標(biāo)簽有一個限制,他是在id是food 元素的內(nèi)部 ,我們要找id是 food 里面的所有p,空格就是后代的意思。
看一個例子:
div span
就是查找所有 div 元素 里面的 span 元素。
發(fā)現(xiàn)span是div的直接子元素, 就算不是直接子元素也可以,只要是內(nèi)部的就一樣可以。
3.4.1子元素選擇
前面我們學(xué)習(xí)了后代選擇器,比如后代選擇器:#choose_car option。選擇 id 為choose_car 的所有 option子元素,不管它們是否是直接子節(jié)點(diǎn)。如果您希望縮小范圍,只選擇某個元素的直接子節(jié)點(diǎn)元素,請使用子元素選擇器(Child selector)。
- 子元(child)選擇器
選擇元素的子元素,和后代選擇器不同(#food p )比如:
#choose_car > option 大于號表示你最終要選擇的option 元素是前面的這個元素的直接子節(jié)點(diǎn)
- 可以是很多級
ul > ol > li > em
方法是可以混合使用的比如div li > #abc這個表達(dá)式意思就是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的子元素)逗號的優(yōu)先級要比后代的優(yōu)先級低,逗號是最后算的。
#food > span,p
選擇 id為food的 所有span子元素 和 id為food的 所有的p元素:
#food > span ,#food > p
可以這樣選擇id為food的的所有子元素:
#food > *
*代表所有元素的意思
p button 與 p ,button大家能區(qū)分清楚嗎,p button意思是p元素里面所有的button元素,p ,button意思是所有的p和所有的button。
selenium也可以用這樣的語法:
eles = driver.find_elements_by_css_selector('p, button')
3.4.3 兄弟節(jié)點(diǎn)選擇
我們說過了 后代元素選擇、子元素選擇,現(xiàn)在我們說下相鄰兄弟選擇器。下面有這樣一段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>
選擇緊接在另一個元素后的元素,而且二者有相同的父元素。注意:兩個條件 緊接在后面、相同父元素 .這個表達(dá)式就是找到id為food2緊跟著的兄弟節(jié)點(diǎn)select節(jié)點(diǎn):
#food2 + select
如果我們想選擇的 只是在另一個元素后的兄弟元素,不一定要緊挨著,二者有相同的父元素 ,用下面的語法。
#food2 ~ select
大家注意“+”和“~”都是選擇后面的元素,#food + div 表示id是food的緊跟著的div,而#food ~ div不一定要緊接著,我們可以聯(lián)合其他語法使用,比如:
#many > div > p.special + p
選擇 #many 的子元素 div 里面的 子元素 p (類型為special) 的后面的兄弟節(jié)點(diǎn)。最后選中的元素是:

3.4.4 屬性選擇
比如:選擇所有具有style屬性的元素
*[style]
又比如:選擇P節(jié)點(diǎn)具有spec值為 len2 的元素,加引號一般用在 屬性值中間有空格的情況,如果沒有空格可以不加引號。這里有個特別要注意的地方,屬性選擇必須要完全一樣
p[spec='len2']
看下面的截圖,標(biāo)紅的部分是不會被選中的。

屬性選擇還有一種寫法,如果要選擇只要spec屬性包含len2的就被選擇那要怎么寫呢,在“=”前面加個“*”號就可以
p[spec*='len2']
或者
a[href*="baidu.com"]
以len2開頭的:
p[spec^='len2']
以len2結(jié)尾的:
p[spec$='len2']
同時滿足兩種屬性的:
p[class=special][name=p1]
CSS選擇器有很多的語法, 詳細(xì)的大家可以參考 這里http://www.w3school.com.cn/cssref/css_selectors.asp
我們css選擇器還有一種常用的方法:nth-child(n)比如p:nth-child(2)意思是首先選擇所有的p節(jié)點(diǎn),冒號表示一個限定,就是p必須是父元素的第二個子元素,這就是nth-child(2),注意不是說第二個p類型的子元素而就是第二個p元素比我我們看個例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>定位網(wǎng)頁元素</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 id="baidulink">轉(zhuǎn)到百度</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)
這個表達(dá)式是什么意思呢?屬于其父元素的第二個子元素,并且要是p ,我們這里第二個子元素是span,所以上面這個表達(dá)式是找不到的。
比如:
#food > p:nth-child(3)
這個表達(dá)式的意思是,屬于其父元素的第3個子元素,并且要是p,這就可以找到。這是正數(shù)第3個我們還可以倒數(shù)nth-last-child(n),還有一種按照類型排序的方法nth-of-type(),p:nth-of-type()和p:nth-child()區(qū)別是:p:nth-child(2)必須是父節(jié)點(diǎn)的第二個子節(jié)點(diǎn),p:nth-of-type(2)第二個p類型的子節(jié)點(diǎn)。
比如:屬于其父元素的倒數(shù)第二個子元素,并且是p
#food > p:nth-last-child(2)
找到的是:

比如:屬于其父元素的第二個p 類型的子元素
#food > p:nth-of-type(2)
找到的是:

比如:屬于其父元素的倒數(shù)第二個p 類型的子元素
#food > p:nth-last-of-type(2)
找到的是:

還有一種選擇元素的方式:not(p),選擇非 <p> 元素的每個元素。
比如:
#food > :not(p)
找到的是:

3.4.5 利用瀏覽器開發(fā)工具獲取 css seletor
對于不太好找的CSS selector的元素,我們可以通過瀏覽器開發(fā)工具幫助我們定位。在chrom瀏覽器里,按F12,打開開發(fā)工具窗口,點(diǎn)擊element標(biāo)簽,然后,在網(wǎng)頁窗口里面 點(diǎn)選我們要 選擇的元素,開發(fā)工具窗口會高亮顯示該元素對應(yīng)的html tag代碼。這時,我們可以 用鼠標(biāo)右鍵 點(diǎn)擊該代碼,在彈出的對話框中,依次選擇Copy --> Copy selector,如下圖所示,這樣,該元素的CSS selector就被拷貝到剪貼板了。

3.4.6 驗(yàn)證css表達(dá)式
當(dāng)我們要寫的自動化腳本比較復(fù)雜的時候,每次到python代碼中調(diào)試CSS選擇器(看看我們的css選擇器是否能選中元素)會非常的麻煩。因?yàn)楹苈?,我們可以利用瀏覽器的開發(fā)工具,直接在瀏覽器中進(jìn)行測試,看看我們寫的CSS選擇器是否能正確找到我們要的web元素。
有兩種方法, 一種是chrome瀏覽器,按F12,打開開發(fā)窗口,點(diǎn)擊元素標(biāo)簽(英文叫Element),按contrl + f,直接輸入css選擇器即可。選中的元素會高亮顯示。

這種方法優(yōu)點(diǎn), 填入內(nèi)容完全就是 css 選擇器, 缺點(diǎn): 也會做 字符匹配, 讓人有點(diǎn)迷糊。
另一種是:
chrome瀏覽器,按F12,打開開發(fā)窗口,點(diǎn)擊控制臺標(biāo)簽(英文叫console),在里面執(zhí)行$$(‘css selector’),其中css selector 就是css選擇器字符串。chrom界面如下所示:

如果能找到對象,返回的結(jié)果就不是空數(shù)組,就表示能找到web元素。而且鼠標(biāo)放在數(shù)組元素上,會高亮顯示對應(yīng)的web元素。這種方法 優(yōu)點(diǎn), 不會做 字符串匹配, 很清晰。缺點(diǎn): 要多輸入點(diǎn)內(nèi)容。
下面是一次作業(yè),可以用今天講的知識做一下
登錄 51job:http://www.51job.com
輸入搜索關(guān)鍵詞 "python", 地區(qū)選擇 "杭州"(注意,如果所在地已經(jīng)選中其他地區(qū),要去掉),搜索最新發(fā)布的職位, 抓取頁面信息。 得到如下的格式化信息:
Python開發(fā)工程師 | 杭州納帕科技有限公司 | 杭州 | 0.8-1.6萬/月 | 04-27
Python高級開發(fā)工程師 | 中浙信科技咨詢有限公司 | 杭州 | 1-1.5萬/月 | 04-27
高級Python開發(fā)工程師 | 杭州新思維計算機(jī)有限公司 | 杭州-西湖區(qū) | 1-1.5萬/月 | 04-27
解析:這里面就只有一個難點(diǎn),怎么只保證選擇 杭州 呢,我們可以定位一下選中的元素有什么特征,我們發(fā)現(xiàn) class="on" 就是選中,如果不選中就沒有class="on" 的值,確保只有杭州是選中的,這里要綜合使用 python 和Selenium 的知識,我們可以先把所有的城市過一遍,如果他是杭州 class值不等于 on 我們就 click 一下,如果選中了就不動他,如果是其他城市是反之,選中了就 click 下沒選中就不管它。
#從selenium里面導(dǎo)入webdriver
from selenium import webdriver
#指定chrom的驅(qū)動
#執(zhí)行到這里的時候Selenium會到指定的路徑將chrome driver程序運(yùn)行起來
driver = webdriver.Chrome('E:\ChromDriver\chromdriver2.43\chromedriver.exe')
driver.implicitly_wait(10)
driver.get('https://www.51job.com/')
#根據(jù)id找到輸入框,輸入python
driver.find_element_by_id('kwdselectid').send_keys('python')
#點(diǎn)擊工作地點(diǎn)
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
#用之前學(xué)過的attribute方法獲取class的值
cassvalue = one.get_attribute('class')
#
if cityname == '杭州':
if cassvalue != 'on':
one.click()
elif cityname != '杭州':
if cassvalue == 'on':
one.click()
#點(diǎn)擊確定按鈕
driver.find_element_by_id('work_position_click_bottom_save').click()
#點(diǎn)擊搜索按鈕
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?我們執(zhí)行到 cityEles = driver.find_elements_by_css_selector('#work_position_click_center_right_list_000000 em') 的時候其實(shí)界面不是一下子呈現(xiàn)出來的,導(dǎo)致我們?nèi)フ?em 的時候狀態(tài)沒來的急更新,有沒有選中是動態(tài)更新的,他在定位城市的時候前端的 js 動態(tài)的獲取了一些信息,然后再把這些城市有沒有選中呈現(xiàn)出來,這樣就導(dǎo)致了當(dāng)前獲取的東西并不是過了一段時間后更新的狀態(tài),就是不是穩(wěn)定的狀態(tài),之前一個臨時的狀態(tài),這里我們就得 sleep 等待一下,有人會有疑問不是有 implicitly_wait 嗎,implicitly_wait 是找不到才會等待這里是可以找到 em ,只是這里過了一段時間才刷新的,大家可以好好看下這段代碼。