天天看點

網絡爬蟲 | XPath解析

在學習xpath提取資料之前,得先了解下解析HTML代碼的一些方法,如果讀者想更加深入學習HTML代碼等相關内容,需要去檢視下前端HTML相關内容,本文僅介紹網絡爬蟲需要用到的部分内容。

本文介紹使用lxml子產品解析HTML與XML,因其支援XPath解析方式,且在解析效率方面非常優秀。

Tips: 你可以通過

pip install lxml

指令安裝

lxml子產品

解析HTML

parse()

etree.parse(source, parser=None, *, base_url=None)
           

複制

傳回加載源元素的ElementTree對象。如果沒有解析器作為第二個參數提供,則使用預設解析器。

source: 可以是下列任何一種:

  • 檔案名/路徑
  • 檔案對象
  • 一個類似檔案的對象
  • 使用HTTP或FTP協定的URL

注意,從檔案路徑或URL解析通常更快,而不是從打開的檔案對象或類檔案對象。支援從gzip壓縮源透明解壓(除非在libxml2中顯式禁用)。

base_url:

關鍵字允許為文檔設定URL從類檔案對象進行解析時。這是在尋找時需要的具有相對路徑的外部實體(DTD, XInclude,…)。

fromstring()

如果要解析字元串,請使用

'fromstring()'

函數。fromstring函數可以把一串xml解析為一個xml元素(傳回值類型和etree.Element一樣,是lxml.etree._Element類)。

>>> some_xml_data = "<root>data</root>"
>>> root = etree.fromstring(some_xml_data)
>>> etree.tostring(root)
b'<root>data</root>'
           

複制

1、解析本地的HTML檔案

#  'studio.html'
from lxml import etree
parser = etree.HTMLPaser()                       # 建立HTMLPaser對象
html = etree.parse('studio.html', parser=parser) # 解析本地的HTML檔案
html_txt = etree.tostring(html, encoding='utf-8')# 轉換字元串類型,并進行編碼
html_txt.decode('utf-8')
           

複制

使用tostring()可以提取出xml中所含的全部文本。

HTML()

HTML函數會自動加上html和body元素(如果原字元串沒有的話),同樣是傳回Element類。

>>> root = etree.HTML("<p>data</p>")
>>> etree.tostring(root)
b'<html><body><p>data</p></body></html>'
           

複制

注意:HTML函數的傳回值依然會被當成标準XML處理。

2、解析字元串類型的HTML代碼

>>> root = etree.HTML('<head/><p>Hello<br/>World</p>')
# 沒有XML聲明, 預設為ASCII編碼。
>>> etree.tostring(root)
b'<html><head/><body><p>Hello<br/>World</p></body></html>'
>>> etree.tostring(root, method='html') 
b'<html><head></head><body><p>Hello<br>World</p></body></html>'
# 換行
>>> html_txt = etree.tostring(root, method='html', encoding='uft-8') 
>>> html_txt.decode('utf-8')
'<html><head></head><body><p>Hello<br>World</p></body></html>'
           

複制

lxml.etree.HTML(),lxml.etree.fromstring()和lxml.etree.tostring()

三者之間的差別和聯系
文檔格式化方法 類型type 根節點 編碼方式 XPath
etree.HTML() <class 'lxml.etree._Element'> html (X.encode('utf-8')) 支援
etree.fromstring() <class 'lxml.etree._Element'> 原文檔根節點 (X.encode('utf-8')) 支援
etree.tostring() <class 'bytes'> 不支援
  • 從類型上看,

    etree.HTML()和etree.fromstring()

    都是屬于同一種

    "class類"

    ,這個類型才會支援使用xpath。也就說

    etree.tostring()是"位元組bytes類"

    ,不能使用xpath!
  • 從根節點看,

    etree.HTML()

    的文檔格式已經變成

    html類型

    ,是以根節點自然就是

    html标簽

    ]。但是,

    etree.fromstring()

    的根節點還是原文檔中的根節點,說明這種格式化方式并不改變原文檔的整體結構,這樣有利于使用xpath的絕對路徑方式查找資訊!而

    etree.tostring()

    是沒有所謂的根節點的,因為這個方法得到的文檔類型是

    "bytes類"

  • 從編碼方式上看,

    etree.HTML()和etree.fromstring()

    的括号内參數都要以

    "utf-8"的方式進行編碼

    !表格中的

    X是表示用read()方法之後的原文檔内容

3、解析伺服器傳回的HTML代碼

發送網絡請求後傳回的響應結果轉為字元串類型,如果傳回的結果是HTML代碼,則需要解析HTML代碼。

from lxml import etree
import requests
from requests.auth import HTTPBasicAuth
url = 'http://'
auth = HTTPBasicAuth('username', 'password')
response = requests.get(url=url, auth=auth)
if response.status_code == 200:
   html = etree.HTML(response.text)
    html_txt = etree.tostring(html, encoding='utf-8')
    print(html_txt.decode('utf-8'))
           

複制

XPath解析方式

官方網站(

https://www.w3.org/TR/xpath/all/

XPath

是一門路徑提取語言,常用于從

html/xml

檔案中提取資訊。它的基規則如下.

選取節點

表達式 描述
nodename 選取此節點的所有子節點
/ 從根節點選取
// 從比對選擇的目前節點選擇文檔中的節點,而不考慮他們的位置
. 選取目前節點
.. 選取目前節點的父節點
@ 選取屬性
* 選取所有節點

下面為一些路徑表達式及表達式結果:

路徑表達式 結果
petstore 選取 petstore 元素的所有子節點
/petstore 選取根元素 petstore。注釋:假如路徑起始于正斜杠( / ),則此路徑始終代表到某元素的絕對路徑!
petstore/corgi 選取屬于 petstore 的子元素的所有 corgi 元素
//corgi 選取所有 corgi 子元素,而不管它們在文檔中的位置。
petstore//corgi 選擇屬于 petstore 元素的後代的所有 corgi 元素,而不管它們位于 petstore 之下的什麼位置。
//@dog 選取名為 dog 的所有屬性。

謂語(Predicates)

謂語用來查找某個特定的節點或者包含某個指定的值的節點。謂語被嵌在方括号中。下面為一些帶有謂語的路徑表達式,及表達式結果。

路徑表達式 結果
/petstore/corgi[1] 選取屬于 petstore 子元素的第一個 corgi 元素。
/petstore/corgi[last()] 選取屬于 petstore 子元素的最後一個 corgi 元素。
/petstore/corgi[last()-1] 選取屬于 petstore 子元素的倒數第二個 corgi 元素。
/petstore/corgi[position()<3] 選取最前面的兩個屬于 petstore 元素的子元素的 corgi 元素。
//title[@dog] 選取所有擁有名為 dog 的屬性的 title 元素。
//title[@dog='female'] 選取所有 title 元素,且這些元素擁有值為 female 的 dog 屬性。
/petstore/corgi[price>2500.00] 選取 petstore 元素的所有 corgi 元素,且其中的 price 元素的值須大于 2500.00。
/petstore/corgi[price>2500.00]/title 選取 petstore 元素中的 corgi 元素的所有 title 元素,且其中的 price 元素的值須大于 2500.00。
//div[contains(@class,"f1")] 選擇div屬性包含"f1"的元素

選取未知節點

XPath 通配符可用來選取未知的 XML 元素。

通配符 描述
* 比對任何元素節點。
@* 比對任何屬性節點。
node() 比對任何類型的節點。

在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:

路徑表達式 結果
/petstore/* 選取 petstore 元素的所有子元素。
//* 選取文檔中的所有元素。
html/node()/meta/@* 選擇html下面任意節點下的meta節點的所有屬性
//title[@*] 選取所有帶有屬性的 title 元素。

contains()方法

實作屬性多值比對

contains(指定屬性名稱, 指定屬性值)

如需既擷取class=class="main-hd",又要擷取class="main"的節點時,如果HTML代碼中包含指定的屬性值,就可以比對成功。

html = etree.HTML(html_str)
div_all = html.xpath('//div[contains(@lcass, "main")]/text()') # text()擷取問題,下面介紹
           

複制

and

多屬性比對

在一個節點中出現多個屬性,這時就需要同時多個屬性,以便更加精确地擷取指定節點中的資料。

>>> from lxml import etree
>>> html_str = '''
<div class="main-hd">
 <div class="main review-item" id="13045497">雲朵</div>
 <div class="main review-item" id="13054201">資料STUDIO</div>
</div>
'''
>>> html = etree.HTML(html_str)
>>> div_all = html.xpath('//div[@class="main review-item" and @id="13054201"]/text()')
>>> print(div_all)
['資料STUDIO']
           

複制

擷取文本

可以使用XPath的

text()

方法擷取HTML代碼中的文本。

>>> from lxml import etree
>>> html_str = '''
<header class="main-hd">
        <a href="https://www.douban.com/people/162291901/" class="name">蒼華</a>
            <span class="allstar10 main-title-rating" title="很差"/>
        <span content="2020-12-10" class="main-meta">2020-12-10 01:29:29</span>
    </header>
'''
>>> html = etree.HTML(html_str)
>>> a_text = html.xpath('//a/text()')
>>> print(f'所有a下節點文本資訊:{a_text}')
所有a下節點文本資訊:['蒼華']
           

複制

XPath表達式中運算符:

運算符 描述 執行個體 傳回值
+ 加法 5 + 4 9
減法 5 – 4 1
* 乘法 5 * 4 20
div 除法 5 div 5 1
= 等于 price=100.0 如果 price 是 100.0,則傳回 true。如果 price 是 110.0,則傳回 false。
!= 不等于 price!=100.0 如果 price 是 100.0,則傳回 true。如果 price 是 99.0,則傳回 false。
< 小于 price<100.0 如果 price 是 99.0,則傳回 true。如果 price 是 100.0,則傳回 false。
<= 小于或等于 price<=100.0 如果 price 是 100.0,則傳回 true。如果 price 是 99.0,則傳回 false。
> 大于 price>100.0 如果 price 是 110.0,則傳回 true。如果 price 是 99.0,則傳回 false。
>= 大于或等于 price>=100.0 如果 price 是 110.0,則傳回 true。如果 price 是 99.0,則傳回 false。
or price=100.0or price=99.0 如果 price 是 99.0,則傳回 true。如果 price 是 99.8,則傳回 false。
and price>99.0 and price<100.0 如果 price 是 99.8,則傳回 true。如果 price 是 88.0,則傳回 false。
mod 計算除法的餘數 6 mod 4 2
| 計算兩個節點集 //div|//ul 傳回所有div和a節點集

XPath 軸(Axes)

軸可定義相對于目前節點的節點集。

軸名稱 結果
ancestor 目前節點的所有先輩(父、祖父等)。
ancestor-or-self 目前節點的所有先輩(父、祖父等)以及目前節點本身。
attribute 目前節點的所有屬性。
child 目前節點的所有子元素。
descendant 目前節點的所有後代元素(子、孫等)。
descendant-or-self 目前節點的所有後代元素(子、孫等)以及目前節點本身。
following 文檔中目前節點的結束标簽之後的所有節點。
following-sibling 目前節點之後的所有兄弟節點
namespace 目前節點的所有命名空間節點。
parent 目前節點的父節點。
preceding 文檔中目前節點的開始标簽之前的所有節點。
preceding-sibling 目前節點之前的所有同級節點。
self 目前節點。

from lxml import etree
html_str = '''
<div class="main-hd">
  <li><a href="https://www.douban.com/people/162291901/" class="name">蒼華</a>
</div>
'''
html = etree.HTML(html_str)
# 擷取li[0]所有祖先節點
html.xpath('//li[0]/ancestor::*')
# 擷取li[0]屬性為class="main"的祖先節點
class_div = html.xpath('//li[0]/ancestor::*[@class="main"]')           

複制