爬蟲解析庫:XPath

XPath

????XPath膛堤,全稱 XML Path Language,即 XML 路徑語言壳影,它是一門在 XML 文檔中查找信息的語言拱层。最初是用來搜尋 XML 文檔的,但同樣適用于 HTML 文檔的搜索宴咧。所以在做爬蟲時完全可以使用 XPath 做相應的信息抽取根灯。

1. XPath 概覽

????XPath 的選擇功能十分強大,它提供了非常簡潔明了的路徑選擇表達式掺栅。另外烙肺,它還提供了超過 100 個內建函數,用于字符串氧卧、數值桃笙、時間的匹配以及節(jié)點、序列的處理等沙绝,幾乎所有想要定位的節(jié)點都可以用 XPath 來選擇搏明。
????官方文檔:https://www.w3.org/TR/xpath/

2. XPath 常用規(guī)則

表達式 描述
nodename 選取此節(jié)點的所有子節(jié)點
/ 從當前節(jié)點選區(qū)直接子節(jié)點
// 從當前節(jié)點選取子孫節(jié)點
. 選取當前節(jié)點
.. 選取當前節(jié)點的父節(jié)點
@ 選取屬性

????這里列出了 XPath 的常用匹配規(guī)則,示例如下:

//title[@lang='eng']

????這是一個 XPath 規(guī)則闪檬,代表的是選擇所有名稱為 title星著,同時屬性 lang 的值為 eng 的節(jié)點,后面會通過 Python 的 lxml 庫粗悯,利用 XPath 進行 HTML 的解析虚循。

3. 安裝

windows->python3環(huán)境下:pip install lxml

4. 實例引入

from lxml import etree

text = '''
<div>
<ul>
<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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''

????首先導入 lxml 庫的 etree 模塊,然后聲明一段 HTML 文本样傍,調用 HTML 類進行初始化邮丰,成功構造一個 XPath 解析對象。注意:HTML 文本中最后一個 li 節(jié)點沒有閉合铭乾,但是 etree 模塊可以自動修正 HTML 文本。
????調用 tostring() 方法即可輸出修正后的 HTML 代碼娃循,但結果是 bytes 類型炕檩,可以用 decode() 方法將其轉化為 str 類型,結果如下:

<html><body><div>
<ul>
<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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div>
</body></html>

????經過處理后捌斧,li 節(jié)點標簽被補全笛质,并且還自動添加了 body、html 節(jié)點捞蚂。
????還可以直接讀取文本文件進行解析:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))

????test.html 的內容就是上面例子的 HTML 代碼妇押,內容如下:

<div>
<ul>
<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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>

????這次輸出結果略有不同,多了一個 DOCTYPE 聲明姓迅,不過對解析沒有任何影響敲霍,結果如下:

<html><body><div>
<ul>
<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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div></body></html>

5. 所有節(jié)點

????用以 // 開頭的 XPath 規(guī)則來選取所有符合要求的節(jié)點:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)

# 運行結果
"""
[<Element html at 0x1d6610ebe08>, <Element body at 0x1d6610ebf08>, 
<Element div at 0x1d6610ebf48>, <Element ul at 0x1d6610ebf88>, 
<Element li at 0x1d6610ebfc8>, <Element a at 0x1d661115088>, 
<Element li at 0x1d6611150c8>, <Element a at 0x1d661115108>, 
<Element li at 0x1d661115148>, <Element a at 0x1d661115048>, 
<Element li at 0x1d661115188>, <Element a at 0x1d6611151c8>, 
<Element li at 0x1d661115208>, <Element a at 0x1d661115248>]
"""

????* 代表匹配所有節(jié)點俊马,返回的結果是一個列表,每個元素都是一個 Element 類型肩杈,后跟節(jié)點名稱柴我。
????也可以指定匹配的節(jié)點名稱:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li')
print(result)

# 運行結果
[<Element li at 0x23fb219af08>, <Element li at 0x23fb219af48>, <Element li at 0x23fb219af88>, 
<Element li at 0x23fb219afc8>, <Element li at 0x23fb21c5048>]

<Element li at 0x23fb219af08>

????取出其中某個對象時可以直接用索引。

6. 子節(jié)點

????通過 / 或 // 即可查找元素的子節(jié)點或子孫節(jié)點扩然。選擇 li 節(jié)點的所有直接 a 子節(jié)點:

from lxml import etree

html = etree.parse('.test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)

????此處的 / 用來獲取直接子節(jié)點艘儒,如果要獲取所有子孫節(jié)點,將 / 換成 // 即可夫偶。

7. 父節(jié)點

????知道子節(jié)點界睁,查詢父節(jié)點可以用 .. 來實現(xiàn):

# 獲得 href 屬性為 link4.html 的 a 節(jié)點的父節(jié)點的 class 屬性

# 方法一
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
print(result)

# 方法二
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/parent::*/@class')
print(result)

# 運行結果:['item-1']

8. 屬性匹配

????匹配時可以用@符號進行屬性過濾:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-inactive"]')
print(result)

# 運行結果:[<Element li at 0x2089793a3c8>]

9. 文本獲取

????有兩種方法:一是獲取文本所在節(jié)點后直接獲取文本,二是使用 //兵拢。

# 第一種
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)

# 第二種
from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
print(result)

????第二種方法會獲取到補全代碼時換行產生的特殊字符翻斟,推薦使用第一種方法,可以保證獲取的結果是整潔的卵佛。

10. 屬性獲取

????在 XPath 語法中杨赤,@符號相當于過濾器,可以直接獲取節(jié)點的屬性值:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)

# 運行結果:['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']

11. 屬性多值匹配

????有時候截汪,某些節(jié)點的某個屬性可能有多個值:

from lxml import etree

text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li")]/a/text()')
print(result)

# 運行結果:['first item']

12. 多屬性匹配

????當前節(jié)點有多個屬性時疾牲,需要同時進行匹配:

from lxml import etree

text = '''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)

# 運行結果:['first item']
擴展:XPath 運算符
運算符 描述 實例 返回值
or age=18 or age=20 age=18:True;age=21:False
and age>18 and age<21 age=20:True衙解;age=21:False
mod 計算除法的余數 5 mod 2 1
| 計算兩個節(jié)點集 //book | //cd 返回所有擁有 book 和 cd 元素的節(jié)點集
+ 加法 5 + 3 8
- 減法 5 - 3 2
* 乘法 5 * 3 15
div 除法 8 div 4 2
= 等于 age=19 判斷簡單阳柔,不再贅述
!= 不等于 age!=19 判斷簡單,不再贅述
< 小于 age<19 判斷簡單蚓峦,不再贅述
<= 小于等于 age<=19 判斷簡單舌剂,不再贅述
> 大于 age>19 判斷簡單,不再贅述
>= 大于等于 age>=19 判斷簡單暑椰,不再贅述

13. 按序選擇

????匹配結果有多個節(jié)點霍转,需要選中第二個或最后一個,可以按照中括號內加索引或其他相應語法獲得:

from lxml import etree

text = '''
<div>
<ul>
<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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''

html = etree.HTML(text)
# 獲取第一個
result = html.xpath('//li[1]/a/text()')
print(result)
# 獲取最后一個
result = html.xpath('//li[last()]/a/text()')
print(result)
# 獲取前兩個
result = html.xpath('//li[position()<3]/a/text()')
print(result)
# 獲取倒數第三個
result = html.xpath('//li[last()-2]/a/text()')
print(result)

"""
運行結果:
['first item']
['fifth item']
['first item', 'second item']
['third item']
"""

XPath 中提供了100多個函數一汽,包括存取避消、數值、邏輯召夹、節(jié)點岩喷、序列等處理功能,具體作用可以參考:
http://www.w3school.com.cn/xpath/xpath_functions.asp

14. 節(jié)點軸選擇

????XPath 提供了很多節(jié)點軸選擇方法监憎,包括子元素纱意、兄弟元素、父元素鲸阔、祖先元素等:

from lxml import etree

text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html"><span>first item</span></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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''

html = etree.HTML(text)
# 獲取所有祖先節(jié)點
result = html.xpath('//li[1]/ancestor::*')
print(result)
# 獲取 div 祖先節(jié)點
result = html.xpath('//li[1]/ancestor::div')
print(result)
# 獲取當前節(jié)點所有屬性值
result = html.xpath('//li[1]/attribute::*')
print(result)
# 獲取 href 屬性值為 link1.html 的直接子節(jié)點
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result)
# 獲取所有的的子孫節(jié)點中包含 span 節(jié)點但不包含 a 節(jié)點
result = html.xpath('//li[1]/descendant::span')
print(result)
# 獲取當前所有節(jié)點之后的第二個節(jié)點
result = html.xpath('//li[1]/following::*[2]')
print(result)
# 獲取當前節(jié)點之后的所有同級節(jié)點
result = html.xpath('//li[1]/following-sibling::*')
print(result)

"""
[<Element html at 0x231a8965388>, <Element body at 0x231a8965308>, <Element div at 0x231a89652c8>, <Element ul at 0x231a89653c8>]
[<Element div at 0x231a89652c8>]
['item-0']
[<Element a at 0x231a89653c8>]
[<Element span at 0x231a89652c8>]
[<Element a at 0x231a89653c8>]
[<Element li at 0x231a8965308>, <Element li at 0x231a8965408>, <Element li at 0x231a8965448>, <Element li at 0x231a8965488>]
"""

更多參考文檔:
軸的用法:http://www.w3school.com.cn/xpath/xpath_axes.asp
XPath 的用法:http://www.w3school.com.cn/xpath/index.asp
Python lxml 的用法:http://lxml.de

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末偷霉,一起剝皮案震驚了整個濱河市迄委,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腾它,老刑警劉巖跑筝,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瞒滴,居然都是意外死亡曲梗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門妓忍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虏两,“玉大人,你說我怎么就攤上這事世剖《ò眨” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵旁瘫,是天一觀的道長祖凫。 經常有香客問我,道長酬凳,這世上最難降的妖魔是什么惠况? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮宁仔,結果婚禮上稠屠,老公的妹妹穿的比我還像新娘。我一直安慰自己翎苫,他們只是感情好权埠,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著煎谍,像睡著了一般攘蔽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呐粘,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天秩彤,我揣著相機與錄音,去河邊找鬼事哭。 笑死,一個胖子當著我的面吹牛瓜富,可吹牛的內容都是我干的鳍咱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼与柑,長吁一口氣:“原來是場噩夢啊……” “哼谤辜!你這毒婦竟也來了蓄坏?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤丑念,失蹤者是張志新(化名)和其女友劉穎涡戳,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體脯倚,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡渔彰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了推正。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恍涂。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖植榕,靈堂內的尸體忽然破棺而出再沧,到底是詐尸還是另有隱情,我是刑警寧澤尊残,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布炒瘸,位于F島的核電站,受9級特大地震影響寝衫,放射性物質發(fā)生泄漏顷扩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一竞端、第九天 我趴在偏房一處隱蔽的房頂上張望屎即。 院中可真熱鬧,春花似錦事富、人聲如沸技俐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雕擂。三九已至,卻和暖如春贱勃,著一層夾襖步出監(jiān)牢的瞬間井赌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工贵扰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仇穗,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓戚绕,卻偏偏與公主長得像纹坐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舞丛,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容