淺談解析庫XPath,bs4和pyquery

《淺談解析庫XPath,bs4和pyquery》

作者:墨非墨菲非菲

前幾天在CSDN看到一篇帖子,題目是“如何讓自己像打王者一樣發了瘋,拼了命,石樂志的學習”。這裏面講到了階段性反饋機制,我覺得蠻有意思的,正好前兩天用python寫了一個scrawler爬取了某XXXX軟件上面的挑戰答題並自動匹配。在解析題庫網頁的時候碰到了一系列的問題,把三種解析庫都回顧了個遍。藉着這個興奮勁兒,決定碼一篇python解析庫————lxml,bs4,以及pyquery的簡要概述。某xxxx :happy:
下面僅僅是我個人的回憶和記錄,僅供參考,錯誤之處還請多多指正。

寫在前面

以上提到的三個是python語言中最最最常用的三個解析庫,解析庫是用來提取網頁的html信息的。首先要解釋一下,網頁中如此多的信息,爲什麼能夠被精準的獲取到。網頁可以分爲三大部分————HTML,JavaScrip和CSS,這些要素構成了我們看到的豐富多彩的網頁。

解析庫基於網頁的兩類特徵,一類是節點樹(HTML DOM),一類是css選擇器。如果把節點樹比作一個家庭,那麼它就包含了它爺爺,它老漢(parent),它兄弟(sibling)以及它兒子和孫孫(children)。有了這個節點樹,網頁裏面每一個元素就層級分明的展現了出來。節點樹 具體的,構成這棵樹的枝節,又包根元素(html),元素(title,body,a…),屬性(class,href…)和文本等等。css,即層疊樣式表,它有一套屬於自己的語法規則,舉個例子,選擇器(.link)代表了’class=“link”’,(#song)代表了’id=“song”’,(a)代表了’a’的所有節點。 html網頁
有了上面的規則,每個元素的位置就被唯一確定了下來了,接下來解析庫就登上了舞臺,使出了元哥般的秀髮。

下面是分割線


【解析庫一:XPath】

XPath,全名是XML Path Lauguage。拿到一個庫,如果有精力和毅力閱讀官方文檔當然最好,雖然我幾乎沒有過。知道名字,好像意義不大,所以,直接上乾貨。

1.初始化

先說說初始化(initition),可被解析的有字符串(string)和html文件(filename.html)。具體的使用方法:
首先要安裝lxml庫,python CMD:pip install lxml


```python

```python
res = '''   <div class="info">
        <div class="hd">
            <a href="https://movie.douban.com/subject/1316510/" class="">
                <span class="title">射鵰英雄傳之東成西就</span>
                        <span class="title"> / 射鵰英雄傳之東成西就</span>
                    <span class="other class-1" name="item"> / 東成西就  /  大英雄 (日本)</span>
            </a>
                <span class="playable">[可播放]</span>
    </div></div>
            '''
from lxml import etree
html = etree.HTML(res) #調用HTML()方法初始化字符串(string)例
html = etree.tostring() #調用tostring()方法可以補全未閉合的標籤
print(html.decoding('utf8')) #以字符串的格式打印

 

文件採用以下方法:

from lxml import etree  
html = etree.parse('./text.html',etree.HTMLParser()) #調parse()方法來實例化文件(filename.html)  
result = html.xpath('//*') #xpath()裏面傳入匹配規則  

到這裏,初始化的方法就說完了(我所知道的,手動狗頭保命/滑稽)

2.匹配方法

(1)子節點(/)和子孫節點(//)

例如,我想獲取以上res文本中的“射鵰英雄傳之東成西就”和’a’標籤裏的網址,
以下涉及到了子節點,子孫節點的選擇,屬性的匹配,文本和屬性值的獲取。

movie = html.xpath('//div[@class="info"]//a/span/text()')  
url = html.xpath('//div[@class="info"]//a/@href')  

(2)獲取文本“東成西就”,class屬性值有兩個,那麼就引入了屬性多值匹配和多屬性匹配

text=html.xpath('//div[@class="info"]//span[contains(@class,"other") and @name="item"]/text()')  

3.節點選擇

在匹配方法中主要涉及了子節點和子孫節點。接下來還有父節點(parent)和祖父節點(parents)

result = html.xpath('//span[@class="titile"]/../a')  

因爲xpath默認解析全部符合規則的標籤,所以這裏就不提及sibling節點。

4.類型

如果可愛的你玩元哥不想成爲神仙打架,那麼時時刻刻清楚自己的本體在哪是很重要的。如果在運用解析庫的時候不想天花亂墜,得時時刻刻清楚下一步輸出的文件類型!!!知道類型才知道應該用什麼方法。

print(type(result),result,sep='|')  
>>><class 'list'>|[<Element div at 0x19804f1d308>]  

是列表類型記得迭代,for搞定一切!

5.按序選擇

因爲同級的標籤是有很多的,上面得出了輸出文件是list的結論,那麼聰明的你一定知道了,它可以切片,我彷彿看到了樂事薯片發出的香甜可口的氣息~好了,pia!回到正題,按序選擇就是用中括號傳入索引獲取特定了次序的節點。
注意是從“1”開始
直接上碼

result = html.xpath('//li[1]/a/text()')
#可替換的例如:[last()], [last()-1], [positon()<3]等等

6.好了,沒有6了

當然,更多詳細的關於python的lxml庫的使用可以訪問網址
個人的一點小總結,lxml的使用方法和BeautifulSoup是比較接近的,都是依賴於節點樹和標籤。功能上說得上中規中矩,該有的它都有。一定要說一點優勢嘛,它的層級結構是非常清楚的,非常易於閱讀。並且直接建立在lxml之上,匹配速度應該算是很快的了(空口說這個速度會不會有些有失公允,讓用戶感知不強?)。不過關於屬性匹配的寫法,難免複雜了些。遇到屬性多值的匹配還傲嬌,必須得用[constains(@class,"…")來進行匹配,多多少少有些複雜。當然,這都是些後話啦。

【解析庫二:BeautifulSoup】

一個強大的解析工具BeautifulSoup,藉助網頁結構和屬性來解析網頁。最先接觸的解析庫,本着“國外的月亮更圓”的陋習,在只知道BeautifulSoup的時候,還去硬剛過re,結果被re複雜的匹配規則勸退。順便說一下,re它長這樣:

>data-src = re.findall('<dd>.*?class="star">(.*?)</dd>))$。  

手動(黑人臉問好.png),廢話不多說,直接上菜。

1.初始化

BeautifulSoup可傳入字符串(string),在解析時依賴於解析器,例如’BeautifulSoup(res,‘html.parser’),這裏的html.parser就是python內置的標準解析庫,執行速度適中,文檔容錯性強。如果你非要快,'lxml’和’xml’是你的不二選擇,不過提前安裝好C語言庫哦。
首先要安裝bs4庫,python CMD:pip install bs4

import requests
from bs4 import BeautifulSoup
res = requests.get('https://www.baidu.com/s?ie=UTF-8wd=%E5%B0%8F%E8%AF%B4').text    #獲取網頁源代碼
soup = BeautifulSoup(res,'lxml')     #格式化字符串,依靠lxml析庫

2.基本用法

find_all(),顧名思義,查找所有符合條件的元素。它的api如下:
find_all(name,attr,text,**kwargs)
實操環節

items = soup.find('div', class_="c-tabs c-gap-top-small")find('ul').find_all('li)['href']
for item in items[0]:
    print(item)

以上實例展示了find():查找符合條件的第一個標籤,屬性的引用需要說明,在python中class爲關鍵字,所以碰到class標籤要加上下劃線,獲取屬性值[‘href’] or .get('herf), 文本的獲取(.text)/(.string)

3.節點選擇

(1)子節點和子孫節點

選取節點元素之後,如果想要獲取它的直接子節點,直接調用contents屬性或children即可,例如:

print(items.a.contents)

如果要得到子孫節點,可以調用descendants屬性。值得一提,descendants會遞歸查詢所有的子孫節點。

(2)父節點和祖父節點

print(items.a.parent)   #父節點
print(items.a.parents)   #祖先節點

(3)兄弟節點

print(items.a.sibling)  

哈哈哈,到這裏,你以爲只是簡單的複述幾個單詞咯?下面纔是冷知識點。
next_sibling, previous_sibling, list(enumerate(next_siblings)), list(enumerate(previous_siblings))

4.類型

類型永遠是個重點,初次試水記得時時刻刻用type()函數查詢,直接撿現的:

print(type(soup))
>>><class 'bs4.element.Tag'>

在沒有被text之前,它始終是個bs4.element.Tag類型,這也意味着,能夠在bs4.element.Tag上面套娃————實現嵌套功能:find(‘ul’).find_all('li)。

5.CSS選擇器

BeautifulSoup還提供了css選擇器,只需要調用select()方法,傳入相應的css選擇即可。並且,它繼承了BeautifulSoup的bs4.element.Tag類型,支持嵌套功能。舉個例子:

items = soup.find(‘div’, class_=“c-tabs c-gap-top-small”)find(‘ul’).find_all('li)[‘href’] #find()寫法
items = soup.select(‘div[contains(@class=“c-tabs”)]/ul/li@href’) #select()寫法


6.好了,BeautifulSoup的基本用法就介紹完了

最後來點小總結。聰明的你一定會發現,BeautifulSoup的篇幅足夠短,因爲它足夠easy和brief。而且它向左兼容節點,向右能用css。給它個“好看又能打”的稱號一點也不爲過了。從初始化開始,它的文件類型始終是<class ‘bs4.element.Tag’>,你可以隨意嵌套。碰到單個文件,直接打印;多個文件,迭代打印。唯一需要注意的是class_="…",一定不要忘記!

【解析庫三:pyquery】

接下來,讓我們來感受一下這個偏科生————pyquery的強大之處。

1.初始化

首先安裝pyquery庫,python CMD:pip install pyquery
pyquery支持三種類型的參數傳入,分別是字符串(string),網址(url=‘www.baidu.com’),文件(filename=’.*.html’),舉個例子:

from pyquery import PyQuery as pq
doc = pq(url='www.baidu.com) 

細心的你在這裏已經發現了問題:爲啥傳了一個網址進去,這怎麼用。其實,pyquery自帶了獲取html的功能,它相當於:

doc = pq(requests.get('www.baidu.com).text) 

2.基本用法

複習一下CSS選擇器的規則。

lis = '''<div id="container">
    <div class="hd">
        <a href="https://movie.douban.com/subject/1291875/" class="">
            <span class="title">陽光燦爛的日子</span>
            <span class="other">&nbsp;/&nbsp;In the Heat of the Sun</span>
        </a>
    </div>
    <div class="bd">
        <p class="">導演: 姜文 Wen Jiang&nbsp;&nbsp;&nbsp;主演: 夏雨 Yu Xia / 寧靜 Jing Ning / 陶虹 Hong Tao<br>
        1994&nbsp;/&nbsp;中國大陸 中國香港&nbsp;/&nbsp;劇情 愛情</p>
    </div>
</div>
'''  
from pyquery import PyQuery as pq
doc = pq(lis)
print(doc('#container .hd a span.title).text())
>>>陽光燦爛的日子 

獲取屬性:調用attr方法(a.attr('href))或(a.attr.href);獲取文本:text();獲取html:html()

3.查找節點

(1)子節點和子孫節點

查找子節點,需要用到find()方法,會將符合條件的所有節點全部提取出來。如果只查找子節點,調用children()方法就即可。

(2)父節點和祖父節點

我們可以用parent()來獲取某個節點的父節點,parent()獲取祖先節點,可以傳入參數篩選。

items = lis('span.title').parents('a')

(3)兄弟節點

獲取兄弟節點可以用siblings()方法

4.文件類型

如期而至,讓我們來看看pyquery的文件類型是什麼。

print(type(doc))
<class 'pyquery.pyquery.PyQuery'>

如果獲取的是單個節點,可以直接輸出,也可以轉化爲字符串輸出。
讓我們來看看下面這種情況:

print(type(items))
<class 'generator'> #generator意思是"發生器",這個時候就需要歷了。使用之前需要先用items()格式化。
for item in items.items()
    print(item)

!!!重點在這裏,pyquery返回的多個節點需要用遍歷處理。

5.節點處理

下面列舉幾種pyquery常用的節點處理的方法。

(1)addClass( ) 和removeClass( )

lis='''<div class="hd">
        <a href="https://movie.douban.com/subject/1291875/" class="">
            <span class="title">陽光燦爛的日子</span>
            <span class="other">&nbsp;/&nbsp;In the Heat of the Sun</span>
        </a>
    </div>'''  
from pyquery import PyQuery as pq
doc = pq(lis)
print(doc('.hd').removeClass('hd').addClass('hahah'))

(2)attr( )和text( )

print(doc('a').attr('name','link'))
print9doc('a').text('changed items)

(3)remove( )

比如提取標籤裏面“陽光燦爛的日子”

item = lis('a')
item.find('.other').remove()
print(item)

6.好了,pyquery的用法到這裏也介紹完了

總結一下,功能強大,寫法簡潔。願你解析半天,歸來還是pq。

#Title1
##line2
###line3 >muname
<‘alert(‘hello world’);’ >
‘’’
<print(‘hello world’)>
‘’’
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tvW7TjBx-1585707653825)(C:\Users\tingy\Desktop\節點樹.jpg)]

寫在最後

從2020/03/31 15:20開始,到04/01 09:59,這一篇文章已經算是圓滿了。再次感謝我媽和我的芬芬兒昨天晚上幫我洗碗,我才能夠比較順利的,時效的完成這篇梳理。

引用

[1]崔慶才,Python3網絡開發與實踐,[M],2018.4,人民郵電出版社。
[2]URL:https://docs.python.org/zh-cn/3.7/ 點擊閱讀python官方文檔
[3]python3 lxml標準庫點擊閱讀lxml文檔
[4]沈承放,莫達隆,beautifulsoup庫在網絡爬蟲中的使用技巧及應用,[J],2019.3,2019(28)點擊閱讀paper
[5]風變編程——BeautifulSoup實踐。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章