1. 程式人生 > 其它 >Python爬蟲——使用XPath和lxml庫解析HTML

Python爬蟲——使用XPath和lxml庫解析HTML

目錄

在re、bs4、xpath等解析庫中,re庫執行起來效率最高,但用起來太麻煩;XPath 使用較為方便,而且效率損失不大。因此應某人的需求,本人開始學習 XPath。

0 安裝 XPath Helper 外掛

在 edge 瀏覽器的“擴充套件”中,開啟“開發者模式”和“允許來自其他應用商店的擴充套件”,將資料夾《chrome_Xpath_v2.0.2》拖入到“擴充套件”頁面中即可完成安裝。

本人設定鍵盤快捷方式為“Ctrl+Shift+C”,這樣每次想用時就可以快速開啟這玩意了,再按下“F12”檢視程式碼,就可以開玩 XPath 了。這個外掛是真的好用,強烈推薦安裝。

1 XPath 語法

1.1 節點

表示式 描述 用法舉例 用法舉例說明
nodename 選取此節點下的所有節點 div 選取div下的所有標籤
// 全域性節點(可粗略理解為“絕對路徑”) //div 選取整個HTML頁面的所有div標籤
//nodename// 選取某個節點下的所有節點(包括子節點、子節點的子節點...) //header//div 選取header標籤下的所有div標籤,不管是不是header標籤的第一級節點
/ 選取某個節點下的子節點(可粗略理解為“相對路徑”)(只能一級) //div/a 選取div標籤下的a標籤(而非整個頁面的a標籤)
@ 選取帶有某個屬性(attr)的節點 //div[@class] 選取帶有class屬性的div標籤
@ 獲取屬性的內容 //div/span/@title 選取span標籤的屬性title的內容
@attr=str 選取帶有某個屬性(attr)為str的節點 //div[@class="menv"]
選取帶有class屬性為menv的div標籤
. 當前節點下 ./span 當前節點下的span標籤

1.2 謂語

表示式 描述 用法舉例 用法舉例說明
[n] 當選取有多個標籤時,可以指定選取第n個標籤(注意n從1開始) //div[@class="menv"][2] 選取第二個帶有class屬性為menv的div標籤
[last()] 選取最後一個標籤 //div[last()] 選取最後一個div標籤
[last()-n] 選取倒數第n個標籤 //div[last()-3] 選取倒數第三個div標籤
[position()<n] 選取前n個標籤 //div[position()❤️] 選取前三個div標籤
[element表示式條件] 元素值符合表示式條件的標籤 //div[price<35.00] 選取div標籤且price元素需小於35.00

2 lxml 庫使用例項

lxml 庫的作用:將 html 字串進行解析,供 XPath 語法進行資料提取。

現在我們通過一個例項來初步認知 lxml 的用法,如下為一個 HTML 字串:

text = \
"""
<ul class="ullist" padding="1" spacing="1">
    <li>
        <div id="top">
            <span class="position" width="350">職位名稱</span>
            <span>職位類別</span>
            <span>人數</span>
            <span>地點</span>
            <span>釋出時間</span>
        </div>
        <div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=33824&amp;keywords=python&amp;tid=87&amp;lid=2218">python開發工程師</a>
            </span>
            <span>技術類</span>
            <span>2</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="odd">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=29938&amp;keywords=python&amp;tid=87&amp;lid=2218">python後端</a>
            </span>
            <span>技術類</span>
            <span>2</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=31236&amp;keywords=python&amp;tid=87&amp;lid=2218">高階Python開發工程師</a>
            </span>
            <span>技術類</span>
            <span>2</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="odd">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=31235&amp;keywords=python&amp;tid=87&amp;lid=2218">python架構師</a>
            </span>
            <span>技術類</span>
            <span>1</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=34531&amp;keywords=python&amp;tid=87&amp;lid=2218">Python資料開發工程師</a>
            </span>
            <span>技術類</span>
            <span>1</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="odd">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=34532&amp;keywords=python&amp;tid=87&amp;lid=2218">高階影象演算法研發工程師</a>
            </span>
            <span>技術類</span>
            <span>1</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=31648&amp;keywords=python&amp;tid=87&amp;lid=2218">高階AI開發工程師</a>
            </span>
            <span>技術類</span>
            <span>4</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="odd">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=32218&amp;keywords=python&amp;tid=87&amp;lid=2218">後臺開發工程師</a>
            </span>
            <span>技術類</span>
            <span>1</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=32217&amp;keywords=python&amp;tid=87&amp;lid=2218">Python開發(自動化運維方向)</a>
            </span>
            <span>技術類</span>
            <span>1</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        <div id="odd">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=34511&amp;keywords=python&amp;tid=87&amp;lid=2218">Python資料探勘講師 </a>
            </span>
            <span>技術類</span>
            <span>1</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
    </li>
</ul>
"""

2.1 解析字串為 HTML

這一步的操作是將字串轉化為可識別的 HTML,並且還會進行“查缺補漏”,修復不規範標籤。

from lxml import etree

'''
0. 解析字串為 HTML
'''
html = etree.HTML(text)

2.2 獲取 div 標籤

XPath 表示式://div

'''
1. 獲取 div 標籤
'''
# 獲取 div 標籤,div1 是一個列表
div1 = html.xpath('//div')
# 列表遍歷,轉化為字串輸出
for div in div1:
    d = etree.tostring(div, encoding='utf-8').decode('utf-8')
    print(d)
    print('*' * 50)

注意 1:div1 是一個列表,因此需要使用 for 對其進行訪問遍歷。

注意 2:若轉化字串和解析 HTML 時不用 utf8,則預設為 byte 流資料。

輸出結果:

<div id="top">
            <span class="position" width="350">職位名稱</span>
            <span>職位類別</span>
            <span>人數</span>
            <span>地點</span>
            <span>釋出時間</span>
        </div>
        
**************************************************
<div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=33824&amp;keywords=python&amp;tid=87&amp;lid=2218">python開發工程師</a>
            </span>
            <span>技術類</span>
            <span>2</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        
**************************************************
<div id="odd">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=29938&amp;keywords=python&amp;tid=87&amp;lid=2218">python後端</a>
            </span>
            <span>技術類</span>
            <span>2</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        
**************************************************

(後面結果省略)

2.3 獲取某個指定的 div 標籤

假設我們想要獲取第 1 個 div 標籤,那麼 XPath 表示式://div[1]

'''
2. 獲取某個指定的 div 標籤
'''
# div2 是一個列表
div2 = html.xpath('//div[1]')[0]
DIV2 = etree.tostring(div2, encoding='utf-8').decode('utf-8')
print(DIV2)

注意:div2 依然是一個列表,而且是隻有一個元素的列表,存放了第一個 div 標籤。

輸出結果:

<div id="top">
            <span class="position" width="350">職位名稱</span>
            <span>職位類別</span>
            <span>人數</span>
            <span>地點</span>
            <span>釋出時間</span>
        </div>

2.4 獲取屬性為 id='even' 的 div 標籤

XPath 表示式://div[@id="even"]

'''
3. 獲取屬性為 id='even' 的 div 標籤
'''
# div3 是一個列表
div3 = html.xpath('//div[@id="even"]')
# 列表遍歷,轉化為字串輸出
for div in div3:
    d = etree.tostring(div, encoding='utf-8').decode('utf-8')
    print(d)
    print('*' * 50)

輸出結果:

<div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=33824&amp;keywords=python&amp;tid=87&amp;lid=2218">python開發工程師</a>
            </span>
            <span>技術類</span>
            <span>2</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        
**************************************************
<div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=31236&amp;keywords=python&amp;tid=87&amp;lid=2218">高階Python開發工程師</a>
            </span>
            <span>技術類</span>
            <span>2</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        
**************************************************
<div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=34531&amp;keywords=python&amp;tid=87&amp;lid=2218">Python資料開發工程師</a>
            </span>
            <span>技術類</span>
            <span>1</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        
**************************************************
<div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=31648&amp;keywords=python&amp;tid=87&amp;lid=2218">高階AI開發工程師</a>
            </span>
            <span>技術類</span>
            <span>4</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        
**************************************************
<div id="even">
            <span class="l square">
              <a target="_blank" href="position_detail.php?id=32217&amp;keywords=python&amp;tid=87&amp;lid=2218">Python開發(自動化運維方向)</a>
            </span>
            <span>技術類</span>
            <span>1</span>
            <span>上海</span>
            <span>2018-10-23</span>
        </div>
        
**************************************************

2.5 獲取標籤下的屬性值

2.5.1 初步想法

XPath 表示式://div/@id//a/@href

'''
4. 獲取標籤下的屬性值
'''
# div4 是一個列表
div4 = html.xpath('//div/@id')
print(div4)

# hrefs 是一個列表
hrefs = html.xpath('//a/@href')
print(hrefs)

輸出結果:

['top', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']
['position_detail.php?id=33824&keywords=python&tid=87&lid=2218', 'position_detail.php?id=29938&keywords=python&tid=87&lid=2218', 'position_detail.php?id=31236&keywords=python&tid=87&lid=2218', 'position_detail.php?id=31235&keywords=python&tid=87&lid=2218', 'position_detail.php?id=34531&keywords=python&tid=87&lid=2218', 'position_detail.php?id=34532&keywords=python&tid=87&lid=2218', 'position_detail.php?id=31648&keywords=python&tid=87&lid=2218', 'position_detail.php?id=32218&keywords=python&tid=87&lid=2218', 'position_detail.php?id=32217&keywords=python&tid=87&lid=2218', 'position_detail.php?id=34511&keywords=python&tid=87&lid=2218']

2.5.2 改程序序

很多時候,在獲取屬性時,我們不想要這樣一個長長的列表(因為處理起來有點麻煩),如果我們需要獲取一個 url 字串,那該怎麼辦呢?

觀察 HTML,發現 a 標籤都在 div 標籤裡。我們可以先獲取 div 標籤(這樣就會得到一個 div 列表),遍歷這個列表,再在裡面獲取 a 標籤,這樣就可以分離出每一個 url 了。

先選取 div 標籤://div(得到一個許多元素的列表)

然後在以上基礎選取 a 標籤:.//a/@href(得到多個元素只有一個的列表)

注意:.表示上一個路徑,意思是在 div
標籤下進行全域性選取。如果直接寫成//a/@href,意思是在整個頁面下進行全域性選取 a 標籤。

# div4 是一個列表,從元素 1 開始取
div4 = html.xpath("//div")[1:]
for div in div4:
    url = div.xpath(".//a/@href")
    print(url)

輸出結果:

['position_detail.php?id=33824&keywords=python&tid=87&lid=2218']
['position_detail.php?id=29938&keywords=python&tid=87&lid=2218']
['position_detail.php?id=31236&keywords=python&tid=87&lid=2218']
['position_detail.php?id=31235&keywords=python&tid=87&lid=2218']
['position_detail.php?id=34531&keywords=python&tid=87&lid=2218']
['position_detail.php?id=34532&keywords=python&tid=87&lid=2218']
['position_detail.php?id=31648&keywords=python&tid=87&lid=2218']
['position_detail.php?id=32218&keywords=python&tid=87&lid=2218']
['position_detail.php?id=32217&keywords=python&tid=87&lid=2218']
['position_detail.php?id=34511&keywords=python&tid=87&lid=2218']

這時我們得到的是許多列表,但是每個列表只有一個元素(字串),處理起來方便多了。所以可以繼續改進,url 列表獲取 0 號元素,直接得到字串:

# div4 是一個列表,從元素 1 開始取
div4 = html.xpath("//div")[1:]
for div in div4:
    url = div.xpath(".//a/@href")[0]
    print(url)

輸出結果:

position_detail.php?id=33824&keywords=python&tid=87&lid=2218
position_detail.php?id=29938&keywords=python&tid=87&lid=2218
position_detail.php?id=31236&keywords=python&tid=87&lid=2218
position_detail.php?id=31235&keywords=python&tid=87&lid=2218
position_detail.php?id=34531&keywords=python&tid=87&lid=2218
position_detail.php?id=34532&keywords=python&tid=87&lid=2218
position_detail.php?id=31648&keywords=python&tid=87&lid=2218
position_detail.php?id=32218&keywords=python&tid=87&lid=2218
position_detail.php?id=32217&keywords=python&tid=87&lid=2218
position_detail.php?id=34511&keywords=python&tid=87&lid=2218

2.6 獲取標籤下的文字資訊

假設我們想要獲取 a 標籤下的文字資訊,那麼:

先選取 div 標籤://div(得到一個許多元素的列表)

然後在以上基礎選取 a 標籤的文字資訊:.//a/text()(得到多個元素只有一個的列表)

'''
5. 獲取標籤下的文字
'''
# div5 是一個列表,從元素 1 開始取
div5 = html.xpath("//div")[1:]
for div in div5:
    work = div.xpath(".//a/text()")[0]
    print(work)

輸出結果:

python開發工程師
python後端
高階Python開發工程師
python架構師
Python資料開發工程師
高階影象演算法研發工程師
高階AI開發工程師
後臺開發工程師
Python開發(自動化運維方向)
Python資料探勘講師

寫在後面

搞了一晚上和一個白天,因為使用 PyCharm 呼叫 lxml 庫時輸出的結果完全不符合預期,今天重灌了一次 lxml 庫後居然恢復正常了,一直沒有搞懂是怎麼回事......

暫時就寫這麼多吧。

---EOF---