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