python版本回退_Python爬虫之BeautifulSoup解析之路
上一篇分享了正則表達(dá)式的使用,相信大家對正則也已經(jīng)有了一定的了解。它可以針對任意字符串做任何的匹配并提取所需信息。
但是我們爬蟲基本上解析的都是html或者xml結(jié)構(gòu)的內(nèi)容,而非任意字符串。正則表達(dá)式雖然很強(qiáng)大靈活,但是對于html這樣結(jié)構(gòu)復(fù)雜的來說,寫pattern的工作量會大大增加,并且有任意一處出錯都得不到匹配結(jié)果,比較麻煩。
本篇將介紹一款針對html和xml結(jié)構(gòu),操作簡單并容易上手的解析利器—BeautifulSoup。
BeautifulSoup的介紹
第一次使用BeautifulSoup的時候就在想:這個名字有什么含義嗎?美味的湯?于是好信也在網(wǎng)上查了一下。
來看,官方文檔是這么解釋的:
“
BeautifulSoup:?We called him Tortoise because he taught us”
意思是我們叫他烏龜因?yàn)樗塘宋覀?#xff0c;當(dāng)然這里Tortoise是Taught us的諧音。BeautifulSoup這個詞來自于《愛麗絲漫游仙境》,意思是“甲魚湯”。上面那個官方配圖也是來自于《愛麗絲漫游仙境》,看來是沒跑了,估計(jì)是作者可能很喜歡這部小說吧,因而由此起了這個名字。
好,讓我們看看真正的BeautifulSoup是什么?
BeautifulSoup是Python語言中的模塊,專門用于解析html/xml,非常適合像爬蟲這樣的項(xiàng)目。它有如下幾個使其強(qiáng)大的特點(diǎn):
它提供了幾個超級簡單的方法和Pythonic的語句來實(shí)現(xiàn)強(qiáng)大的導(dǎo)航、搜索、修改解析樹的功能。
它會自動把將要處理的文檔轉(zhuǎn)化為Unicode編碼,并輸出為utf-8的編碼,不需要你再考慮編碼的問題。
支持Python標(biāo)準(zhǔn)庫中的HTML解析器,還支持第三方的模塊,如 lxml解析器 。
BeautifulSoup的安裝
目前BeautifulSoup的最新發(fā)型版本是BeautifulSoup4,在Python中以bs4模塊引入。
博主使用的Python3.x,可以使用?pip3 install bs4?來進(jìn)行安裝,也可以通過官方網(wǎng)站下載來安裝,鏈接:https://www.crummy.com/software/BeautifulSoup/,具體安裝步驟不在此敘述了。
以為安裝完了嗎?還沒有呢。
上面介紹BeautifulSoup的特點(diǎn)時說到了,BeautifulSoup支持Python標(biāo)準(zhǔn)庫的解析器html5lib,純Python實(shí)現(xiàn)的。除此之外,BeautifulSoup還支持lxml解析器,為了能達(dá)到更好的解析效果,建議將這兩個解析器也一并安裝上。
根據(jù)操作系統(tǒng)不同,可以選擇下列方法來安裝lxml:
$?apt-get?install?Python-lxml
$?easy_install?lxml
$?pip?install?lxml
另一個可供選擇的解析器是純Python實(shí)現(xiàn)的?html5lib?, html5lib的解析方式與瀏覽器相同,可以選擇下列方法來安裝html5lib:
$?apt-get?install?Python-html5lib
$?easy_install?html5lib
$?pip?install?html5lib
下面列出上面提到解析器的使用方法。
解析器使用方法
Python標(biāo)準(zhǔn)庫BeautifulSoup(markup, "html.parser")
lxml HTML解析器BeautifulSoup(markup, "lxml")
lxml HTML解析器BeautifulSoup(markup, ["lxml", ? "xml"])
BeautifulSoup(markup, "xml")
html5libBeautifulSoup(markup, "html5lib")
推薦使用lxml作為解析器,lxml是用C語言庫來實(shí)現(xiàn)的,因此效率更高。在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必須安裝lxml或html5lib, 因?yàn)槟切㏄ython版本的標(biāo)準(zhǔn)庫中內(nèi)置的HTML解析方法不夠穩(wěn)定。
BeautifulSoup的文檔對象創(chuàng)建
首先引入bs4庫,也就是BeautifulSoup在Python中的模塊。
from?bs4?import?BeautifulSoup
好了,我們來看一下官方提供的例子,這段例子引自《愛麗絲漫游記》。
html_doc?="""
The Dormouse's storyThe Dormouse's story
Once upon a time there were three little sisters; and their names were
Elsie,
Lacie
andTillie;
and they lived at the bottom of a well.
...
"""
假設(shè)以上html_doc就是我們已經(jīng)下載的網(wǎng)頁,我們需要從中解析并獲取感興趣的內(nèi)容。
首先的首先,我們需要創(chuàng)建一個BeautifulSoup的文檔對象,依據(jù)不同需要可以傳入“字符串”或者“一個文件句柄”。
傳入“字符串”
soup = BeautifulSoup(html_doc)
傳入“文件句柄”,打開一個本地文件
soup = BeautifulSoup(open("index.html"))
文檔首先被轉(zhuǎn)換為Unicode,如果是解析html文檔,直接創(chuàng)建對象就可以了(像上面操作那樣),這時候BeautifulSoup會選擇一個最合適的解析器對文檔進(jìn)行解析。
但同時,BeautifulSoup也支持手動選擇解析器,根據(jù)指定解析器進(jìn)行解析(也就是我們安裝上面html5lib和lxml的原因)。
手動指定解析器如下:
soup = BeautifulSoup(html_doc, "lxml")
如果僅是想要解析HTML文檔,只要用文檔創(chuàng)建 BeautifulSoup?對象就可以了。Beautiful Soup會自動選擇一個解析器來解析文檔。但是還可以通過參數(shù)指定使用那種解析器來解析當(dāng)前文檔。
BeautifulSoup?第一個參數(shù)應(yīng)該是要被解析的文檔字符串或是文件句柄,第二個參數(shù)用來標(biāo)識怎樣解析文檔。如果第二個參數(shù)為空,那么Beautiful Soup根據(jù)當(dāng)前系統(tǒng)安裝的庫自動選擇解析器,解析器的優(yōu)先數(shù)序: lxml, html5lib, Python標(biāo)準(zhǔn)庫。在下面兩種條件下解析器優(yōu)先順序會變化:
要解析的文檔是什么類型: 目前支持, “html”, “xml”, 和 “html5”
指定使用哪種解析器: 目前支持, “l(fā)xml”, “html5lib”, 和 “html.parser”
BeautifulSoup的對象種類
Beautiful Soup將復(fù)雜HTML文檔轉(zhuǎn)換成一個復(fù)雜的樹形結(jié)構(gòu),每個節(jié)點(diǎn)都是Python對象,所有對象可以歸納為4種:Tag
NavigableString
BeautifulSoup
Comment
Tag就是html或者xml中的標(biāo)簽,BeautifulSoup會通過一定的方法自動尋找你想要的指定標(biāo)簽。查找標(biāo)簽這部分會在后面“遍歷查找樹”和“搜索查找樹”中介紹,這里僅介紹對象。
soup = BeautifulSoup('Extremely bold')
tag=soup.b
type(tag)
>>>
Tag標(biāo)簽下也有對象,有兩個重要的屬性對象:name和attributes。
Name
Name就是標(biāo)簽tag的名字,以下簡單操作便可獲取。
tag.name
>>> u'b'
Attributes
我們都知道一個標(biāo)簽下可能有很多屬性,比如上面那個標(biāo)簽b有class屬性,屬性值為boldest,那么我們?nèi)绾潍@取這個屬性值呢?
其實(shí)標(biāo)簽的屬性操作和Python中的字典操作一樣的,如下:
tag['class']
>>> u'boldest'
也可以通過“點(diǎn)”來獲取,比如:
tag.attrs
>>> {u'class': u'boldest'}
NavigableString是可遍歷字符串的意思,其實(shí)就是標(biāo)簽內(nèi)包括的字符串,在爬蟲里也是我們主要爬取的對象之一。
在BeautifulSoup中可以非常簡單的獲取標(biāo)簽內(nèi)這個字符串。
tag.string
>>> u'Extremely bold'
就這么簡單的完成了信息的提取,簡單吧。要說明一點(diǎn),tag中包含的字符串是不能編輯的,但是可以替換。
tag.string.replace_with("No longer bold")
tag
>>>
No longer boldBeautifulSoup對象表示的是一個文檔的全部內(nèi)容。大部分時候,可以把它當(dāng)作Tag對象。
soup.name
>>> u'[document]'
BeautifulSoup對象不是一個真正的tag,沒有name和attributes,但是卻可以查看它的name屬性。如上所示,“[document]”為BeautifulSoup文檔對象的特殊屬性名字。
還有一些對象也是我們需要特殊注意的,就是注釋。其實(shí)comment對象是一個特殊類型的NavigableString對象,請看下面。
markup =?""
soup = BeautifulSoup(markup)
comment?=?soup.b.string
type(comment)
>>>
comment
>>> u'Hey, buddy. Want to buy a used parser'
這和NavigableString的使用是一樣,同樣使用?.string?對標(biāo)簽內(nèi)字符串進(jìn)行提取。但是,請看上面comment這個例子,里面字符串是一個comment,有這樣的格式,一樣使用了?.string對其進(jìn)行提取,得到的結(jié)果是去掉了comment標(biāo)志的里面的字符串。這樣的話,當(dāng)我們并不知道它是否是comment,如果得到以上的結(jié)果很有可能不知道它是個comment。
因此,這可能會讓我們得到我們不想要的comment,擾亂我們的解析結(jié)果。
為了避免這種問題的發(fā)生,可以在使用之前首先通過以下代碼進(jìn)行一個簡單的判斷,然后再進(jìn)行其它操作。
iftype(soup.b.string)==bs4.element.Comment:
print(soup.b.string)
BeautifulSoup的遍歷文檔樹
仍然用最開始的《愛麗絲》中的一段話作為例子。
子節(jié)點(diǎn)
子節(jié)點(diǎn)有?.contents?和?.children?兩種用法。
contents
content屬性可以將標(biāo)簽所有子節(jié)點(diǎn)以列表形式返回。
#
The Dormouse's storyprint(soup.head.contents)
>>> [title>The Dormouse's story]
這樣就可以返回一個子節(jié)點(diǎn)標(biāo)簽了。當(dāng)然你也可以通過soup.title來實(shí)現(xiàn),但是當(dāng)文檔結(jié)構(gòu)復(fù)雜的時候,比如有不止一個title的話,那這樣就不如contents使用來的快了。
head下只有一個標(biāo)簽title,那么如果我們查看一下body下的子標(biāo)簽。
print(soup.body.contents)
>>>
['\n',
The Dormouse's story
, '\n',Once upon a time there were three little sisters; and their names were
Elsie,
Lacie
andTillie;
and they lived at the bottom of a well.
, '\n',...
, '\n']你會發(fā)現(xiàn)這些子節(jié)點(diǎn)列表中有很多“\n”,這是因?yàn)樗芽崭癜ㄟM(jìn)去了,所以這里需要注意一下。
children
也可以通過?.chidren?得到相同的結(jié)果,只不過返回的children是一個生成器(generator),而不是一個列表。
print(soup.body.children)
>>>
看到這是一個生成器,因此我們可以for..in..進(jìn)行遍歷,當(dāng)然也可以得到以上同樣的結(jié)果。
forchildinsoup.body.children:?print(child)
子孫節(jié)點(diǎn)
子孫節(jié)點(diǎn)使用.descendants屬性。如果子節(jié)點(diǎn)可以直接獲取標(biāo)簽的直接子節(jié)點(diǎn),那么子孫節(jié)點(diǎn)則可以獲取所有子孫節(jié)點(diǎn),注意說的是所有,也就是說孫子的孫子都得給我找出來,下用面開一個例子。
for?child?in?head_tag.descendants:?? ?print(child)
>>>
The Dormouse's story>>> The Dormouse's stor
title是head的子節(jié)點(diǎn),而title中的字符串是title的子節(jié)點(diǎn),title和title所包含的字符串都是head的子孫節(jié)點(diǎn),因此被循環(huán)遞歸的查找出來。.descendants?的用法和?.children?是一樣的,會返回一個生成器,需要for..in..進(jìn)行遍歷。
父節(jié)點(diǎn)
父節(jié)點(diǎn)使用?.parents?屬性實(shí)現(xiàn),可以得到父輩的標(biāo)簽。
title_tag?=?soup.title
title_tag
>>>
The Dormouse's storytitle_tag.parent
>>>
The Dormouse's storytitle_tag.parent.name
>>> head
獲得全部父節(jié)點(diǎn)則使用.parents屬性實(shí)現(xiàn),可以循環(huán)得到所有的父輩的節(jié)點(diǎn)。
link?=?soup.a
for?parent?in?link.parents:?? ?if?parent?is?None:?? ? ? ?print(parent)?? ?else:?? ? ? ?print(parent.name)
>>>
p
body
html
[document]
None
可以看到a節(jié)點(diǎn)的所有父輩標(biāo)簽都被遍歷了,包括BeautifulSoup對象本身的[document]。
兄弟節(jié)點(diǎn)
兄弟節(jié)點(diǎn)使用.next_sibling和.previous_sibling屬性。
兄弟嘛,不難理解自然就是同等地位的節(jié)點(diǎn)了,其中next_sibling 獲取下一個兄弟節(jié)點(diǎn),而previous_sibling 獲取前一個兄弟節(jié)點(diǎn)。
a_tag?=?soup.find("a",?id="link1")
a_tag.next_sibling
>>> ,
a_tag.previous_element
>>>
Once upon a time there were three little sisters; and their names were
兄弟節(jié)點(diǎn)可以通過?.next_siblings?和?.previous.sibling?獲取所有前后兄弟節(jié)點(diǎn),同樣需要遍歷獲取每個元素。
回退和前進(jìn)
當(dāng)然還有一些其它用法,如回退和前進(jìn).next_element和.previous_element,它是針對所有節(jié)點(diǎn)的回退和前進(jìn),不分輩分。
a_tag?=?soup.find("a",?id="link1")
a_tag
>>>
Elsie,
a_tag.next_element
>>> Elsie
a_tag.previous_element
>>>
Once upon a time there were three little sisters; and their names were
因?yàn)槭褂昧嘶赝?#xff0c;將會尋找下一個節(jié)點(diǎn)對象而不分輩分,那么這個標(biāo)簽的下一個節(jié)點(diǎn)就是它的子節(jié)點(diǎn)Elsie,而上一個節(jié)點(diǎn)就是上一個標(biāo)簽的字符串對象。find用法會在后續(xù)搜索文檔樹里面詳細(xì)介紹。
回退和前進(jìn)也可以尋找所有的前后節(jié)點(diǎn),使用.next_elements和.previous_elements。
for?elem?in?last_a_tag.next_elements:
if?elem.nameisNone:continue
print(elem.name)
>>>
a
a
p
返回對象同樣是生成器,需要遍歷獲得元素。其中使用了if判斷去掉了不需要的None。
節(jié)點(diǎn)內(nèi)容
前面提到過NavigableString對象的?.string?用法,這里在文檔遍歷再次體會一下其用法。
如果tag只有一個NavigableString?類型子節(jié)點(diǎn),那么這個tag可以使用.string得到子節(jié)點(diǎn),就像之前提到的一樣。而如果一個tag里面僅有一個子節(jié)點(diǎn)(比如tag里tag的字符串節(jié)點(diǎn)),那么這個tag也可以使用.string方法,輸出結(jié)果與當(dāng)前唯一子節(jié)點(diǎn)的.string結(jié)果相同(如上所示)。
title_tag.string
>>> u'The Dormouse's story'
head_tag.contents
>>> [
The Dormouse's story]head_tag.string
>>> u'The Dormouse's story'
但是如果這個tag里面有多個節(jié)點(diǎn),那就不靈了。因?yàn)閠ag無法確定該調(diào)用哪個節(jié)點(diǎn),如下面這種。
print(soup.html.string)
>>> None
如果tag中包含多個字符串,可以使用?.strings?來循環(huán)獲取,輸出的字符串中可能包含了很多空格或空行,使用.stripped_strings可以去除多余空白內(nèi)容。
上面提介紹的都是如何遍歷各個節(jié)點(diǎn),下面我們看看如何搜索我們我們真正想獲取的內(nèi)容,如標(biāo)簽屬性等。
BeautifulSoup的搜索文檔樹
搜索文檔樹有很多種用法,但使用方法都基本一致。這里只選擇介紹一種.find_all。
find_all()
find_all(name,?attrs?,?recursive?,?text?,?**kwargs)
find_all()?方法可以搜索當(dāng)前標(biāo)簽下的子節(jié)點(diǎn),并會經(jīng)過過濾條件判斷是否符合標(biāo)準(zhǔn),先隨便看個例子。
soup.find_all("a")
>>>
[Elsie,
Lacie,
Tillie]
soup.find_all(id="link2")
>>>
[Lacie]
通過以上例子,可以發(fā)現(xiàn),我們只要設(shè)定好我們的過濾條件,便可輕松的解析我們想要的內(nèi)容。這些條件如何設(shè)定呢?
就是通過find_all()的這些參數(shù)來設(shè)置的,讓我們來看看。
Name參數(shù)
name參數(shù)就是標(biāo)簽的名字,如上面的例子尋找所有標(biāo)簽,name參數(shù)可以是字符串、True、正則表達(dá)式、列表、甚至具體方法。
下面舉個正則表達(dá)式的例子。
importre
soup?=BeautifulSoup(html_doc,?'lxml')fortag?insoup.find_all(re.compile("^t")):print(tag.name)
>>>?title
可以看到正則表達(dá)式的意思是匹配任何以“t”開頭的標(biāo)簽名稱,就只有title一個。
使用“True”會匹配任何值,使用“列表”會匹配列表中所有的標(biāo)簽項(xiàng),如果沒有合適的過濾條件,還可以自定義一個“方法”。
Keyword參數(shù)
就如同Python中的關(guān)鍵字參數(shù)一樣,我們可以搜索指定的標(biāo)簽屬性來定位標(biāo)簽。
soup.find_all(id='link2')
>>>
[Lacie]
找到了id屬性為link2的標(biāo)簽。
soup.find_all(href=re.compile("elsie"))
>>>
[Elsie]
找到了href屬性里含有“elsie”字樣的標(biāo)簽。
也可以同時定義多個關(guān)鍵字條件來過濾匹配結(jié)果。
soup.find_all(href=re.compile("elsie"),?id='link1')
>>>
[three]
text參數(shù)
通過text參數(shù)可以搜索匹配的字符串內(nèi)容,與name的用法相似,也可以使用字符串、True、正則表達(dá)式、列表、或者具體方法。
soup.find_all(text="Elsie")>>> [u'Elsie']
soup.find_all(text=re.compile("Dormouse")) >>>
[u"The Dormouse's story", u"The Dormouse's story"]
limit參數(shù)
limit參數(shù)可以限制返回匹配結(jié)果的數(shù)量,看下面這個例子。
soup.find_all("a",?limit=2)
>>>
[Elsie,
Lacie]
文檔中本來有三個標(biāo)簽,但是通過限制只得到了兩個。
recursive參數(shù)
find_all()會尋找符合匹配條件的所有子孫節(jié)點(diǎn),如果我們只想找直接的子節(jié)點(diǎn),就可以設(shè)置recursive參數(shù)來進(jìn)行限制,recursive=False。
soup.html.find_all("title")
>>> [
The Dormouse's story]soup.html.find_all("title",?recursive=False)
>>> [ ]
上面是兩種使用recursive和沒有使用recursive的情況,可以發(fā)現(xiàn)它的作用。
以上就是find_all()所有參數(shù)的介紹,其它方法如find(),find_parents()等更多方法與find_all()基本一致,可以舉一反三。
總結(jié)
以上就是BeautifulSoup的使用方法介紹,主要記住三個部分內(nèi)容:
BeautifulSoup對象種類
BeautifulSoup的遍歷文檔樹
BeautifulSoup的搜索文檔樹
總結(jié)
以上是生活随笔為你收集整理的python版本回退_Python爬虫之BeautifulSoup解析之路的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “瑕璧丽锦质”下一句是什么
- 下一篇: lempel ziv matlab,基于