欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python爬蟲(chóng)中如何使用xpath解析HTML

 更新時(shí)間:2024年11月30日 11:43:23   作者:bananaplan  
文章詳細(xì)介紹了如何使用Python的lxml庫(kù)中的xpath進(jìn)行網(wǎng)頁(yè)數(shù)據(jù)爬取,解釋了xpath與BeautifulSoup相比的優(yōu)勢(shì),介紹如何使用lxml庫(kù)加載HTML內(nèi)容,包括parse()、fromstring()和HTML()方法的使用,展示了如何使用xpath定位HTML節(jié)點(diǎn),包括使用絕對(duì)定位和相對(duì)定位

最近工作上寫(xiě)了個(gè)爬蟲(chóng),要爬取國(guó)家標(biāo)準(zhǔn)網(wǎng)上的一些信息,這自然離不了 Python,而在解析 HTML 方面,xpath 則可當(dāng)仁不讓的成為兵器譜第一。

你可能之前聽(tīng)說(shuō)或用過(guò)其它的解析方式,像 Beautiful Soup,用的人好像也不少,但 xpath 與之相比,語(yǔ)法更簡(jiǎn)單,解析速度更快,就像正則表達(dá)式一樣,剛上手要學(xué)習(xí)一番,然而用久了,那些規(guī)則自然而然的就記住了,熟練之后也很難忘記。

安裝 lxml

xpath 只是解析規(guī)則,其背后是要有相應(yīng)的庫(kù)來(lái)實(shí)現(xiàn)功能的,就像正則表達(dá)式只是規(guī)則,而 Python 內(nèi)置的 re 庫(kù),則是提供了解析功能。在 Python 中,lxml 就是 xpath 解析的實(shí)現(xiàn)庫(kù)。

安裝 lxml 非常簡(jiǎn)單,pip install lxml 就搞定了。

下面我們來(lái)看一下,在我這次真實(shí)的項(xiàng)目中,該如何發(fā)揮出它的威力。

加載 HTML 內(nèi)容

加載 HTML 內(nèi)容,應(yīng)該用 etree.parse()、etree.fromstring() 還是 etree.HTML() ?

首先,把 lxml 庫(kù)導(dǎo)進(jìn)來(lái):from lxml import etree。

HTML 內(nèi)容的加載,是通過(guò) etree 的方法載入的,具體有 3 個(gè)方法:parse()、fromstring() 和 HTML()。

parse() 是從文件加載。fromstring() 是從字符串加載。HTML() 也是從字符串加載,但是以 HTML 兼容的方式加載進(jìn)來(lái)的。

那我們應(yīng)該選哪個(gè)方法呢?別猶豫,選 etree.HTML(),即使你的 HTML 內(nèi)容來(lái)自文件。這是為何?

首先要說(shuō)的一點(diǎn)是,HTML 也是 XML 的一種,而 XML 的標(biāo)準(zhǔn)規(guī)定,其必須擁有一個(gè)根標(biāo)簽,否則,這段 XML 就是非法的。而我們加載進(jìn)來(lái)的 HTML 內(nèi)容,可能本身就不是完整的,只是個(gè)片段,且沒(méi)有根標(biāo)簽;或是加載進(jìn)來(lái)的 HTML 從頭到腳看起來(lái)都是完整的,但是中間的節(jié)點(diǎn),有的缺少結(jié)束標(biāo)簽,這些情況,其實(shí)都是非法的 XML。那么,在用 parse() 或 formstring() 加載這種缺胳膊少腿的 HTML 的時(shí)候,就會(huì)報(bào)錯(cuò);而用 etree.HTML() 則不會(huì)。

這是因?yàn)?etree.HTML() 加載方式,有很好的 HTML 兼容性,它會(huì)補(bǔ)全缺胳膊少腿的 HTML,把它變成一個(gè)完整的、合法的 HTML。

下面是一個(gè)從文件加載 HTML 的例子:

from lxml import etree

with open('test.html', 'r') as f:
    html = etree.HTML(f.read())
    print(html, type(html))

打印出來(lái)的結(jié)果是:<Element html at 0x7f7efa762040> <class 'lxml.etree._Element'>,加載進(jìn)來(lái)的 HTML 字符串,已經(jīng)變成了 Element 對(duì)象。

后面我們通過(guò) xpath 找 HTML 節(jié)點(diǎn),全都是在這個(gè) Element 對(duì)象上操作的。

找到你需要的 HTML 節(jié)點(diǎn)

下面是我想要找的 HTML 節(jié)點(diǎn)

在這個(gè) table 表格中,第一個(gè) tbody 是表頭,第二個(gè) tbody 是表內(nèi)容,我們要如何定位到第二個(gè) tbody ?

我們通常是調(diào)用上面獲得的 Element 對(duì)象的 xpath() 方法,通過(guò)傳入的 xpath 路徑查找的。而路徑有兩種寫(xiě)法:一種是 / 開(kāi)頭,從 html 根標(biāo)簽,沿著子節(jié)點(diǎn)一個(gè)個(gè)找下來(lái);另一種是 // 開(kāi)頭,即不論我們要找的節(jié)點(diǎn)在什么位置,找到就算,這種方式是最常用的。

比如,我們現(xiàn)在要找的 tbody 節(jié)點(diǎn),它在 table 節(jié)點(diǎn)下,我們就可以這樣寫(xiě):html.xpath('//table/tbody')。這里的 html 是上面獲得的 Element 對(duì)象,然后去找 HTML 內(nèi)容中的、不管在任何位置的所有的 table,找到后再繼續(xù)找它們下面的直接子節(jié)點(diǎn) tbody,于是就匹配出來(lái)了。

可是這里有 2 個(gè) tbody,我需要的是第二個(gè),我們可以在 [] 中寫(xiě)條件表達(dá)式:html.xpath('//table/tbody[2]'),注意這里的序號(hào)是從 1 開(kāi)始的。

強(qiáng)大的屬性選擇器

你可能有個(gè)疑問(wèn),如果 HTML 內(nèi)容中不只有一個(gè) table 表格,那我們通過(guò) html.xpath('//table/tbody[2]') 豈不是找到了 2 個(gè) table 里的第二個(gè) tbody,而我需要的只是其中之一。沒(méi)錯(cuò),是存在這樣一個(gè)問(wèn)題。此時(shí),我們就可以用屬性選擇器,來(lái)更精確的定位元素。

觀察一下上面的 HTML 結(jié)構(gòu),table 表格的最外層有一個(gè) div,它還有個(gè) class 屬性:table-responsive,假設(shè)這個(gè) div 的 class 屬性是整個(gè) HTML 里獨(dú)一無(wú)二的,那么我們就可以很放心的去查找 div.table-responsive 下的 table,進(jìn)而精確定位我們想要的元素。

那么,要怎樣寫(xiě) class = "table-responsive" 這個(gè)條件呢?看看上面寫(xiě)條件表達(dá)式的 [],那里面除了可以寫(xiě)數(shù)字來(lái)指定位置以外,也可以寫(xiě)其它各式各樣的條件,比如:

html.xpath('//div[@class="table-responsive"]/table/tbody[2]'),這里我們就把 class = "table-responsive" 這個(gè)條件寫(xiě)進(jìn)去了,從而定位到想要的元素。注意,在 xpath 中,所有的 HTML 屬性匹配都是以 @ 打頭的,比如有這樣一個(gè) <a href="#">Click Me</a> 元素,我們想要通過(guò) id 定位它,可以這樣寫(xiě)://a[@id="show_me"],是不是很簡(jiǎn)單。

假設(shè)很遺憾,我們這里的 table-responsive 不是唯一的,可能還有其它地方的 div 的 class = "table-responsive",這該怎么辦?沒(méi)關(guān)系,我們可以找其它具有唯一 class 值的元素,比如:最外層 div 下的 table.result_list 這個(gè)元素,這個(gè)是唯一的。好了,下面開(kāi)始寫(xiě)定位代碼:html.xpath('//table[@class="result_list"]/tbody[2]'),但是運(yùn)行后,發(fā)現(xiàn)找不到元素,這是為什么?

其實(shí)仔細(xì)觀察一下就能發(fā)現(xiàn),這個(gè)元素的 class 里不只有 result_list,它還包括其它一長(zhǎng)串的內(nèi)容:class = "table result_list table-striped table-hover",所以匹配失敗了。那要如何指定 class 包含某個(gè)屬性呢?其實(shí)可以在條件表達(dá)式中,用 contains() 函數(shù),無(wú)需精確匹配,而是模糊匹配,只要包含指定的字符串就可以了。比如:html.xpath('//table[contains(@class, "result_list")]/tbody[2]') 這樣就可以實(shí)現(xiàn)了。

需要提一點(diǎn)的是,xpath 定位到的元素,不管是不是全局唯一的,它的返回值都是一個(gè)列表,需要通過(guò)下標(biāo)獲取其中的元素。

相對(duì)定位

我最終的目標(biāo),是要遍歷表格中所有的內(nèi)容行,獲取其中的標(biāo)準(zhǔn)號(hào)和標(biāo)準(zhǔn)名稱,于是我初步完成了如下代碼:

from lxml import etree

with open('test.html', 'r') as f:
    html = etree.HTML(f.read())
    rows = html.xpath('//table[contains(@class, "result_list")]/tbody[2]/tr')

    for row in rows:
        td_list = row.xpath('...')

現(xiàn)在我能夠成功地定位到每一行,下面需要再基于每一行,找到我需要的列:

此時(shí),我在 for 循環(huán)的內(nèi)部,已經(jīng)拿到了每一行 row,再通過(guò) row.xpath('//td') 繼續(xù)往下定位 td 就好了。

可是,當(dāng)你運(yùn)行這段代碼的時(shí)候,你會(huì)發(fā)現(xiàn)不對(duì)勁,一行里面總共只有 8 個(gè) td,為什么出來(lái)了 80 個(gè)【一行 8 個(gè),總共 10 行】?這是把 HTML 中所有的 td 都找出來(lái)了吧,可是我明明是用上面獲取的 row 對(duì)象來(lái)查找的呀,不是應(yīng)該只基于當(dāng)前行往下找嗎?

這就牽扯到了 絕對(duì)定位相對(duì)定位。

其實(shí),我們上面講到的 ///,都是絕對(duì)定位,也就是從 HTML 內(nèi)容的根節(jié)點(diǎn)往下查找。一個(gè) HTML 內(nèi)容的根節(jié)點(diǎn)是什么呢,它是 html,再往下是 body,再再往下才是自定義的標(biāo)簽。所以,上面代碼的執(zhí)行結(jié)果是那種情況,也就不足為奇了,因?yàn)樗皇窃诋?dāng)前所在的 row 節(jié)點(diǎn)查找的,而是從根節(jié)點(diǎn) /html/body/xxx/xxx/td 往下查找的呀。

所以,在這里不能用 絕對(duì)定位 了,要用 相對(duì)定位,那要如何用?很簡(jiǎn)單,用 ... 即可,這個(gè)我們可太熟悉了,. 就代表了當(dāng)前節(jié)點(diǎn) row,而 .. 則代表了當(dāng)前節(jié)點(diǎn)的上一層父節(jié)點(diǎn) tbody。

好了,我們修正上面的代碼:

from lxml import etree

with open('test.html', 'r') as f:
    html = etree.HTML(f.read())
    rows = html.xpath('//table[contains(@class, "result_list")]/tbody[2]/tr')

    for row in rows:
        td_list = row.xpath('./td')

        i = 1
        for td in td_list:
            if i == 2:
                pass
            elif i == 4:
                pass
            i += 1

這樣就可以正常地找到每一行里面的 8 個(gè) td,然后再單獨(dú)處理第 2 個(gè)和第 4 個(gè)單元格,獲取其中的信息就好了。

通過(guò)已知節(jié)點(diǎn)獲取屬性和文本

到目前為止,我們能拿到第 2 個(gè)和第 4 個(gè) td 節(jié)點(diǎn)了,只要再獲取里面的 a 標(biāo)簽的屬性和文本就可以了。

我們先獲取 onclick 屬性,通過(guò) td.xpath('./a'),可以找到此 td 節(jié)點(diǎn)下面的 a 標(biāo)簽,然后調(diào)用 a 節(jié)點(diǎn)的 get() 方法,即可獲得對(duì)應(yīng)的屬性值,代碼如下:

a1 = td.xpath('./a')[0]
onclick = a1.get('onclick')

注意哦,xpath() 方法的返回值,始終是一個(gè)列表,所以我們用下標(biāo) [0] 先把它從列表中取出來(lái),然后再獲取其屬性。 至于屬性內(nèi)的值,我實(shí)際想取的是里面的一串 ID 字符串,這個(gè)再用正則表達(dá)式取一下就可以了。

要獲取節(jié)點(diǎn)內(nèi)的文本,也很簡(jiǎn)單,獲得到的節(jié)點(diǎn)有一個(gè) text 屬性,可以直接得到節(jié)點(diǎn)的文本內(nèi)容:a1.text。

好用的兄弟節(jié)點(diǎn)選擇器

上面的代碼邏輯有點(diǎn)挫,我們先是獲取到一行里的所有 td,然后循環(huán)遍歷它,在遍歷的過(guò)程當(dāng)中,只取其中的 2 個(gè) td,著實(shí)有些浪費(fèi)。假設(shè)一行里有 1000 個(gè) td,那這里豈不是要循環(huán) 1000 次,就只為了取 2 個(gè)?

雖然從實(shí)際運(yùn)行速度上來(lái)講,影響微乎其微,但對(duì)于有代碼潔癖和強(qiáng)迫癥的人來(lái)說(shuō),是不可接受的,所以,我們要改造它。

重新觀察一下 HTML 結(jié)構(gòu),我發(fā)現(xiàn)第 4 個(gè)單元格有個(gè)明顯的特征,它的 class = "mytxt"

我們可以很容易地找到它:title_td = tr.xpath('./td[@class="mytxt"]')[0],然后再基于剛找到的 title_td,查找從它往上數(shù)第 2 個(gè)兄弟節(jié)點(diǎn),這樣就省略了一個(gè)循環(huán),只要查找兩次就完成了。

那么,怎么查找上面的兄弟節(jié)點(diǎn)呢?用 preceing-sibling,比如:title_td.xpath('./preceding-sibling::td[2]'),這就代表要查找 title_td 上面的、從它這里往上數(shù)、排在第 2 位的 td 節(jié)點(diǎn)。

除了 preceding-sibling 之外,還有 following-sibling,顧名思意,是往下查找兄弟節(jié)點(diǎn)。

以上我只介紹了這 2 個(gè),其實(shí)還有很多類似的選擇器,具體可以參考下面的速查手冊(cè)。

最后,我改造的代碼如下:

from lxml import etree

with open('test.html', 'r') as f:
    html = etree.HTML(f.read())
    rows = html.xpath('//table[contains(@class, "result_list")]/tbody[2]/tr')

    for row in rows:
        title_td = row.xpath('./td[@class="mytxt"]')[0]
        title_link = title_td.xpath('./a')[0]
        title_onclick = title_link.get('onclick')

        print(title_onclick, title_link.text)

        id_td = title_td.xpath('./preceding-sibling::td[2]')[0]
        id_link_text = id_td.xpath('./a/text()')[0]

        print(id_link_text)

速查手冊(cè)

xpath 的規(guī)則并不復(fù)雜,常用的也就那些,用熟了自然就記住了。但像正則表達(dá)式一樣,它還有許多不常用卻很好用的特性,你還是需要偶爾查一下具體的作用和用法。

這里有一個(gè)非常好的速查手冊(cè),雖然里面的內(nèi)容看起來(lái)不夠豐富、很簡(jiǎn)單,但是可以一目了然,并且它用 css 的語(yǔ)法來(lái)作類比,就能夠更好地理解每一個(gè) xpath 規(guī)則的實(shí)際用途。

速查手冊(cè):https://devhints.io/xpath

總結(jié)

文章詳細(xì)介紹了如何使用Python的lxml庫(kù)中的xpath進(jìn)行網(wǎng)頁(yè)數(shù)據(jù)爬取,解釋了xpath與BeautifulSoup相比的優(yōu)勢(shì),介紹如何使用lxml庫(kù)加載HTML內(nèi)容,包括parse()、fromstring()和HTML()方法的使用,展示了如何使用xpath定位HTML節(jié)點(diǎn),包括使用絕對(duì)定位和相對(duì)定位。

到此這篇關(guān)于Python爬蟲(chóng)中如何使用xpath解析HTML的文章就介紹到這了,更多相關(guān)Python爬蟲(chóng)使用xpath解析HTML內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于Pycharm加載多個(gè)項(xiàng)目過(guò)程圖解

    基于Pycharm加載多個(gè)項(xiàng)目過(guò)程圖解

    這篇文章主要介紹了基于Pycharm加載多個(gè)項(xiàng)目過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • Python序列化基礎(chǔ)知識(shí)(json/pickle)

    Python序列化基礎(chǔ)知識(shí)(json/pickle)

    這篇文章主要為大家詳細(xì)介紹了Python序列化json和pickle基礎(chǔ)知識(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Python WSGI的深入理解

    Python WSGI的深入理解

    這篇文章主要給大家介紹了關(guān)于Python WSGI的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • Python基礎(chǔ)教程之循環(huán)語(yǔ)句(for、while和嵌套循環(huán))

    Python基礎(chǔ)教程之循環(huán)語(yǔ)句(for、while和嵌套循環(huán))

    這篇文章主要給大家介紹了關(guān)于Python基礎(chǔ)教程之循環(huán)語(yǔ)句(for、while和嵌套循環(huán))的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • pandas 顛倒列順序的兩種解決方案

    pandas 顛倒列順序的兩種解決方案

    這篇文章主要介紹了pandas 顛倒列順序的兩種解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-03-03
  • 在tensorflow下利用plt畫(huà)論文中l(wèi)oss,acc等曲線圖實(shí)例

    在tensorflow下利用plt畫(huà)論文中l(wèi)oss,acc等曲線圖實(shí)例

    這篇文章主要介紹了在tensorflow下利用plt畫(huà)論文中l(wèi)oss,acc等曲線圖實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-06-06
  • Python生成器(Generator)詳解

    Python生成器(Generator)詳解

    這篇文章主要介紹了Python生成器(Generator)詳解,本文講解了什么是生成器、簡(jiǎn)單生成器、帶yield 語(yǔ)句的生成器、加強(qiáng)的生成器等內(nèi)容,需要的朋友可以參考下
    2015-04-04
  • Python內(nèi)建類型str源碼學(xué)習(xí)

    Python內(nèi)建類型str源碼學(xué)習(xí)

    這篇文章主要為大家介紹了Python內(nèi)建類型str的源碼學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • 一文詳解python如何將編寫(xiě)的模塊打包上傳至pypi

    一文詳解python如何將編寫(xiě)的模塊打包上傳至pypi

    我們此前花了很大功夫?qū)懥艘粋€(gè)極其簡(jiǎn)單的web框架myWeb,想要給別人用的時(shí)候,需要讓別人拷貝源代碼才行,這太low了,所以本篇文章會(huì)介紹如何將自己寫(xiě)的模塊打包上傳至pypi,以便讓需要的人通過(guò)pip進(jìn)行安裝,感興趣的同學(xué)可以參考閱讀
    2023-05-05
  • Python selenium實(shí)現(xiàn)斷言3種方法解析

    Python selenium實(shí)現(xiàn)斷言3種方法解析

    這篇文章主要介紹了Python selenium實(shí)現(xiàn)斷言3種方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09

最新評(píng)論