Python中利用xpath解析HTML的方法
在進(jìn)行網(wǎng)頁(yè)抓取的時(shí)候,分析定位html節(jié)點(diǎn)是獲取抓取信息的關(guān)鍵,目前我用的是lxml模塊(用來(lái)分析XML文檔結(jié)構(gòu)的,當(dāng)然也能分析html結(jié)構(gòu)), 利用其lxml.html的xpath對(duì)html進(jìn)行分析,獲取抓取信息。
首先,我們需要安裝一個(gè)支持xpath的python庫(kù)。目前在libxml2的網(wǎng)站上被推薦的python binding是lxml,也有beautifulsoup,不嫌麻煩的話還可以自己用正則表達(dá)式去構(gòu)建,本文以lxml為例講解。
假設(shè)有如下的HTML文檔:
<html>
<body>
<form>
<div id='leftmenu'>
<h3>text</h3>
<ul id='china'><!-- first location -->
<li>...</li>
<li>...</li>
......
</ul>
<ul id='england'><!-- second location-->
<li>...</li>
<li>...</li>
......
</ul>
</div>
</form>
</body>
</html>
直接使用lxml處理:
import codecs
from lxml import etree
f=codecs.open("ceshi.html","r","utf-8")
content=f.read()
f.close()
tree=etree.HTML(content)
etree提供了HTML這個(gè)解析函數(shù),現(xiàn)在我們可以直接對(duì)HTML使用xpath了,是不是有點(diǎn)小激動(dòng),現(xiàn)在就嘗試下吧。
在使用xpath之前我們先來(lái)看看作為對(duì)照的jQuery和RE。
在jQuery里要處理這種東西就很簡(jiǎn)單,特別是假如那個(gè)ul節(jié)點(diǎn)有id的話(比如是<ul id='china'>):
$("#china").each(function(){...});
具體到此處是:
找到id為leftmenu的節(jié)點(diǎn),在其下找到一個(gè)內(nèi)容包含為”text”的h3節(jié)點(diǎn),再取其接下來(lái)的一個(gè)ul節(jié)點(diǎn)。
在python里要是用RE來(lái)處理就略麻煩一些:
block_pattern=re.compile(u"<h3>檔案</h3>(.*?)<h3>", re.I | re.S) m=block_pattern.findall(content) item_pattern=re.compile(u"<li>(.*?)</li>", re.I | re.S) items=item_pattern.findall(m[0]) for i in items: print i
那么用xpath要怎么做呢?其實(shí)跟jQuery是差不多的:
nodes=tree.xpath("/descendant::ul[@id='china']")
當(dāng)然,現(xiàn)在沒(méi)有id的話也就只能用類似于jQuery的方法了。完整的xpath應(yīng)該是這樣寫(xiě)的(注意,原文件中的TAG有大小寫(xiě)的情況,但是在XPATH里只能用小寫(xiě)):
更簡(jiǎn)單的方法就是像jQuery那樣直接根據(jù)id定位:
nodes=tree.xpath(u"http://div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")
這兩種方法返回的結(jié)果中,nodes[0]就是那個(gè)“text”的h3節(jié)點(diǎn)后面緊跟的第一個(gè)ul節(jié)點(diǎn),這樣就可以列出后面所有的ul節(jié)點(diǎn)內(nèi)容了。
如果ul節(jié)點(diǎn)下面還有其他的節(jié)點(diǎn),我們要找到更深節(jié)點(diǎn)的內(nèi)容,如下的循環(huán)就是把這些節(jié)點(diǎn)的文本內(nèi)容列出:
nodes=nodes[0].xpath("li/a")
for n in nodes:
print n.text
對(duì)比三種方法應(yīng)該可以看出xpath和jQuery對(duì)于頁(yè)面的解析都是基于XML的語(yǔ)義進(jìn)行,而RE則純粹是基于plain text。RE對(duì)付簡(jiǎn)單的頁(yè)面是沒(méi)有問(wèn)題,如果頁(yè)面結(jié)構(gòu)復(fù)雜度較高的時(shí)候(比如一堆的DIV來(lái)回嵌套之類),設(shè)計(jì)一個(gè)恰當(dāng)?shù)腞E pattern可能會(huì)遠(yuǎn)比寫(xiě)一個(gè)xpath要復(fù)雜。特別是目前主流的基于CSS的頁(yè)面設(shè)計(jì)方式,其中大部分關(guān)鍵節(jié)點(diǎn)都會(huì)有id――對(duì)于使用jQuery的頁(yè)面來(lái)說(shuō)則更是如此,這時(shí)xpath相比RE就有了決定性的優(yōu)勢(shì)。
附錄:基本XPATH語(yǔ)法介紹,詳細(xì)請(qǐng)參考XPath的官方文檔
XPATH基本上是用一種類似目錄樹(shù)的方法來(lái)描述在XML文檔中的路徑。比如用“/”來(lái)作為上下層級(jí)間的分隔。第一個(gè)“/”表示文檔的根節(jié)點(diǎn)(注意,不是指文檔最外層的tag節(jié)點(diǎn),而是指文檔本身)。比如對(duì)于一個(gè)HTML文件來(lái)說(shuō),最外層的節(jié)點(diǎn)應(yīng)該是”/html”。
同樣的,“..”和“.”分別被用來(lái)表示父節(jié)點(diǎn)和本節(jié)點(diǎn)。
XPATH返回的不一定就是唯一的節(jié)點(diǎn),而是符合條件的所有節(jié)點(diǎn)。比如在HTML文檔里使用“/html/head/scrpt”就會(huì)把head里的所有script節(jié)點(diǎn)都取出來(lái)。
為了縮小定位范圍,往往還需要增加過(guò)濾條件。過(guò)濾的方法就是用“[”“]”把過(guò)濾條件加上。比如在HTML文檔里使用“/html/body/div[@id='main']”,即可取出body里id為main的div節(jié)點(diǎn)。
其中@id表示屬性id,類似的還可以使用如@name, @value, @href, @src, @class….
而 函數(shù)text()的意思則是取得節(jié)點(diǎn)包含的文本。比如:<div>hello<p>world</p>< /div>中,用”div[text()='hello']“即可取得這個(gè)div,而world則是p的text()。
函數(shù)position()的意思是取得節(jié)點(diǎn)的位置。比如“l(fā)i[position()=2]”表示取得第二個(gè)li節(jié)點(diǎn),它也可以被省略為“l(fā)i[2]”。
不過(guò)要注意的是數(shù)字定位和過(guò)濾 條件的順序。比如“ul/li[5][@name='hello']”表示取ul下第五項(xiàng)li,并且其name必須是hello,否則返回空。而如果用 “ul/li[@name='hello'][5]”的意思就不同,它表示尋找ul下第五個(gè)name為”hello“的li節(jié)點(diǎn)。
此外,“*”可以代替所有的節(jié)點(diǎn)名,比如用”/html/body/*/span”可以取出body下第二級(jí)的所有span,而不管它上一級(jí)是div還是p或是其它什么東東。
而 “descendant::”前綴可以指代任意多層的中間節(jié)點(diǎn),它也可以被省略成一個(gè)“/”。比如在整個(gè)HTML文檔中查找id為“l(fā)eftmenu”的 div,可以用“/descendant::div[@id='leftmenu']”,也可以簡(jiǎn)單地使用“ //div[@id='leftmenu']”。
至于“following-sibling::”前綴就如其名所說(shuō),表示同一層的下一個(gè)節(jié)點(diǎn)?!眆ollowing-sibling::*”就是任意下一個(gè)節(jié)點(diǎn),而“following-sibling::ul”就是下一個(gè)ul節(jié)點(diǎn)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python Selenium常見(jiàn)的報(bào)錯(cuò)問(wèn)題以及措施
這篇文章主要介紹了Python Selenium常見(jiàn)的報(bào)錯(cuò)問(wèn)題以及措施,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
解決python3中的requests解析中文頁(yè)面出現(xiàn)亂碼問(wèn)題
requests是一個(gè)很實(shí)用的Python HTTP客戶端庫(kù),編寫(xiě)爬蟲(chóng)和測(cè)試服務(wù)器響應(yīng)數(shù)據(jù)時(shí)經(jīng)常會(huì)用到。這篇文章給大家介紹了解決python3中的requests解析中文頁(yè)面出現(xiàn)亂碼問(wèn)題,感興趣的朋友一起看看吧2019-04-04
解決nohup重定向python輸出到文件不成功的問(wèn)題
今天小編就為大家分享一篇解決nohup重定向python輸出到文件不成功的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Django之使用內(nèi)置函數(shù)和celery發(fā)郵件的方法示例
這篇文章主要介紹了Django之使用內(nèi)置函數(shù)和celery發(fā)郵件的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
Python+OpenCV編寫(xiě)車輛計(jì)數(shù)器系統(tǒng)
本文,我們將使用歐幾里德距離跟蹤和輪廓的概念在 Python 中使用 OpenCV 構(gòu)建車輛計(jì)數(shù)器系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-05-05
python將dict中的unicode打印成中文實(shí)例
這篇文章主要介紹了python將dict中的unicode打印成中文實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-05-05
Python實(shí)現(xiàn)快速排序和插入排序算法及自定義排序的示例
這篇文章主要介紹了Python實(shí)現(xiàn)快速排序和插入排序算法及自定義排序的示例,自定義排序用到了Python的sort和sorted函數(shù),需要的朋友可以參考下2016-02-02
Python基于Dlib的人臉識(shí)別系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了Python基于Dlib的人臉識(shí)別系統(tǒng)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Python3二分查找?guī)旌瘮?shù)bisect(),bisect_left()和bisect_right()的區(qū)別
這篇文章主要介紹了Python3二分查找?guī)旌瘮?shù)bisect(),bisect_left()和bisect_right()的區(qū)別,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
Python實(shí)現(xiàn)從訂閱源下載圖片的方法
這篇文章主要介紹了Python實(shí)現(xiàn)從訂閱源下載圖片的方法,涉及Python采集的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03

