肝了三天三夜,一文道盡 Python的 Xpath 解析!
大家在寫爬蟲時,往往獲取到網(wǎng)頁之后,需要從網(wǎng)頁中提取我們需要的信息。這時候就需要用到 xpath 或者 css 選擇器來定位頁面元素信息。但是,由于這兩者都是非人性化的語法,導(dǎo)致好多人望而生畏,經(jīng)常為這個發(fā)愁。
今天我就嘗試用一篇文章來道盡 xpath 解析 HTML 的方方面面,希望大家看完這篇文章后,從此不再害怕 xpath 解析。
路徑表達(dá)式
- nodename:選取此節(jié)點的所有子節(jié)點
- /:從當(dāng)前節(jié)點選取直接子節(jié)點
- //:從當(dāng)前接點選取子孫節(jié)點
- .:選取當(dāng)前節(jié)點
- ..:選取當(dāng)前接點的父節(jié)點
- @:選取屬性
我們先放上一段 HTML 代碼:
- <html>
- <head>
- <title>
- Xpath test page
- </title>
- </head>
- <body>
- <div class="navli">
- <span class="nav_tit">
- <a href="https://www.baidu.com/">
- 百度
- </a>
- <i class="group" />
- </span>
- </div>
- <div class="navli">
- <span class="nav_tit">
- <a href="https://news.cctv.com/">
- 新聞頻道
- </a>
- </span>
- </div>
- <div class="navli">
- <span class="nav_tit">
- <a href="https://sports.cctv.com/">
- 體育頻道
- </a>
- </span>
- </div>
- </body>
- </html>
接下來,我們針對這段 HTML 代碼來進(jìn)行 xpath 解析。
要進(jìn)行 xpath 解析,我們先要將 HTML 文本轉(zhuǎn)化成對象:
- from lxml import etree
- text = '''
- <div>
- <ul id='ultest'>
- <li class="item-0"><a href="link1.html">first item</a></li>
- <li class="item-1"><a href="link2.html">second item</a></li>
- <li class="item-inactive"><a href="link3.html">third item</a></li>
- <li class="item-1"><a href="link4.html"><span>fourth item</span></a></li>
- <li class="item-0"><a href="link5.html">fifth item</a> # 注意,此處缺少一個 </li> 閉合標(biāo)簽
- </ul>
- </div>
- '''
- # 調(diào)用HTML類進(jìn)行初始化,這樣就成功構(gòu)造了一個XPath解析對象。
- page = etree.HTML(text)
- print(type(page))
我們可以看到打印的結(jié)果:
- <class 'lxml.etree._Element'>
nodename
nodename 表示根據(jù)標(biāo)簽名字選取標(biāo)簽,注意只會選擇子標(biāo)簽!比如:如果是兒子的兒子則選取不到。
- print(page.xpath("body"))
- //[<Element body at 0x1966d1c48c0>]
- print(page.xpath("ul"))
- // []
這個 nodename 我有點不是太清楚,當(dāng)我使用 body 時,可以找到出 body 節(jié)點元素,但是使用 ul 時,找不到 ul 節(jié)點元素,打印的是空。這個網(wǎng)上搜索也沒有什么準(zhǔn)確的答案,如果你知道這里面的原理,還請告訴我。
/
/ 表示從根節(jié)點選取一級一級篩選(不能跳)。
- print(page.xpath("/html"))
- // [<Element html at 0x27107f41100>]
- print(page.xpath("/body"))
- // []
可以看到,我選取根節(jié)點 html ,可以打印出根節(jié)點元素,而我選取 body 打印時,是找不到的,這個符號只能從根節(jié)點開始找。
//
// 表示從匹配選擇的當(dāng)前節(jié)點選擇文檔中的節(jié)點,而不考慮它們的位置。注意:是所有符合條件的。
- print(page.xpath("//li"))
- // [<Element li at 0x1cd2a325780>, <Element li at 0x1cd2a325840>, <Element li at 0x1cd2a3259c0>, <Element li at 0x1cd2a325b00>, <Element li at 0x1cd2a325ac0>]
.
. 表示選取當(dāng)前標(biāo)簽。
- ul = page.xpath("//ul")
- print(ul)
- print(ul[0].xpath("."))
- print(ul[0].xpath("./li"))
- // [<Element ul at 0x1cd2a325840>]
- // [<Element ul at 0x1cd2a325840>]
- // [<Element li at 0x1cd2a325700>, <Element li at 0x1cd2a325b00>, <Element li at 0x1cd2a325640>, <Element li at 0x1cd2a325ac0>, <Element li at 0x1cd2a325c00>]
我們先定位到 ul 元素節(jié)點,這里的結(jié)果是一個列表,然后再打印當(dāng)前節(jié)點列表的第一個 ul,接著我們打印這個 ul 節(jié)點的子節(jié)點 li。
..
.. 表示選取當(dāng)前標(biāo)簽的父節(jié)點。
- print(ul[0].xpath(".."))
- // []
這里打印第一個 ul 節(jié)點的父元素,也就是 div 。
@
@ 表示獲取標(biāo)簽的屬性值。
- print(ul[0].xpath("@id"))
- // ['ultest']
我們打印第一個 ul 節(jié)點的 id 屬性,可以看到結(jié)果是 ‘ultest’。
謂語
謂語用來查找某個或某些特定的節(jié)點或者包含某個指定值的節(jié)點。謂語被嵌在方括號中。
- //a[n] n為大于零的整數(shù),代表子元素排在第n個位置的<a>元素
- //a[last()] last() 代表子元素排在最后個位置的<a>元素
- //a[last()-] 和上面同理,代表倒數(shù)第二個
- //a[position()<3] 位置序號小于3,也就是前兩個,這里我們可以看出xpath中的序列是從1開始
- //a[@href] 擁有href的<a>元素
- //a[@href='www.baidu.com'] href屬性值為'www.baidu.com'的<a>元素
- //book[@price>2] price值大于2的<book>元素
同樣的,我們來舉一些例子:
- # 第三個li標(biāo)簽
- print(page.xpath('//ul/li[3]'))
- # 最后一個li標(biāo)簽
- print(page.xpath('//ul/li[last()]'))
- # 倒數(shù)第二個li標(biāo)簽
- print(page.xpath('//ul/li[last()-1]'))
- # 序號小于3的li標(biāo)簽
- print(page.xpath('//ul/li[position()<3]'))
- # 有class屬性的li標(biāo)簽
- print(page.xpath('//li[@class]'))
- # class屬性為item-inactive的li標(biāo)簽
- print(page.xpath("//li[@class='item-inactive']"))
獲取文本
text()
我們用text()獲取某個節(jié)點下的文本:
- print(page.xpath('//ul/li/a/text()'))
- // ['first item', 'second item', 'third item', 'fourth item', 'fifth item']
string()
我們用string()獲取某個節(jié)點下所有的文本:
- print(page.xpath('string(//ul)'))
輸出內(nèi)容為:
- first item
- second item
- third item
- fourth item
- fifth item # 注意,此處缺少一個 閉合標(biāo)簽
fifth item # 注意,此處缺少一個 閉合標(biāo)簽
通配符
- * 任意元素
- @* 任意屬性
* 表示匹配任何元素節(jié)點:
- print(page.xpath('//li/*'))
- // [<Element a at 0x208931f0f00>, <Element a at 0x208931f0f40>, <Element a at 0x208931f0c40>, <Element a at 0x208931f0d80>, <Element a at 0x208931ff080>]
@* 表示匹配任何屬性節(jié)點:
- print(page.xpath('//li/@*'))
- // ['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
或運算
通過在路徑表達(dá)式中使用"|"運算符,可以實現(xiàn)選取若干個路徑。
- # 選取所有的li和a節(jié)點
- print(page.xpath("//li|//a"))
- // [<Element li at 0x29bb7190ac0>, <Element a at 0x29bb7190b00>, <Element li at 0x29bb7190f00>, <Element a at 0x29bb7190dc0>, <Element li at 0x29bb7190fc0>, <Element a at 0x29bb7190e00>, <Element li at 0x29bb7190f80>, <Element a at 0x29bb71b1080>, <Element li at 0x29bb71b1040>, <Element a at 0x29bb7190cc0>]
函數(shù)
xpath內(nèi)置很多函數(shù)。更多函數(shù)查看https://www.w3school.com.cn/xpath/xpath_functions.asp。
- contains(string1,string2)
- starts-with(string1,string2)
- text()
- last()
- position()
- node()
contains
有的時候,class作為選擇條件的時候不合適@class='....' 這個是完全匹配,當(dāng)網(wǎng)頁樣式發(fā)生變化時,class或許會增加或減少像active的class。用contains就能很方便。
- print(page.xpath("//*[contains(@class, 'item-inactive')]"))
- // []
starts-with
- print(page.xpath("//*[starts-with(@class, 'item-inactive')]"))
- // [<Element li at 0x1a297641d00>]
其他幾個函數(shù),我們在上面使用過。注意,并不是所有的 xpath 函數(shù)python都會支持,比如 ends-with(string1,string2) 和 upper-case(string) 就不支持。
節(jié)點軸選擇
ancestor軸
調(diào)用 ancestor 軸,獲取所有祖先節(jié)點。其后需要跟兩個冒號,然后是節(jié)點的選擇器。返回結(jié)果:第一個li節(jié)點的所有祖先節(jié)點。
- print(page.xpath('//li[1]/ancestor::*'))
- // [, , , ]
attribute軸
調(diào)用 attribute 軸,獲取所有屬性值。返回結(jié)果:li節(jié)點的所有屬性值。
- print(page.xpath('//li[1]/attribute::*'))
- // ['item-0']
child軸
調(diào)用 child 軸,獲取所有直接子節(jié)點。返回結(jié)果:選取 href 屬性為 link1.html 的 a 子節(jié)點。
- print(page.xpath('//li[1]/child::a[@href="link1.html"]'))
- // [<Element a at 0x13972af5b40>]
descendant軸
調(diào)用 descendant 軸,獲取所有子孫節(jié)點。同時加了限定條件。返回結(jié)果:選取 li 節(jié)點下的子孫節(jié)點里的 span 節(jié)點。
- print(page.xpath('//li[4]/descendant::span'))
- // [<Element span at 0x1a4d5700d00>]
following軸
調(diào)用 following 軸,獲取當(dāng)前節(jié)點之后的所有節(jié)點。
- print(page.xpath('//li[4]/following::*[2]'))
- // [<Element a at 0x1583f8c0d00>]
following-sibling軸
調(diào)用 following-sibling 軸,獲取當(dāng)前節(jié)點之后的所有同級節(jié)點。
- print(page.xpath('//li[4]/following-sibling::*'))
- // []
總結(jié)
到這里,我們的 xpath 學(xué)習(xí)之路就結(jié)束了,文章中基本涵蓋了大家需要用的的 xpath 解析方法。大家看一遍沒記住不要緊,以后遇到此類解析直接搬出這篇文章對照著寫就行。