lxml庫使用
我們可以利用python中的lxml庫來使用Xpath對HTML文檔進行搜尋。
選取節點:
nodename 選取此節點的所有子節點。
/ 從根節點選取。
// 從比對選擇的目前節點選擇文檔中的節點,而不考慮它們的位置。
. 選取目前節點。
… 選取目前節點的父節點。
@ 選取屬性。
from lxml import etree
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >first item<a/></li>
<li class="item-1"><a href="link2.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >second item<a/></li>
<li class="item-inactive"><a href="link3.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >third item<a/></li>
<li class="item-1"><a href="link4.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fourth item</a></li>
<li class="item-0"><a href="link5.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fifth item</a>
</ul>
</div>
'''
# 利用lxml庫的etree子產品調用HTML類将html文本進行初始化,構造一個Xpath解析對象。
html=etree.HTML(text)
result=etree.tostring(html) # 調用tostring()的方法可以輸出修正後的HTML代碼
print(type(result))
print(result.decode('utf-8')) # 結果是bytes類型,利用decode()将其轉化為str類型
#以下輸出結果中,殘缺的html文本被補全,并且自動添加了body、html節點
<class 'bytes'>
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >first item</a><a/></li>
<li class="item-1"><a href="link2.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >second item</a><a/></li>
<li class="item-inactive"><a href="link3.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >third item</a><a/></li>
<li class="item-1"><a href="link4.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fourth item</a></li>
<li class="item-0"><a href="link5.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fifth item</a>
</li></ul>
</div>
</body></html>
# 也可以直接讀取檔案進行解析
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=etree.tostring(html)
print(result.decode('utf-8'))
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>from lxml import etree
text = '''
</p><div>
<ul>
<li class="item-0"><a href="link1.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >first item</a></li>
<li class="item-1"><a href="link2.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >second item</a></li>
<li class="item-inactive"><a href="link3.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >third item</a></li>
<li class="item-1"><a href="link4.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fourth item</a></li>
<li class="item-0"><a href="link5.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fifth item</a>
</li></ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)</body></html>
所有節點
一般用 // 開頭的XPath規則來選擇所有符合要求的節點。
from lxml import etree
'''這裡使用*來比對所有節點,整個html文本的所有節點都會被擷取。
傳回一個清單,每個元素是Element類型,其後跟了節點的名稱。'''
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//*')
print(result)
[<Element html at 0x280b7673808>, <Element body at 0x280b764be48>, <Element p at 0x280b764bb88>, <Element div at 0x280b75d54c8>, <Element ul at 0x280b76736c8>, <Element li at 0x280b76738c8>, <Element a at 0x280b76735c8>, <Element li at 0x280b7673308>, <Element a at 0x280b7673848>, <Element li at 0x280b7673908>, <Element a at 0x280b7673508>, <Element li at 0x280b7673448>, <Element a at 0x280b7673408>, <Element li at 0x280b7673a48>, <Element a at 0x280b7673cc8>]
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
# 選取所有li節點,可以使用//,直接加上節點名稱即可,調用時直接使用xpath()方法
result=html.xpath('//li')
print(result)
[<Element li at 0x280b7626d08>, <Element li at 0x280b7673a08>, <Element li at 0x280b7673f48>, <Element li at 0x280b7673f88>, <Element li at 0x280b7673fc8>]
子節點
通過 / 或 // 來查找元素的節點或子孫節點
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li/a') # 查找li節點中的所有直接a子節點
print(result)
[<Element a at 0x280b75d5408>, <Element a at 0x280b7673f08>, <Element a at 0x280b7673dc8>, <Element a at 0x280b76735c8>, <Element a at 0x280b7673308>]
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//ul//a') # 查找ul節點下的所有a節點。
print(result)
# 如果用//ul/a就不會傳回結果,因為a不是ul的直接節點
[<Element a at 0x280b764b448>, <Element a at 0x280b7673e48>, <Element a at 0x280b7673d08>, <Element a at 0x280b7673e08>, <Element a at 0x280b7673408>]
父節點
當我們知道了子節點後,我們可以用 … 來查找父節點
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
# 查找順序:<a href='link4.html'>-->(直接父節點<li>)-->直接屬性class的内容
result=html.xpath('//a[@href="link4.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ]/../@class')
print(result)
['item-1']
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//a[@href="link4.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ]/parent::*/@class') #也可以用parent::來擷取
print(result)
['item-1']
屬性比對
我們可以通過@符号進行屬性過濾
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]') # 選取屬性class為'item-0'的li節點
print(result)
[<Element li at 0x280b7671bc8>, <Element li at 0x280b7673f08>]
文本擷取
利用text()方法擷取節點中文本
from lxml import etree
# 擷取所有<li class='item-0'>節點的文本,法一:
html=etree.parse('./test.html',etree.HTMLParser())
# 因為文本内容實際在a節點内,是以先要進入a節點,再用text()方法進行提取文本
result=html.xpath('//li[@class="item-0"]/a/text()')
print(result)
['first item', 'fifth item']
from lxml import etree
# 擷取所有<li class='item-0'>節點的文本,法二:
html=etree.parse('./test.html',etree.HTMLParser())
#使用//擷取其節點下所有文本内容,其中\r\n是補全後li節點内的内容
result=html.xpath('//li[@class="item-0"]//text()')
print(result)
['first item', 'fifth item', '\r\n ']
屬性擷取
同樣用@可以用來擷取屬性
from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li/a/@href') # 比對所有li節點下a節點的href屬性
print(result)
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
屬性多值比對
當某些節點的屬性包含多個值時,就無法單純的用xpath()方法比對,此時需要用contains()方法,第一個參數傳入屬性名稱,第二個參數傳入屬性值。
from lxml import etree
text='''
<li class="li li-first"><a href="link.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class,"li")]/a/text()')
print(result)
['first item']
多屬性比對
當多個屬性确定一個節點時,我們需要同時比對多個屬性,此時可以用運算符 and 來連接配接
from lxml import etree
text='''
<li class="li li-first" name="item"><a href="link.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >first item</a></i>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class,li) and @name="item"]/a/text()')
print(result)
['first item']
按序選擇
from lxml import etree
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >first item</a></li>
<li class="item-1"><a href="link2.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >second item</a></li>
<li class="item-inactive"><a href="link3.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ><span class="bold">third item</span></a></li>
<li class="item-1"><a href="link4.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fourth item</a></li>
<li class="item-0"><a href="link5.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fifth item</a></li>
</ul>
</div>
'''
html=etree.HTML(text)
result=html.xpath('//li[1]/a/text()') # 查找第一個li節點内的文本
print(result)
result=html.xpath('//li[last()]/a/text()') # 查找最後一個節點内的文本
print(result)
result=html.xpath('//li[position()<3]/a/text()') # 查找位置小于3的文本
print(result)
result=html.xpath('//li[last()-2]/a/text()') # 查找倒數第三位的節點
print(result)
['first item']
['fifth item']
['first item', 'second item']
[]
節點軸選擇
ancestor:比對目前節點的所有祖先節點
attribute:比對目前節點的所有屬性值
child:比對目前節點的所有子節點
decendant:比對目前節點的所有子孫節點
following:比對目前節點之後的所有節點
following-sibling:比對目前節點之後的所有同級節點
from lxml import etree
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ><span>first item</span></a></li>
<li class="item-1"><a href="link2.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >second item</a></li>
<li class="item-inactive"><a href="link3.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ><span class="bold">third item</span></a></li>
<li class="item-1"><a href="link4.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fourth item</a></li>
<li class="item-0"><a href="link5.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >fifth item</a></li>
</ul>
</div>
'''
html=etree.HTML(text)
result=html.xpath('//li[1]/ancestor::*') # 調用ancestor軸,比對第一個li節點的所有祖先節點
print(result)
result=html.xpath('//li[1]/ancestor::div') # 調用ancestor軸,比對第一個li節點的祖先節點中的div節點
print(result)
result=html.xpath('//li[1]/attribute::*') # 調用attribute軸,比對第一個li節點的所有屬性值
print(result)
result=html.xpath('//li[1]/child::a[@href="link1.html" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ]') # 調用child軸,比對第一個li節點的中特定屬性的a節點
print(result)
result=html.xpath('//li[1]/descendant::span') # 調用descendant軸,比對第一個節點的所有子孫節點中的span節點
print(result)
result=html.xpath('//li[1]/following::*[2]') # 調用following軸,比對第一個節點的後續節點的第二個節點
print(result)
result=html.xpath('//li[1]/following-sibling::*') # 調用following-sibling軸,擷取第一個li節點的所有同級節點
print(result)
[<Element html at 0x280b765ee08>, <Element body at 0x280b7673408>, <Element div at 0x280b767c708>, <Element ul at 0x280b767cdc8>]
[<Element div at 0x280b767c708>]
['item-0']
[<Element a at 0x280b7687cc8>]
[<Element span at 0x280b7680208>]
[<Element a at 0x280b767c708>]
[<Element li at 0x280b7673408>, <Element li at 0x280b7680208>, <Element li at 0x280b7680248>, <Element li at 0x280b7680348>]
參考:崔慶才《python3網絡爬蟲開發實戰》