09. 數(shù)據(jù)提取-XPath
之前 BeautifulSoup 的用法,這個已經(jīng)是非常強大的庫了,不過還有一些比較流行的解析庫,例如 lxml,使用的是 Xpath 語法,同樣是效率比較高的解析方法。如果大家對 BeautifulSoup 使用不太習慣的話,可以嘗試下 Xpath
http://lxml.de/index.html
http://www.w3school.com.cn/xpath/index.asp
pip install lxml
3. XPath語法
XPath 是一門在 XML 文檔中查找信息的語言。XPath 可用來在 XML 文檔中對元素和屬性進行遍歷。XPath 是 W3C XSLT 標準的主要元素,并且 XQuery 和 XPointer 都構(gòu)建于 XPath 表達之上
3.1 節(jié)點的關(guān)系
父(Parent)
子(Children)
同胞(Sibling)
先輩(Ancestor)
后代(Descendant)
3.2 選取節(jié)點
3.2.1 常用的路徑表達式
表達式描述nodename選取此節(jié)點的所有子節(jié)點/從根節(jié)點選取//從匹配選擇的當前節(jié)點選擇文檔中的節(jié)點,而不考慮它們的位置.選取當前節(jié)點..選取當前節(jié)點的父節(jié)點@選取屬性
3.2.2 通配符
XPath 通配符可用來選取未知的 XML 元素。
通配符描述舉例結(jié)果*匹配任何元素節(jié)點xpath('div/*')獲取div下的所有子節(jié)點@*匹配任何屬性節(jié)點xpath('div[@*]')選取所有帶屬性的div節(jié)點node()匹配任何類型的節(jié)點
3.2.3 選取若干路徑
通過在路徑表達式中使用“|”運算符,您可以選取若干個路徑
表達式結(jié)果xpath('//div|
//table')獲取所有的div與table節(jié)點
3.2.4 謂語
謂語被嵌在方括號內(nèi),用來查找某個特定的節(jié)點或包含某個制定的值的節(jié)點
表達式結(jié)果xpath('/body/div[1]')選取body下的第一個div節(jié)點xpath('/body/div[last()]')選取body下最后一個div節(jié)點xpath('/body/div[last()-1]')選取body下倒數(shù)第二個節(jié)點xpath('/body/div[positon()<3]')選取body下前丙個div節(jié)點xpath('/body/div[@class]')選取body下帶有class屬性的div節(jié)點xpath('/body/div[@class="main"]')選取body下class屬性為main的div節(jié)點xpath('/body/div[price>35.00]')選取body下price元素大于35的div節(jié)點
3.2.5 XPath 運算符
運算符描述實例返回值計算兩個節(jié)點集//book+加法6 + 410–減法6 – 42*乘法6 * 424div除法8 div 42=等于price=9.80如果 price 是 9.80,則返回 true。如果 price 是 9.90,則返回 false。!=不等于price!=9.80如果 price 是 9.90,則返回 true。如果 price 是 9.80,則返回 false。<小于price<9.80如果 price 是 9.00,則返回 true。如果 price 是 9.90,則返回 false。<=小于或等于price<=9.80如果 price 是 9.00,則返回 true。如果 price 是 9.90,則返回 false。>大于price>9.80如果 price 是 9.90,則返回 true。如果 price 是 9.80,則返回 false。>=大于或等于price>=9.80如果 price 是 9.90,則返回 true。如果 price 是 9.70,則返回 false。or或price=9.80 or price=9.70如果 price 是 9.80,則返回 true。如果 price 是 9.50,則返回 false。and與price>9.00 and price<9.90如果 price 是 9.80,則返回 true。如果 price 是 8.50,則返回 false。mod計算除法的余數(shù)5 mod 21
3.3 使用
3.3.1 小例子
from lxml import etree
text = '''
<div>
? ?<ul>
? ? ? ? <li><a href="link1.html">first item</a></li>
? ? ? ? <li><a href="link2.html">second item</a></li>
? ? ? ? <li><a href="link3.html">third item</a></li>
? ? ? ? <li><a href="link4.html">fourth item</a></li>
? ? ? ? <li><a href="link5.html">fifth item</a>
? ? </ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)
首先我們使用 lxml 的 etree 庫,然后利用 etree.HTML 初始化,然后我們將其打印出來。
其中,這里體現(xiàn)了 lxml 的一個非常實用的功能就是自動修正 html 代碼,大家應(yīng)該注意到了,最后一個 li 標簽,其實我把尾標簽刪掉了,是不閉合的。不過,lxml 因為繼承了 libxml2 的特性,具有自動修正 HTML 代碼的功能。
所以輸出結(jié)果是這樣的
<html><body>
<div>
? ?<ul>
? ? ? ? <li><a href="link1.html">first item</a></li>
? ? ? ? <li><a href="link2.html">second item</a></li>
? ? ? ? <li><a href="link3.html">third item</a></li>
? ? ? ? <li><a href="link4.html">fourth item</a></li>
? ? ? ? <li><a href="link5.html">fifth item</a></li>
</ul>
</div>
</body></html>
不僅補全了 li 標簽,還添加了 body,html 標簽。 文件讀取
除了直接讀取字符串,還支持從文件讀取內(nèi)容。比如我們新建一個文件叫做 hello.html,內(nèi)容為
<div>
? ?<ul>
? ? ? ? <li><a href="link1.html">first item</a></li>
? ? ? ? <li><a href="link2.html">second item</a></li>
? ? ? ? <li><a href="link3.html"><span>third item</span></a></li>
? ? ? ? <li><a href="link4.html">fourth item</a></li>
? ? ? ? <li><a href="link5.html">fifth item</a></li>
? ? </ul>
</div>
利用 parse 方法來讀取文件
from lxml import etree
html = etree.parse('hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)
同樣可以得到相同的結(jié)果
3.3.2 XPath具體使用
依然以上一段程序為例
獲取所有的
<li>
標簽
from lxml import etree
html = etree.parse('hello.html')
print (type(html))
result = html.xpath('//li')
print (result)
print (len(result))
print (type(result))
print (type(result[0]))
運行結(jié)果
<type 'lxml.etree._ElementTree'>
[<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]
<type 'list'>
<type 'lxml.etree._Element'>
可見,etree.parse 的類型是 ElementTree,通過調(diào)用 xpath 以后,得到了一個列表,包含了 5 個 <li>
元素,每個元素都是 Element 類型
獲取
<li>
標簽的所有 class
result = html.xpath('//li/@class')
print (result)
運行結(jié)果
['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
獲取
<li>
標簽下 href 為 link1.html 的<a>
標簽
result = html.xpath('//li/a[@href="link1.html"]')
print (result)
運行結(jié)果
[<Element a at 0x10ffaae18>]
獲取
<li>
標簽下的所有<span>
標簽
注意: 這么寫是不對的
result = html.xpath('//li/span')
#因為 / 是用來獲取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用雙斜杠
result = html.xpath('//li//span')
print(result)
運行結(jié)果
[<Element span at 0x10d698e18>]
獲取
<li>
標簽下的所有 class,不包括<li>
result = html.xpath('//li/a//@class')
print (resul)t
#運行結(jié)果
['blod']
獲取最后一個
<li>
的<a>
的 href
result = html.xpath('//li[last()]/a/@href')
print (result)
運行結(jié)果
['link5.html']
獲取倒數(shù)第二個元素的內(nèi)容
result = html.xpath('//li[last()-1]/a') print (result[0].text)
運行結(jié)果
fourth item
獲取 class 為 bold 的標簽名
result = html.xpath('//*[@class="bold"]') print (result[0].tag)
運行結(jié)果
span
選擇XML文件中節(jié)點:
element(元素節(jié)點)
attribute(屬性節(jié)點)
text (文本節(jié)點)
concat(元素節(jié)點,元素節(jié)點)
comment (注釋節(jié)點)