HTML解析庫BeautifulSoup4

本文地址:http://www.reibang.com/p/e9255c446a77

1. 簡(jiǎn)介

BeautifulSoup 是一個(gè)可以從HTML或XML文件中提取數(shù)據(jù)的Python庫吨拍,它的使用方式相對(duì)于正則來說更加的簡(jiǎn)單方便,常常能夠節(jié)省我們大量的時(shí)間陷遮。

BeautifulSoup也是有官方中文文檔的:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html


2. 安裝

BeautifulSoup的安裝也是非常方便的,pip安裝即可届案。

pip install beautifulsoup4

3. 簡(jiǎn)單例子

以下是一段HTML代碼笼蛛,作為例子被多次用到,這是 愛麗絲夢(mèng)游仙境 中的一段內(nèi)容净当。

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a  class="sister" id="link1">Elsie</a>,
<a  class="sister" id="link2">Lacie</a> and
<a  class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

我們獲取的網(wǎng)頁數(shù)據(jù)通常會(huì)像上面這樣是完全的字符串格式翎迁,所以我們首先需要使用BeautifulSoup來解析這段字符串栋猖。然后會(huì)獲得一個(gè)BeautifulSoup對(duì)象,通過這個(gè)對(duì)象我們就可以進(jìn)行一系列操作了汪榔。

In [1]: from bs4 import BeautifulSoup

In [2]: soup = BeautifulSoup(html_doc)

In [3]: soup.title
Out[3]: <title>The Dormouse's story</title>

In [4]: soup.title.name
Out[4]: 'title'

In [5]: soup.title.string
Out[5]: "The Dormouse's story"

In [6]: soup.title.parent.name
Out[6]: 'head'

In [7]: soup.p
Out[7]: <p class="title"><b>The Dormouse's story</b></p>

In [8]: soup.p['class']
Out[8]: ['title']

In [9]: soup.a
Out[9]: <a class="sister"  id="link1">Elsie</a>

In [10]: soup.find_all('a')
Out[10]:
[<a class="sister"  id="link1">Elsie</a>,
 <a class="sister"  id="link2">Lacie</a>,
 <a class="sister"  id="link3">Tillie</a>]

In [11]: soup.find(id="link3")
Out[11]: <a class="sister"  id="link3">Tillie</a>

可以看到蒲拉,相對(duì)于正則來說,操作簡(jiǎn)單了不止一個(gè)量級(jí)痴腌。


4. 指定解析器

在上面的例子中雌团,我們可以看到在查找數(shù)據(jù)之前,是有一個(gè)解析網(wǎng)頁的過程的:

soup = BeautifulSoup(html_doc)

BeautifulSoup會(huì)自動(dòng)的在系統(tǒng)中選定一個(gè)可用的解析器士聪,以下是主要的幾種解析器:

解析器 使用方法 優(yōu)勢(shì) 劣勢(shì)
Python標(biāo)準(zhǔn)庫 BeautifulSoup(markup, "html.parser") Python的內(nèi)置標(biāo)準(zhǔn)庫執(zhí)行速度適中文檔容錯(cuò)能力強(qiáng) Python 2.7.3 or 3.2.2)前 的版本中文檔容錯(cuò)能力差
lxml HTML 解析器 BeautifulSoup(markup, "lxml") 速度快文檔容錯(cuò)能力強(qiáng) 需要安裝C語言庫
lxml XML 解析器 BeautifulSoup(markup, ["lxml", "xml"])``BeautifulSoup(markup, "xml") 速度快唯一支持XML的解析器 需要安裝C語言庫
html5lib BeautifulSoup(markup, "html5lib") 最好的容錯(cuò)性以瀏覽器的方式解析文檔生成HTML5格式的文檔 速度慢不依賴外部擴(kuò)展

由于這個(gè)解析的過程在大規(guī)模的爬取中是會(huì)影響到整個(gè)爬蟲系統(tǒng)的速度的锦援,所以推薦使用的是lxml,速度會(huì)快很多剥悟,而lxml需要單獨(dú)安裝:

pip install lxml

安裝成功后灵寺,在解析網(wǎng)頁的時(shí)候曼库,指定為lxml即可。

soup = BeautifulSoup(html_doc, 'lxml')

注意:如果一段HTML或XML文檔格式不正確的話,那么在不同的解析器中返回的結(jié)果可能是不一樣的替久,所以要指定某一個(gè)解析器凉泄。


5.節(jié)點(diǎn)對(duì)象

BeautifulSoup將復(fù)雜HTML文檔轉(zhuǎn)換成一個(gè)復(fù)雜的樹形結(jié)構(gòu)躏尉,每個(gè)節(jié)點(diǎn)都是Python對(duì)象蚯根,所有對(duì)象可以歸納為4種:TagNavigableString胀糜,BeautifulSoup颅拦,Comment

5.1. tag

tag就是標(biāo)簽的意思教藻,tag還有許多的方法和屬性距帅。

>>> soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
>>> tag = soup.b
>>> type(tag)
<class 'bs4.element.Tag'>
  • name

    每一個(gè)tag對(duì)象都有name屬性,為標(biāo)簽的名字括堤。

    >>> tag.name
    'b'
    
  • Attributes

    在HTML中碌秸,tag可能有多個(gè)屬性,所以tag屬性的取值跟字典相同悄窃。

    >>> tag['class']
    'boldest'
    

    如果某個(gè)tag屬性有多個(gè)值讥电,那么返回的則是列表格式。

    >>> soup = BeautifulSoup('<p class="body strikeout"></p>')
    >>> soup.p['class']
    ["body", "strikeout"]
    
  • get_text()

    通過get_text()方法我們可以獲取某個(gè)tag下所有的文本內(nèi)容轧抗。

    In [1]: soup.body.get_text()
    Out[1]: "The Dormouse's story\nOnce upon a time there were three little sisters; and their names were\nElsie,\nLacie and\nTillie;\nand they lived at the bottom of a well.\n...\n"
    

5.2. NavigableString

NavigableString的意思是可以遍歷的字符串恩敌,一般被標(biāo)簽包裹在其中的的文本就是NavigableString格式。

In [1]: soup = BeautifulSoup('<p>No longer bold</p>')

In [2]: soup.p.string
Out[2]: 'No longer bold'

In [3]: type(soup.p.string)
Out[3]: bs4.element.NavigableString

5.3. BeautifulSoup

BeautifulSoup對(duì)象就是解析網(wǎng)頁獲得的對(duì)象横媚。

5.4. Comment

Comment指的是在網(wǎng)頁中的注釋以及特殊字符串纠炮。


6. Tag與遍歷文檔樹

tag對(duì)象可以說是BeautifulSoup中最為重要的對(duì)象,通過BeautifulSoup來提取數(shù)據(jù)基本都圍繞著這個(gè)對(duì)象來進(jìn)行操作灯蝴。

首先恢口,一個(gè)節(jié)點(diǎn)中是可以包含多個(gè)子節(jié)點(diǎn)和多個(gè)字符串的。例如html節(jié)點(diǎn)中包含著headbody節(jié)點(diǎn)穷躁。所以BeautifulSoup就可以將一個(gè)HTML的網(wǎng)頁用這樣一層層嵌套的節(jié)點(diǎn)來進(jìn)行表示耕肩。

以上方的愛麗絲夢(mèng)游仙境為例:

6.1. contents和children

通過contents可以獲取某個(gè)節(jié)點(diǎn)所有的子節(jié)點(diǎn),包括里面的NavigableString對(duì)象折砸。獲取的子節(jié)點(diǎn)是列表格式看疗。

In [1]: soup.head.contents
Out[1]: [<title>The Dormouse's story</title>]

而通過children同樣的是獲取某個(gè)節(jié)點(diǎn)的所有子節(jié)點(diǎn),但是返回的是一個(gè)迭代器睦授,這種方式會(huì)比列表格式更加的節(jié)省內(nèi)存两芳。

In [1]: tags = soup.head.children

In [2]: tags
Out[2]: <list_iterator at 0x110f76940>

In [3]: for tag in tags:
            print(tag)

<title>The Dormouse's story</title>

6.2. descendants

上面的contentschildren獲取的是某個(gè)節(jié)點(diǎn)的直接子節(jié)點(diǎn),而無法獲得子孫節(jié)點(diǎn)去枷。通過descendants可以獲得所有子孫節(jié)點(diǎn)怖辆,返回的結(jié)果跟children一樣是复,需要迭代或者轉(zhuǎn)類型使用。

In [1]: len(list(soup.body.descendants))
Out[1]: 19

In [2]: len(list(soup.body.children))
Out[2]: 6

6.3. string和strings

我們常常會(huì)遇到需要獲取某個(gè)節(jié)點(diǎn)中的文本值的情況竖螃,如果這個(gè)節(jié)點(diǎn)中只有一個(gè)字符串淑廊,那么使用string可以正常將其取出。

In [1]: soup.title.string
Out[1]: "The Dormouse's story"

而如果這個(gè)節(jié)點(diǎn)中有多個(gè)字符串的時(shí)候特咆,BeautifulSoup就無法確定要取出哪個(gè)字符串了季惩,這時(shí)候需要使用strings

In [1]: list(soup.body.strings)
Out[1]:
["The Dormouse's story",
 '\n',
 'Once upon a time there were three little sisters; and their names were\n',
 'Elsie',
 ',\n',
 'Lacie',
 ' and\n',
 'Tillie',
 ';\nand they lived at the bottom of a well.',
 '\n',
 '...',
 '\n']

而使用stripped_strings可以將全是空白的行去掉腻格。

In [1]: list(soup.body.stripped_strings)
Out[1]:
["The Dormouse's story",
 'Once upon a time there were three little sisters; and their names were',
 'Elsie',
 ',',
 'Lacie',
 'and',
 'Tillie',
 ';\nand they lived at the bottom of a well.',
 '...']

6.4. 父節(jié)點(diǎn)parent和parents

有時(shí)我們也需要去獲取某個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)画拾,也就是包裹著當(dāng)前節(jié)點(diǎn)的節(jié)點(diǎn)。

In [1]: soup.b.parent
Out[1]: <p class="title"><b>The Dormouse's story</b></p>

而使用parents則可以獲得當(dāng)前節(jié)點(diǎn)遞歸到頂層的所有父輩元素菜职。

In [1]: [i.name for i in soup.b.parents]
Out[1]: ['p', 'body', 'html', '[document]']

6.5. 兄弟節(jié)點(diǎn)

兄弟節(jié)點(diǎn)指的就是父節(jié)點(diǎn)相同的節(jié)點(diǎn)青抛。

  • next_sibling 和 previous_sibling

    兄弟節(jié)點(diǎn)選取的方法與當(dāng)前節(jié)點(diǎn)的位置有關(guān),next_sibling獲取的是當(dāng)前節(jié)點(diǎn)的下一個(gè)兄弟節(jié)點(diǎn)酬核,previous_sibling獲取的是當(dāng)前節(jié)點(diǎn)的上一個(gè)兄弟節(jié)點(diǎn)蜜另。

    所以,兄弟節(jié)點(diǎn)中排第一個(gè)的節(jié)點(diǎn)是沒有previous_sibling的嫡意,最后一個(gè)節(jié)點(diǎn)是沒有next_sibling的举瑰。

    In [51]: soup.head.next_sibling
    Out[51]: '\n'
    
    In [52]: soup.head.previos_sibling
    
    In [59]: soup.body.previous_sibling
    Out[59]: '\n'
    
  • next_siblings 和 previous_siblings

    相對(duì)應(yīng)的,next_siblings獲取的是下方所有的兄弟節(jié)點(diǎn)鹅很,previous_siblings獲取的上方所有的兄弟節(jié)點(diǎn)嘶居。

    In [47]: [i.name for i in soup.head.next_siblings]
    Out[47]: [None, 'body']
    
    In [48]: [i.name for i in soup.body.next_siblings]
    Out[48]: []
    
    In [49]: [i.name for i in soup.body.previous_siblings]
    Out[49]: [None, 'head']
    

7. find_all()

上方這種直接通過屬性來進(jìn)行訪問屬性的方法,很多時(shí)候只能適用于比較簡(jiǎn)單的一些場(chǎng)景促煮,所以BeautifulSoup還提供了搜索整個(gè)文檔樹的方法find_all()邮屁。

需要注意的是,find_all()方法基本所有節(jié)點(diǎn)對(duì)象都能調(diào)用菠齿。

7.1. 通過name搜索

就像以下演示的佑吝,find_all()可以直接查找出整個(gè)文檔樹中所有的b標(biāo)簽,并返回列表绳匀。

>>> soup.find_all('b')
[<b>The Dormouse's story</b>]

而如果傳入的是一個(gè)列表芋忿,則會(huì)與列表中任意一個(gè)元素進(jìn)行匹配〖部茫可以看到戈钢,搜索的結(jié)果包含了所有的a標(biāo)簽和b標(biāo)簽。

>>> soup.find_all(["a", "b"])
[<b>The Dormouse's story</b>,
 <a class="sister"  id="link1">Elsie</a>,
 <a class="sister"  id="link2">Lacie</a>,
 <a class="sister"  id="link3">Tillie</a>]

7.2. 通過屬性搜索

我們?cè)谒阉鞯臅r(shí)候一般只有標(biāo)簽名是不夠的是尔,因?yàn)榭赡芡臉?biāo)簽很多殉了,那么這時(shí)候我們就要通過標(biāo)簽的屬性來進(jìn)行搜索。

這時(shí)候我們可以通過傳遞給attrs一個(gè)字典參數(shù)來搜索屬性拟枚。

In [1]: soup.find_all(attrs={'class': 'sister'})
Out[1]:
[<a class="sister"  id="link1">Elsie</a>,
 <a class="sister"  id="link2">Lacie</a>,
 <a class="sister"  id="link3">Tillie</a>]

可以看到找出了所有class屬性為sister的標(biāo)簽薪铜。

7.3. 通過文本搜索

find_all()方法中众弓,還可以根據(jù)文本內(nèi)容來進(jìn)行搜索。

>>> soup.find_all(text="Elsie")
[u'Elsie']

>>> soup.find_all(text=["Tillie", "Elsie", "Lacie"])
[u'Elsie', u'Lacie', u'Tillie']

可見找到的都是字符串對(duì)象隔箍,如果想要找到包含某個(gè)文本的tag谓娃,加上tag名即可。

>>> soup.find_all("a", text="Elsie")
[<a  class="sister" id="link1">Elsie</a>]

7.4. 限制查找范圍為子節(jié)點(diǎn)

find_all()方法會(huì)默認(rèn)的去所有的子孫節(jié)點(diǎn)中搜索蜒滩,而如果將recursive參數(shù)設(shè)置為False滨达,則可以將搜索范圍限制在直接子節(jié)點(diǎn)中。

>>> soup.html.find_all("title")
[<title>The Dormouse's story</title>]

>>> soup.html.find_all("title", recursive=False)
[]

7.5.通過正則表達(dá)式來篩選查找結(jié)果

BeautifulSoup中帮掉,也是可以與re模塊進(jìn)行相互配合的弦悉,將re.compile編譯的對(duì)象傳入find_all()方法窒典,即可通過正則來進(jìn)行搜索蟆炊。

In [1]: import re

In [2]: tags = soup.find_all(re.compile("^b"))

In [3]: [i.name for i in tags]
Out[3]: ['body', 'b']

可以看到,找到了標(biāo)簽名是以'b'開頭的兩個(gè)標(biāo)簽瀑志。

同樣的涩搓,也能夠以正則來篩選tag的屬性。

In [1]: soup.find_all(attrs={'class': re.compile("si")})
Out[1]:
[<a class="sister"  id="link1">Elsie</a>,
 <a class="sister"  id="link2">Lacie</a>,
 <a class="sister"  id="link3">Tillie</a>]

8. CSS選擇器

BeautifulSoup中劈猪,同樣也支持使用CSS選擇器來進(jìn)行搜索昧甘。使用select(),在其中傳入字符串參數(shù)战得,就可以使用CSS選擇器的語法來找到tag充边。

>>> soup.select("title")
[<title>The Dormouse's story</title>]

>>> soup.select("p > a")
[<a class="sister"  id="link1">Elsie</a>,
 <a class="sister"   id="link2">Lacie</a>,
 <a class="sister"  id="link3">Tillie</a>]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市常侦,隨后出現(xiàn)的幾起案子浇冰,更是在濱河造成了極大的恐慌,老刑警劉巖聋亡,帶你破解...
    沈念sama閱讀 212,185評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肘习,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坡倔,警方通過查閱死者的電腦和手機(jī)漂佩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罪塔,“玉大人投蝉,你說我怎么就攤上這事≌骺埃” “怎么了瘩缆?”我有些...
    開封第一講書人閱讀 157,684評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)请契。 經(jīng)常有香客問我咳榜,道長(zhǎng)夏醉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,564評(píng)論 1 284
  • 正文 為了忘掉前任涌韩,我火速辦了婚禮畔柔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘臣樱。我一直安慰自己靶擦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,681評(píng)論 6 386
  • 文/花漫 我一把揭開白布雇毫。 她就那樣靜靜地躺著玄捕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棚放。 梳的紋絲不亂的頭發(fā)上枚粘,一...
    開封第一講書人閱讀 49,874評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音飘蚯,去河邊找鬼馍迄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛局骤,可吹牛的內(nèi)容都是我干的攀圈。 我是一名探鬼主播,決...
    沈念sama閱讀 39,025評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼峦甩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赘来!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凯傲,我...
    開封第一講書人閱讀 37,761評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤犬辰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后泣洞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忧风,經(jīng)...
    沈念sama閱讀 44,217評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,545評(píng)論 2 327
  • 正文 我和宋清朗相戀三年球凰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狮腿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,694評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呕诉,死狀恐怖缘厢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甩挫,我是刑警寧澤贴硫,帶...
    沈念sama閱讀 34,351評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響英遭,放射性物質(zhì)發(fā)生泄漏间护。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,988評(píng)論 3 315
  • 文/蒙蒙 一挖诸、第九天 我趴在偏房一處隱蔽的房頂上張望汁尺。 院中可真熱鬧,春花似錦多律、人聲如沸痴突。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辽装。三九已至,卻和暖如春相味,著一層夾襖步出監(jiān)牢的瞬間拾积,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工攻走, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殷勘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,427評(píng)論 2 360
  • 正文 我出身青樓昔搂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親输拇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子摘符,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,580評(píng)論 2 349