xpath 和 jsonpath 解析


XPath 是一門在 XML 文檔中查找信息的語言,可用來在 XML 文檔中對元素和屬性進行遍歷,快速提取xml文檔中的的信息,詳細的xpath教程參見:https://www.w3school.com.cn/xpath/index.asp

xpath用法

//任意層次下,/ 根或下一層

//@id

任意層次下有id的節點的屬性值

//*[@id]      //book[@id]

所有含有id屬性的節點     含有id屬性的book節點

/bookstore/*      /bookstore//*

一層的所有節點              遞歸下去每一層節點

//*   //@*   // *[@*]   

*表示所有,@是取屬性值

//book[position()=3]   //book[positon()<last()-2]

限制book節點的位置范圍

//book[price>400]

price是子節點,其值大於40的book節點

/book/text()   /book//text()

下的所有文本節點,/子節點和 // 子孫節點

//*[local-name()="book"]

所有節點中名稱為book的

//book/child::node()  

node()表示節點,child::表示子節點

//*[self::title or self::price]

任意層次下的節點,這些節點自身是title或者price

//book[contains(@class, class_name)]

contains() 包含,class屬性中包含class_name的節點

//*[contains(local-name(), book)]

標簽名包含book的節點

其他還提供了如local-name()等函數,可查看w3cSchool獲取詳細用法。

lxml

它是python的一個庫,用於對xml和html文檔進行解析,性能非常好,最新版支持的2.6+,python3支持到3.6。

centos安裝

在centos下需要編譯安裝兩個依賴:libxml2-devel 和 libxslt-devel

yum install libxml2-devel 

不同的平台可能以來不同,詳見官網說明https://lxml.de/installation.html

依賴安裝完成后,使用pip安裝即可

pip install lxml

lxml的使用

lxml是使用C語言的開發的對xml及html文本的解析庫,主要基於C語言的計算速度快的有點,python的lxml只是封裝了調用接口,方便python使用lxml解析xml文本。

etree.HTML 

from lxml import etree

html_text = """<html><body><div>
    <ul>
         <li class="l1"><a href="link1.html">第一行</a></li>
         <li class="l2"><a href="link2.html">第二行</a></li>
         <li class="l3"><a href="link5.html">第三行</a></li>
    </ul>
 </div>
</body></html>"""
# 我們獲取的一個網頁的html文本數據,字符串或者字節序列均可,

root = etree.HTML(html_text)       # 返回一個root節點對象,相當於html的document全局根對象
print(root, type(root))            # <Element html>  <class 'lxml.etree._Element'>

也可以通過fromstring方法解析,獲取根_Element對象。獲取root對象后,可以通過root對象找到其所有的子節點也就是html中的所有子標簽。常用的方式就是通過xpath語法解析及即可。如果我們直接讀取一個html文件中的html 內容,使用html=etree.parse('test.html',etree.HTMLParser())的方式即可,同時會自動修復html中的一些標簽的問題。每一個_Element元素對象中都包含了其直接子_Element,使用容器化的方式關聯了直接子_Element,例如在div標簽下有一個直接子標簽ul。我們可以通過xpath的解析方式去獲得div的_Element對象。

div_element = root.xpath("//div")  # 任意層次下的ul標簽,只有一個
print(div_element)                 # <Element div at 0x23a32345>

print(len(div_element))            # 1,長度為一,只有一個ul子標簽
ul_element = div_element[0]        # 容器的方式直接取第一個子標簽,及ul標簽

# 也可直接對其進行遍歷
for li in ul_element:
    print(li)                      # 3個li對應的_Element對象 

etree._Element

每一個_Element對象對應了html中的標簽,類似於一棵DOM樹的結構,我們可以手動創建單個節點,並可以將多個_Element合並到這個dom 樹上的指定位置,與的其他_Element對象產生關系。

每一個_Element對象的操作方式和jQuery的對dom樹中節點的操作極其相似。

from lxml.etree import Element   # Element是一個工廠函數,返回一個指定名的_Element對象

root = Element("root", attrib={"abc":"hello"}, id="2123")   # 兩種方式添加屬性,
print(etree.tostring(root, pretty_print=True))       # tostring函數打印節點源碼 b'<root id="2123" abc="hello"/>'
print(root.tag)                   # 標簽名 root

屬性值得創建可以通過attrib參數來指定,也可以使用關鍵字傳參得方式指定,未定義得關鍵字都將收集到**extra可變關鍵字參數中,與attrib字典值合並作為屬性。_Element通過字典得方式管理屬性一個標簽的屬性信息,並提供了訪問接口

print(etree.tostring(root))       # tostring函數打印節點源碼 b'<root id="2123" abc="hello"/>'

root.get("id")                    # 獲取一個屬性值,2123
root.set("name", "title")         # 添加一個屬性,name=title

兩個_Element對象之間可以建立關系,可以成為兄弟節點(同級)和父子節點得關系。

root = Element("root")      # <root />
body = Element("body")      # <body />

etree.SubElement(root, body)   # <root> <body /> </root>     body成為root得子節點
root.append(body)              # 或者調用節點的append方法

BeautifulSoup

BeautifulSoup同樣使用xpath語法在文本中提取數據,並在其基礎上做了更加方便使用者的封裝,並且內部解析解可以使用多種解析引擎,其中包括lxml, 是一個效率較高的開源解析庫,而BeautifulSoup由此也獲得更過使用者的青睞。

解析庫包括以下,初始化時根據需要指定不同的features參數即可指定不同的解析器,各個解析器有自己的優缺點。根據需要選擇即可。

解析器 使用方法 優勢 劣勢
Python標准庫 BeautifulSoup(markup, "html.parser")
  • Python的內置標准庫
  • 執行速度適中
  • 文檔容錯能力強
  • Python 2.7.3 or 3.2.2)前 的版本中文檔容錯能力差

lxml

HTML 解析器

BeautifulSoup(markup, "lxml")
  • 速度快
  • 文檔容錯能力強
  • 需要安裝C語言庫

lxml

XML析器

BeautifulSoup(markup, ["lxml-xml"])

BeautifulSoup(markup, "xml")

  • 速度快
  • 唯一支持XML的解析器
  • 需要安裝C語言庫
html5lib BeautifulSoup(markup, "html5lib")
  • 最好的容錯性
  • 瀏覽器的方式解析文檔
  • 生成HTML5格式的文檔
  • 速度慢
  • 不依賴外部擴展

安裝

直接使用pip 安裝即可 pip install bs4該安裝只是安裝了bs4庫,如果需要使用lxml作為xpath解析引擎需要自行安裝lxml庫,才能使用lxml,如果沒有安裝lxml,也可以使用內部bs4內部自帶的

使用

官方文檔有詳細的使用介紹:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/

from bs4 import BeautifulSoup
import request

html_text = """<html><body><div>
    <ul>1234
         <li class="l1"><a href="link1.html">第一行</a></li>
         <li class="l2"><a href="link2.html">第二行</a></li>
         <li class="l3"><a href="link5.html">第三行</a></li>
    </ul>
 </div>
</body></html>"""

soup = BeautifulSoup(markup=html_text, features="lxml")  
# markup指定需要解析的文檔,string或者類文件對象,features指定解析器,這里使用lxml, 返回的
print(soup, type(soup))

## 輸出結果------------
<html><body><div>
<ul>1234
<li class="item-0"><a href="link1.html">第一個</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0"><a href="link5.html">a屬性</a></li>
</ul>
</div>
</body></html>    <class 'bs4.BeautifulSoup'>

BeautifulSoup對象

初始化后返回的是一個Beautifulsoup對象,代表了整個文檔,也是我們進行數據提取的起點。

常用的方法

soup.name
soup.prettify()  # 格式輸出
soup.builder     #  解析引擎,這里是lxml
soup.find_all()
soup.find()      # 默認任意層尋找指定標簽,且深度優先遍歷。
soup.tag_name    # 根據tag名尋找

tag對象

文檔下直接存在div標簽,這些標簽對應一個tag對象。

div = soup.div   # soup 下的任意層次div標簽
print(div.name, div.attrs, div.text) # 標簽名div,標簽中的屬性,None,其中文本內容,子標簽中有全部顯示

tag類型可以繼續向下尋找節點,使用屬性訪問的方式及soup.div的方式只會尋找到soup對象下的第一個div標簽,這實際是電泳find方法找尋一個節點。如果需要找尋所有div節點需要使用find_all方法,傳入尋找的標簽名即可,他會返回節點的列表,遍歷即可逐一獲取。

# find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
# 查詢到所有的li標簽並遍歷
for li in div.find_all("li"):
    print(li.text)

tag.name
tag.attrs   # 屬性字典
tag.get("id"), tag["id"], tag.attrs["id"], tag.attr.get("id")      # 取一個屬性值

NavigableString

如果只需要標簽內部的文本內容,而不在意標簽,調用tag的string屬性即可。

# div為一個Tag 對象
div.string       # div 下必須沒有子標簽,直接為文本內容,則會返回文本,否則為None
div.strings      # 返回迭代器,div標簽下所有的文本內容
div.stripped_strings  # strings的內容去除了兩端空白

print(div.string)                  # None
print(list(div.strings))           # ['1234\n    ', '\n', '第一個', '\n', 'second item', '\n', 'a屬性', '\n', '\n']
print(list(div.stripped_strings))  # ['1234', '第一個', 'second item', 'a屬性']

注釋對象

如果源html文檔中含有注釋內容,經過解析bs4會將其封裝為注釋對象。隸屬於某個標簽的注釋可以使用該標簽的tag.comment獲取

遍歷文檔樹

遍歷子節點
soup.div.contents           # div的直接子節點們,返回一個列表。
soup.div.children           # div返回迭代器,iter(content)
soup.div.descendants        # div 節點的所有子孫節點, 一層層遞歸,且深度優先。
遍歷祖先節點
tag.parent        # 當前tag的第一個父節點,同樣是tag對象
tag.parents       # 父節點們,返回迭代器,從近到遠

soup.li.parent    # 得到ul節點
遍歷兄弟節點
tag.next_sibling         # 下一個兄弟節點
tag.previous_sibling     # 上一個兄弟節點
tag.next_siblings        # 兄弟節點們
li
soup.li.next_sibling        # "\n"   li標簽后存在一個文本節點,這里是換行符,所以下一個不是li。
list(soup.li.next_siblings) #  ['\n', <li>second item</li>, '\n', <li>a屬性</li>, '\n']
其他節點
tag.next_element      # 當前節點的下一個節點(包括標簽和文本),按照深度優先的原則
tag.next_elements     # tag同級的節點和其所有的子節點的迭代器

soup.li.next_element  #  <a></a>   第一個li中內部的a標簽節點
soup.li.next_elements # 按照順序,遍歷li同級極其子標簽(包括文本節點),按照深度優先規則。返回迭代器

find_all

搜索文檔內部所有滿足條件的節點,返回一個列表。

def find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
參數說明:
  name: 字符串 | re.compile("^a")正則對象 | 列表多個 | True(所有節點) | 接受標簽名的函數,返回True 
  attr: 屬性字典,指定標簽名和值,值可以使用字符串,正則對象,列表,True,函數,匹配時結果True表示匹配
  **kwargs:接受沒有被其他關鍵字參數接受的傳參,並合並到attr中作為屬性刪選,class屬性應該寫為class_
  recursive:默認True遞歸查找,是否遞歸相當於使用 // 和 / 的區別
  text: 文本匹配來過濾標簽,同name 使用標簽一樣,參數可以使用正則對象,函數等
  limit:結果的個數

find_all 方法可以簡寫,tag或者soup對象是可調用對象,其__call__方法直接調用find_all,方便使用

soup = Beautifulful(open("index.html"), "lxml")
soup.find_all("div", id="item")     # 等價於 soup("div", id="item")
soup.div.findall("li")              # soup.div("li")

find

find函數是使用深度優先的方式找到第一指定的節點。與屬性訪問的方式相同。

def find(self, name=None, attrs={}, recursive=True, text=None, **kwargs):pass

soup.div # 等價於 soup.find("div")

獲取文本

獲得節點對象后,可以使用text和get_text()方法獲取該節點的文本內容,text等價於默認的get_text方法,結果不會去除兩端的空白字符,如果需要去除,可以使用get_text(strip=True)

tag.text        # 等價tag.get_text()  
tag.get_text(strip=True)         # 去除空白字符 

string 和 strings 方法也可以提取文本內容,但是標簽后的空白字符也會算作一個元素被篩選。

CSS選擇器

css選擇器是通過節點得特殊屬性值,例如id, class,標簽名,來查找html節點

# 使用select方法,即可指定css選擇器規則

tag.select("p")        # 元素選擇
tag.select(".title")   # 類選擇,根據class名
tag.select("#id-1")    # 根據id值

tag.select("div p")    # div下所有p
tag.select("div > p")  # div下第一層的p,其余不要
tag.select("div.content > p:nth-of-type(2)")  # 使用了偽類,class為content的div標簽下第二個p標簽

tag.select(div p)

# 屬性選擇器
tag.select("[src]")      # 有src屬性的
tag.select("[src="/"]")  # [src^="/"] 以開頭     [src$="/"] 以結尾   [src*="http"] 包含有
tag.select("[class=title light]")   # 完全匹配,兩個同時滿足
tag.select("[class~=title]")        # 標簽可以有多個屬性,其中有一個是title即可

JsonPath

jsonpath是對一些多層嵌套結構的json字符串進行信息提取的方式,與xpath解決的問題是相同的,即從一個較為特殊的大字符串中去快速提取的想要的信息。json格式的數據作為服務進程之間的交互已經受到了相當流行,常常需要從某些json結構中提取數據。

使用

jsonpath的使用方式和xpath使用方式相似,只是使用了不同的元字符表示,可以對比xpath的語法進行使用。

語法

JSONPATH 描述
$ 根對象,例如$.name
. 或者 [ ] 子節點,例如$.name
.. 子孫節點訪問,例如$..name

@

當前對象自身

* 通配符,所有,例如$.leader.*
['key0','key1'] 多個節點訪問。例如$['id','name']
[num] 數組索引訪問,可以是負數。例如$[0].leader.departments[-1].name
[num0,num1,num2...] 數組多個元素訪問,可以是負數,返回數組中的多個元素。例如$[0,3,-2,5]
[start:end :step] 數組范圍訪問,可以是負數;step是步長,返回數組中的多個元素。例如$[0:5:2]
[?(key)] 對象屬性非空過濾,例如$.departs[?(name)],存在指定屬性的departs
[key > 123] 數值類型對象屬性比較過濾,例如$[id >= 123],支持=,!=,>,>=,<,<=
[key = '123'] 字符串類型對象屬性比較過濾,例如$[name = '123'],支持=,!=,>,>=,<,<=
[key like 'aa%'] 字符串類型like過濾,例如$[name like 'sz*'],通配符只支持%,支持not like
[key rlike 'regexpr'] 字符串類型正則匹配過濾,指定正則字符串
例如departs[name like 'aa(.)*'],
正則語法為jdk的正則語法,支持not rlike
[key in ('v0', 'v1')] IN過濾, 支持字符串和數值類型
例如:
$.departs[name in ('wenshao','Yako')]
$.departs[id not in (101,102)]
[key between 234 and 456] BETWEEN過濾, 支持數值類型,刪選數值范圍,支持not between
例如:
$.departs[id between 101 and 201]
$.departs[id not between 101 and 201]
length() 或者 size() 數組長度。例如$.values.size()
支持類型java.util.Map和java.util.Collection和數組
keySet() 獲取Map的keySet或者對象的非空屬性名稱。例如$.val.keySet()
支持類型:Map和普通對象
不支持:Collection和數組(返回null)

xpath對比示例

 

XPath JSONPath Result
/store/book/author $.store.book[*].author the authors of all books in the store
//author $..author all authors
/store/* $.store.* all things in store, which are some books and a red bicycle.
/store//price $.store..price the price of everything in the store.
//book[3] $..book[2] the third book
//book[last()] $..book[(@.length-1)]
$..book[-1:]
the last book in order.
//book[position()<3] $..book[0,1]
$..book[:2]
the first two books
//book[isbn] $..book[?(@.isbn)] filter all books with isbn number
//book[price<10] $..book[?(@.price<10)] filter all books cheapier than 10
//* $..* all Elements in XML document. All members of JSON structure.




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM