爬蟲基礎(chǔ):Beautiful Soup

Beautiful Soup 是一個可以從HTML和XML文件中提取數(shù)據(jù)的Python。它可以實現(xiàn)文檔的增刪改查操作结窘,我們側(cè)重點是它的查詢操作。

安裝 Beautiful Soup

你可以根據(jù)自己的系統(tǒng)選擇下面的安裝代碼進行安裝操作:

$ apt-get install Python-bs4
$ easy_install beautifulsoup4
$ pip install beautifulsoup4

安裝解析器

Beautiful Soup支持Python標(biāo)準(zhǔn)庫中的HTML解析器,還支持一些第三方的解析器喳逛,其中一個是 lxml邮偎。根據(jù)操作系統(tǒng)不同管跺,你可以選擇下面方法來安裝 lxml:

$ apt-get install Python-lxml
$ easy_install lxml
$ pip install lxml

另外一個可供選擇的解析器是純Python實現(xiàn)的 html5lib,解析方式和瀏覽器相同禾进,你可以選擇下面的方法來安裝 html5lib:

$ apt-get install Python-html5lib
$ easy_install html5lib
$ pip install html5lib

幾種解析器的優(yōu)缺點

解析器 使用方法 優(yōu)勢 劣勢
Python標(biāo)準(zhǔn)庫 BeautifulSoup(markup, "html.parser") 執(zhí)行速度適中豁跑,文檔容錯能力強 Python2.7.3 or 3.2.3前的版本文檔容錯能力差
lxml HTML 解析器 BeautifulSoup(markup, "lxml") 速度快,文檔容錯能力強 需要安裝C語言庫
lxml XML 解析器 BeautifulSoup(markup, "xml") 速度快泻云,唯一支持XML的解析器 需要安裝C語言庫
html5lib BeautifulSoup(markup, "html5lib")
最高的容錯性艇拍,以瀏覽器的方式解析文檔,生成HTML5格式的文檔 數(shù)獨慢宠纯,不依賴外部擴展

推薦使用 lxml 作為解析器卸夕,因為效率更高。在Python2.7.3之前的版本和Python3.2.3之前的版本婆瓜,必須安裝 lxml 或 html5lib快集,因為Python版本的標(biāo)準(zhǔn)庫中的內(nèi)置的 HTML 解析方法不夠穩(wěn)定。

如何使用

將一段文檔傳入 Beautiful Soup 的構(gòu)造方法,就能得到一個文檔的對象碍讨,可以傳入一段文字或一個文件句柄治力。

from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"))    // 文件句柄
soup = BeautifulSoup("<html>data</html>")   // 文檔

文檔被轉(zhuǎn)換成 Unicode,并且HTML的實例都被轉(zhuǎn)換成 Unicode 編碼勃黍,然后宵统,Beautiful Soup選擇最合適的解析器來解析這段文檔,如果手動指定解析器覆获,那么 Beautiful Soup 會選擇指定的解析器來解析文檔马澈。

對象的種類

Beautiful Soup 將復(fù)雜 HTML 文檔轉(zhuǎn)換成一個復(fù)雜的樹形結(jié)構(gòu),每隔節(jié)點都是Python對象弄息,多有對象可以歸納為4種:Tag痊班,NavigableStringBeautifulSoup摹量,Comment涤伐。

Tag對象

Tag 對象與 XML 或 HTML 原生文檔中的 tag 相同。Tag 有很多方法和屬性缨称,其中最重要的屬性:name 和 attributes凝果。

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
tag = soup.b            // 一個 Tag 對象
print(type(tag))        // <class 'bs4.element.Tag'>
print(tag.name)         // tag都有自己的名字:b
print(tag.attrs)        // tag的屬性字典:{'class': ['boldest']}
print(tag['class'])     // 屬性字典中的'class'值:['boldest']

// tag 的屬性可以被添加,刪除或修改睦尽,屬性操作和字典一樣
tag['class'] = 'myClsss'
print(tag)              // <b class="myClsss">Extremely bold</b>
tag['id'] = 'custemId'
print(tag)              // <b class="myClsss" id="custemId">Extremely bold</b>

NavigableString對象

字符串常被包含在 tag 內(nèi)器净。Beautiful Soup 用 NavigableString 類來包裝 tag 中的字符串。

print(type(tag.string))     // <class 'bs4.element.NavigableString'>
print(tag.string)           // Extremely bold

字符串不支持 .contents 或 .string 屬性或 find() 方法当凡。

BeautifulSoup對象

該對象表示的是一個文檔的全部內(nèi)容山害,大部分的適合可以把它當(dāng)作 Tag 對象。因為 BeautifulSoup 對象并不是真正的 HTML 或 XML 的tag沿量,所以它沒有name和attributes屬性浪慌。但有時查看它時,.name 屬性還是可以的朴则。

print(type(soup))   // <class 'bs4.BeautifulSoup'>
print(soup.name)    // [document]

Comment對象

Tag , NavigableString , BeautifulSoup 幾乎覆蓋了html和xml中的所有內(nèi)容,但是還有一些特殊對象.容易讓人擔(dān)心的內(nèi)容是文檔的注釋部分眷射。

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup,'lxml')
comment = soup.b.string
print(type(comment))    // <class 'bs4.element.Comment'>

Comment 對象是一個特殊類型的 NavigableString 對象:

print(comment)  // Hey, buddy. Want to buy a used parser?

但是當(dāng)它出現(xiàn)在HTML文檔中時, Comment 對象會使用特殊的格式輸出:

print(soup.b.prettify())
<b>
 <!--Hey, buddy. Want to buy a used parser?-->
</b>

搜索文檔樹

說完 Beautiful Soup 中的四種對象,接下來介紹一下如何搜索內(nèi)容佛掖。 Beautiful Soup 提供了很多搜索方法妖碉,這里著重介紹其中的兩個:find()find_all() ,其他的方法和參數(shù)以及用法都類似芥被。

過濾器

過濾器作為搜索文檔的參數(shù)欧宜,貫穿整個搜索的 API。過濾器可以被用在 tag 中的 name 中拴魄,節(jié)點的屬性中冗茸,字符串中或者他們的混合中席镀。過濾器可以是字符串、正則表達(dá)式夏漱、列表豪诲、True值甚至是方法。

  1. 字符串
soup.find_all('b')  // 查找文檔中所有的<b>標(biāo)簽

  1. 正則表達(dá)式
import re
// 查找 b 開頭的標(biāo)簽
for tag in soup.find_all(re.compile("^b")):
    print(tag.name)

// body
// b

  1. 列表
markup = '''
    <b>Hey, buddy. Want to buy a used parser?</b>
    <p class="title">The Dormouse's story</p>
    <a>Once upon a time there were three little sisters</a>
'''
soup = BeautifulSoup(markup,'lxml')

for tag in soup.find_all(['a','p']):
    print(tag)

// <p class="title">The Dormouse's story</p>
// <a>Once upon a time there were three little sisters</a>

  1. True

True 可以匹配任何值

for tag in soup.find_all(True):
    print(tag.name)

// html
// body
// b
// p
// a

  1. 方法

如果沒有合適的過濾器挂绰,那么還可以定一個方法屎篱,方法只接收一個參數(shù),如果這個方法返回 True 表示當(dāng)前元素匹配并且被找到葵蒂,如果不是則返回 False

def has_class(tag):
    return tag.has_attr('class')

for tag in soup.find_all(has_class):
    print(tag)

# <p class="title">The Dormouse's story</p>

find_all()

find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

  • name 參數(shù)

name 參數(shù)可以查找所有名字為 name 的tag交播,字符串對象會被自動忽略掉。其中践付,name 參數(shù)的值可以是任一類型的過濾器:字符串秦士、正則表達(dá)式、列表永高、Ture或是方法隧土。

markup = '''
    <b>Hey, buddy. Want to buy a used parser?</b>
    <p class="title">The Dormouse's story</p>
    <a>Once upon a time there were three little sisters</a>
'''
soup = BeautifulSoup(markup,'lxml')
print(soup.find_all('p'))   
# [<p class="title">The Dormouse's story</p>]

  • keyword 參數(shù)

如果一個指定名字的參數(shù)不是搜索內(nèi)置的參數(shù)名稱,搜索時會把該參數(shù)當(dāng)作指定名字tag的屬性來搜索命爬。

markup = '''
    <b id='link'>Hey, buddy. Want to buy a used parser?</b>
    <p class="title">The Dormouse's story</p>
    <a>Once upon a time there were three little sisters</a>
    <a class="sister"  id="link1">three</a>
    <div data-foo="value">foo!</div>
'''
soup = BeautifulSoup(markup,'lxml')

print(soup.find_all(id='link')) 
# [<b id="link">Hey, buddy. Want to buy a used parser?</b>]

# class是Python的保留關(guān)鍵字曹傀,這里是 class_
print(soup.find_all(class_='title'))
# [<p class="title">The Dormouse's story</p>]

# 可以使用多個指定名字的參數(shù)來過濾多個tag的屬性
import re
print(soup.find_all(id='link1', href=re.compile('example')))
# [<a class="sister"  id="link1">three</a>]

  • attrs 參數(shù)

有些tag屬性在搜索中不能使用,如HTML5中的 data-* 屬性遇骑,我們可以使用 attrs 參數(shù)定一個字典參數(shù)來搜索包含特殊屬性的tag卖毁。

print(soup.find_all(attrs={'data-foo':'value'}))
# [<div data-foo="value">foo!</div>]

  • text參數(shù)

通過 text 參數(shù)可以搜索文檔中的字符串內(nèi)容揖曾,和 name 參數(shù)一樣落萎,接受參數(shù)有:字符串、正則表達(dá)式炭剪、列表练链、True。

print(soup.find_all(text='three'))
# ['three']
import re
print(soup.find_all(text=re.compile('^H.*?$')))
# ['Hey, buddy. Want to buy a used parser?']

  • recursive 參數(shù)

Beautiful Soup 會檢索當(dāng)前tag的所有子孫節(jié)點奴拦,如果你只想搜索tag的直接子節(jié)點媒鼓,可以使用參數(shù) recursive = False。

markup = '''
<html>
 <head>
  <title>
   The Dormouse's story
  </title>
 </head>
</html>
'''
soup = BeautifulSoup(markup, 'lxml')

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

print(soup.html.find_all('title', recursive=False))
# []

  • limit 參數(shù)
markup = '''
    <b id='link'>Hey, buddy. Want to buy a used parser?</b>
    <p class="title">The Dormouse's story</p>
    <a>Once upon a time there were three little sisters</a>
    <a class="sister"  id="link1">three</a>
    <div data-foo="value">foo!</div>
'''
soup = BeautifulSoup(markup,'lxml')

import re
for str in soup.find_all(text=re.compile('o')):
    print(str)
# Hey, buddy. Want to buy a used parser?
# The Dormouse's story
# Once upon a time there were three little sisters
# foo!

for str in soup.find_all(text=re.compile('o'), limit=1):
    print(str)

# Hey, buddy. Want to buy a used parser?

  • 說明

find_all() 是 Beautiful Soup中最常用的搜索方法错妖,該庫還提供了它的簡寫方法绿鸣。BeautifulSoup 對象和 tag 對象都可以被當(dāng)作一個方法來使用,這個方法的執(zhí)行結(jié)果與調(diào)用這個對象的 find_all() 方法相同暂氯,下面代碼是等價的:

soup.find_all("a")
soup("a")

soup.title.find_all(text=True)
soup.title(text=True)

find()

find_all()方法將返回文檔中符合條件的所有tag潮模,盡管有時候我們只想得到一個結(jié)果。比如文檔中只有一個<body>標(biāo)簽痴施,那么使用 find_all() 方法來查找顯然不太合適擎厢,如果使用 limit=1 參數(shù)不如使用 find()方法究流。

soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]

soup.find('title')
# <title>The Dormouse's story</title>

唯一的區(qū)別是 find_all() 方法的返回結(jié)果是值包含一個元素的列表,而 find() 方法直接返回結(jié)果动遭。
find_all() 方法沒有找到目標(biāo)是返回空列表芬探,find() 方法找不到目標(biāo)時,返回 None 厘惦。

soup.head.title 是 tag的名字 方法的簡寫偷仿。這個簡寫的原理就是多次調(diào)用當(dāng)前tag的 find() 方法:

soup.head.title
# <title>The Dormouse's story</title>

soup.find("head").find("title")
# <title>The Dormouse's story</title>

輸出

prettify() 方法將Beautiful Soup的文檔樹格式化后以Unicode編碼輸出,每個XML/HTML標(biāo)簽都獨占一行绵估。

markup = '<a >I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.prettify())
# <html>
#  <body>
#   <a >
#    I linked to
#    <i>
#     example.com
#    </i>
#   </a>
#  </body>
# </html>


Beautiful Soup 功能強大炎疆,除了上面提到的 搜索文檔樹 功能,更有遍歷文檔樹国裳,修改文檔樹等等功能形入。更多詳細(xì)介紹請查閱官方文檔 4.2.0

作者:繁夢三千
鏈接:http://www.reibang.com/p/35b3616c74c1
來源:簡書

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缝左,一起剝皮案震驚了整個濱河市亿遂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渺杉,老刑警劉巖蛇数,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異是越,居然都是意外死亡耳舅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門倚评,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浦徊,“玉大人,你說我怎么就攤上這事天梧】裕” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵呢岗,是天一觀的道長冕香。 經(jīng)常有香客問我,道長后豫,這世上最難降的妖魔是什么悉尾? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮挫酿,結(jié)果婚禮上构眯,老公的妹妹穿的比我還像新娘。我一直安慰自己饭豹,他們只是感情好鸵赖,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布务漩。 她就那樣靜靜地躺著,像睡著了一般它褪。 火紅的嫁衣襯著肌膚如雪饵骨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天茫打,我揣著相機與錄音居触,去河邊找鬼。 笑死老赤,一個胖子當(dāng)著我的面吹牛轮洋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抬旺,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼弊予,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了开财?” 一聲冷哼從身側(cè)響起汉柒,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎责鳍,沒想到半個月后碾褂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡历葛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年正塌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恤溶。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡乓诽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宏娄,到底是詐尸還是另有隱情问裕,我是刑警寧澤逮壁,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布孵坚,位于F島的核電站,受9級特大地震影響窥淆,放射性物質(zhì)發(fā)生泄漏卖宠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一忧饭、第九天 我趴在偏房一處隱蔽的房頂上張望扛伍。 院中可真熱鬧,春花似錦词裤、人聲如沸刺洒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逆航。三九已至鼎文,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間因俐,已是汗流浹背拇惋。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抹剩,地道東北人撑帖。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像澳眷,于是被迫代替她去往敵國和親胡嘿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容