Python爬蟲之xpath語法及案例使用

Python爬蟲之xpath語法及案例使用

—- 鋼鐵俠的知識庫 2022.08.15

我們在寫Python爬蟲時,經常需要對網頁提取信息,如果用傳統正則表達去寫會增加很多工作量,此時需要一種對數據解析的方法,也就是本章要介紹的Xpath表達式。

Xpath是什麼

XPath,全稱 XML Path Language,即 XML 路徑語言,它是一門在 XML 文檔中查找信息的語言。最初是用來搜尋 XML 文檔的,但同樣適用於 HTML 文檔的搜索。所以在做爬蟲時完全可以使用 XPath 做相應的信息抽取。

XPath 的選擇功能十分強大,它提供了非常簡潔明了的路徑選擇表達式。另外,它還提供超過 100 個內置函數,用於字符串、數值、時間的匹配以及節點、序列的處理等,幾乎所有想要定位的節點都可以用 XPath 來選取。

下面介紹實戰中常用的幾個知識點,詳細也可以看W3C介紹://www.w3school.com.cn/xpath/index.asp

Xpath語法介紹

路徑常用規則

表達式 描述 實例
nodename 選取此節點的所有子節點 xpath(‘//div’) 選取了div節點的所有子節點
/ 從根節點選取 xpath(‘/div’) 從根節點上選取div節點
// 選取所有當前節點,不考慮位置 xpath(‘//div’) 選取所有的div節點
. 選取當前節點 xpath(‘./div’) 選取當前節點下的div節點
.. 選取當前節點的父節點 xpath(‘..’) 回到上一個節點
@ 選取屬性 xpath(’//@calss’) 選取所有的class屬性

謂語規則

謂語被嵌在方括號內,用來查找某個特定的節點或包含某個制定的值的節點

表達式 結果
xpath(‘/body/div[1]’) 選取body下的第一個div節點
xpath(‘/body/div[last()]’) 選取body下最後一個div節點
xpath(‘/body/div[last()-1]’) 選取body下倒數第二個div節點
xpath(‘/body/div[positon()❤️]’) 選取body下前兩個div節點
xpath(‘/body/div[@class]’) 選取body下帶有class屬性的div節點
xpath(‘/body/div[@class=”main”]’) 選取body下class屬性為main的div節點
xpath(‘/body/div[price>35.00]’) 選取body下price元素值大於35的div節點

通配符

通配符來選取未知的XML元素

表達式 結果
xpath(’/div/*’) 選取div下的所有子節點
xpath(‘/div[@*]’) 選取所有帶屬性的div節點

取多個路徑

使用「|」運算符可以選取多個路徑

表達式 結果
xpath(‘//div|//table’) 選取所有的div和table節點

功能函數

使用功能函數能夠更好的進行模糊搜索

函數 用法 解釋
starts-with xpath(‘//div[starts-with(@id,”ma”)]’) 選取id值以ma開頭的div節點
contains xpath(‘//div[contains(@id,”ma”)]’) 選取id值包含ma的div節點
and xpath(‘//div[contains(@id,”ma”) and contains(@id,”in”)]’) 選取id值包含ma和in的div節點
text() xpath(‘//div[contains(text(),”ma”)]’) 選取節點文本包含ma的div節點

語法熟悉

下面舉一段HTML文本進行語法熱身,代碼如下

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# time: 2022/8/8 0:05
# author: gangtie
# email: [email protected]
from lxml import etree

text = '''
<div>
            <ul id='ultest'>
                 <li class="item-0"><a href="link1.html">first item</a></li>
                 <li class="item-1"><a href="link2.html">second item</a></li>
                 <li class="item-inactive"><a href="link3.html">third item</a></li>
                 <li class="item-1"><a href="link4.html"><span>fourth item</span></a></li>
                 <li class="item-0"><a href="link5.html">fifth item</a>
             </ul>
         </div>
'''
# 調用HTML類進行初始化,這樣就成功構造了一個XPath解析對象。
# 利用etree.HTML解析字符串
page = etree.HTML(text)   
print(type(page))

可以看到打印結果已經變成XML元素:

<class 'lxml.etree._Element'>

字符串轉換HTML

字符串利用etree.HTML解析成html格式:

print(etree.tostring(page,encoding='utf-8').decode('utf-8'))

```
<html><body><div>
            <ul id="ultest">
                 <li class="item-0"><a href="link1.html">first item</a></li>
                 <li class="item-1"><a href="link2.html">second item</a></li>
                 <li class="item-inactive"><a href="link3.html">third item</a></li>
                 <li class="item-1"><a href="link4.html"><span>fourth item</span></a></li>
                 <li class="item-0"><a href="link5.html">fifth item</a>
             </li></ul>
         </div>
</body></html>

Process finished with exit code 0
```

經過處理可以看到缺失的</li>也自動補全了,還自動添加html、body節點。

查找絕對路徑

通過絕對路徑獲取a標籤的所有內容

a = page.xpath("/html/body/div/ul/li/a")
for i in a:
    print(i.text)

```
first item
second item
third item
None
fifth item
```

查找相對路徑(常用)

查找所有li標籤下的a標籤內容

html = etree.HTML(text)
a = html.xpath("//a/text()")
print(a)

```
['first item', 'second item', 'third item', 'fifth item']
```

當前標籤節點

. 表示選取當前標籤的節點。

我們先定位 ul 元素節點得到一個列表,打印當前節點列表得到第一個 ul,
接着打印 ul 節點的子節點 li,text()輸出。

page = etree.HTML(text)
ul = page.xpath("//ul")
print(ul)
print(ul[0].xpath("."))
print(ul[0].xpath("./li"))
print(ul[0].xpath("./li/a/text()"))

```
[<Element ul at 0x234d16186c0>]
[<Element ul at 0x234d16186c0>]
[<Element li at 0x234d1618ac0>, <Element li at 0x234d1618b00>, <Element li at 0x234d1618b40>, <Element li at 0x234d1618b80>, <Element li at 0x234d1618bc0>]
['first item', 'second item', 'third item', 'fifth item']
```

父節點

.. 表示選取當前標籤的父節點。

可以看到得到ul的上一級div

page = etree.HTML(text)
ul = page.xpath("//ul")
print(ul[0].xpath("."))
print(ul[0].xpath(".."))

```
[<Element ul at 0x1d6d5cd8540>]
[<Element div at 0x1d6d5cd8940>]
```

屬性匹配

匹配時可以用@符號進行屬性過濾
查找a標籤下屬性href值為link2.html的內容

html = etree.HTML(text)
a = html.xpath("//a[@href='link2.html']/text()")
print(a)

```
['second item']
```

函數

last():查找最後一個li標籤里的a標籤的href屬性

html = etree.HTML(text)
a = html.xpath("//li[last()]/a/text()")
print(a)

```
['fifth item']
```

contains:查找a標籤中屬性href包含link的節點,並文本輸出

html = etree.HTML(text)
a = html.xpath("//a[contains(@href, 'link')]/text()")
print(a)

```
['first item', 'second item', 'third item', 'fifth item']
```

實戰案例

上面說完基本用法,接下來做幾個實戰案例練練手。

案例一:豆瓣讀書

#  -*-coding:utf8 -*-
# 1.請求並提取需要的字段
# 2.保存需要的數據
import requests
from lxml import etree

class DoubanBook():

    def __init__(self):
        self.base_url = '//book.douban.com/chart?subcat=all&icn=index-topchart-popular'
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/104.0.0.0 Safari/537.36'
        }

    # 請求並提取需要的字段
    def crawl(self):
        res = requests.get(self.base_url, headers=self.headers)
        lis = etree.HTML(res.text).xpath('//*[@id="content"]/div/div[1]/ul/li')
        # print(type(lis))
        books = []
        for li in lis:
            # print(etree.tostring(li,encoding='utf-8').decode('utf-8'))
            # print("==================================================")
            title = "".join(li.xpath(".//a[@class='fleft']/text()"))
            score = "".join(li.xpath(".//p[@class='clearfix w250']/span[2]/text()"))
            # list輸出帶有['\n                    劉瑜 / 2022-4 / 廣西師範大學出版社 / 82.00元 / 精裝\n                ']
            publishing = "".join(li.xpath(".//p[@class='subject-abstract color-gray']/text()")).strip()
            book = {
                'title': title,
                'score': score,
                'publishing': publishing,
            }
            books.append(book)
        self.save_data(books)

    def save_data(self, datas):
            with open('books.txt', 'w', encoding='utf-8') as f:
                f.write(str(datas))

    def run(self):
        self.crawl()

if __name__ == '__main__':
    DoubanBook().run()

案例二:彼岸圖片下載

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 鋼鐵知識庫
# email: [email protected]

import os
import requests
from lxml import etree

# 彼岸圖片下載
class BiAn():

    def __init__(self):
        self.url = '//pic.netbian.com'
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/104.0.0.0 Safari/537.36',
            'cookie': '__yjs_duid=1_cb922eedbda97280755010e53b2caca41659183144320; Hm_lvt_c59f2e992a863c2744e1ba985abaea6c=1649863747,1660203266; zkhanecookieclassrecord=%2C23%2C54%2C55%2C66%2C60%2C; Hm_lpvt_c59f2e992a863c2744e1ba985abaea6c=1660207771; yjs_js_security_passport=1225f36e8501b4d95592e5e7d5202f4081149e51_1630209607_js'
        }
        # 如果目錄不存在會報錯
        if not os.path.exists('BianPicture'):
            os.mkdir('BianPicture')

    # 請求拿到ul列表
    def crawl(self):
        res = requests.get(self.url, headers=self.headers)
        res.encoding = 'gbk'
        uls = etree.HTML(res.text).xpath('//div[@class="slist"]/ul[@class="clearfix"]/li')
        # print(etree.tostring(uls,encoding='gbk').decode('gbk'))

        # 循環拿到圖片名、圖片地址,拼接請求再次下載到圖片
        for ul in uls:
            img_name = ul.xpath('.//a/b/text()')[0]
            img_src = ul.xpath('.//a/span/img/@src')[0]
            # print(img_name + img_src)
            img_url = self.url + img_src
            # 拼接後下載圖片,轉義Bytes
            img_res = requests.get(img_url, headers=self.headers).content
            img_path = "BianPicture/" + img_name + ".jpg"
            data = {
                'img_res': img_res,
                'img_path': img_path
            }
            self.save_data(data)

    # 數據保存邏輯
    def save_data(self, data):
        with open(data['img_path'], 'wb') as f:
            f.write(data['img_res'])
        # print(data)

    def run(self):
        self.crawl()

if __name__ == '__main__':
    BiAn().run()

image-20220812165403134

案例三:全國城市名稱爬取

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 鋼鐵知識庫
# email: [email protected]
import os

import requests
from lxml import etree

class CityName():
    def __init__(self):
        self.url = '//www.aqistudy.cn/historydata/'
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
        }
        # 判斷目錄是否存在
        if not os.path.exists('city_project'):
            os.mkdir('city_project')

    def crawl(self):
        res = requests.get(url=self.url, headers=self.headers).text
        uls = etree.HTML(res).xpath('//div[@class="all"]/div[2]/ul/div[2]/li')

        all_city_name = list()
        for ul in uls:
            city_name = ul.xpath('.//a/text()')[0]
            # print(type(city_name))
            all_city_name.append(city_name)
            self.save_data(all_city_name)

    def save_data(self, data):
        with open('./city_project/city.txt', 'w') as f:
            f.write(str(data))

    def run(self):
        self.crawl()

if __name__ == '__main__':
    CityName().run()

xpath使用工具

chrome生成XPath表達式

經常使用chome的朋友都應該知道這功能,在 審查 狀態下(快捷鍵ctrl+shift+i,F12),定位到元素(快捷鍵ctrl+shift+c) ,在Elements選項卡中,右鍵元素 Copy->Copy xpath,就能得到該元素的xpath了

image-20220808172320534

Xpath Helper插件

為chome裝上XPath Helper就可以很輕鬆的檢驗自己的xpath是否正確了。安裝插件需要特別上網,安裝好插件後,在chrome右上角點插件的圖標,調出插件的黑色界面,編輯好xpath表達式,表達式選中的元素被標記為黃色

image-20220808172538959

—- 鋼鐵俠的知識庫 2022.08.15

結語:

以上就是利用XPath的所有用法,從常用語法,到案例練習都走了一遍。下一章 鋼鐵知識庫 會繼續介紹另一種好用的解析框架,Beautiful Soup,覺得有用點贊加關注,就當你送了紅包666