爬蟲入門系列(四):HTML文本解析庫BeautifulSoup

系列文章的第3篇介紹了網(wǎng)絡(luò)請求庫神器 Requests 婿斥,請求把數(shù)據(jù)返回來之后就要提取目標(biāo)數(shù)據(jù),不同的網(wǎng)站返回的內(nèi)容通常有多種不同的格式,一種是 json 格式,這類數(shù)據(jù)對開發(fā)者來說最友好另绩。另一種 XML 格式的儒陨,還有一種最常見格式的是 HTML 文檔,今天就來講講如何從 HTML 中提取出感興趣的數(shù)據(jù)

自己寫個 HTML 解析器來解析嗎笋籽?還是用正則表達(dá)式蹦漠?這些都不是最好的辦法,好在车海,Python 社區(qū)在這方便早就有了很成熟的方案笛园,BeautifulSoup 就是這一類問題的克星,它專注于 HTML 文檔操作侍芝,名字來源于 Lewis Carroll 的一首同名詩歌研铆。

BeautifulSoup 是一個用于解析 HTML 文檔的 Python 庫,通過 BeautifulSoup竭贩,你只需要用很少的代碼就可以提取出 HTML 中任何感興趣的內(nèi)容蚜印,此外莺禁,它還有一定的 HTML 容錯能力留量,對于一個格式不完整的HTML 文檔,它也可以正確處理哟冬。

安裝 BeautifulSoup

pip install beautifulsoup4

BeautifulSoup3 被官方放棄維護楼熄,你要下載最新的版本 BeautifulSoup4。

HTML 標(biāo)簽

學(xué)習(xí) BeautifulSoup4 前有必要先對 HTML 文檔有一個基本認(rèn)識浩峡,如下代碼可岂,HTML 是一個樹形組織結(jié)構(gòu)。

<html>  
    <head>
     <title>hello, world</title>
    </head>
    <body>
        <h1>BeautifulSoup</h1>
        <p>如何使用BeautifulSoup</p>
    <body>
</html>  
  • 它由很多標(biāo)簽(Tag)組成翰灾,比如 html缕粹、head、title等等都是標(biāo)簽
  • 一個標(biāo)簽對構(gòu)成一個節(jié)點纸淮,比如 <html>...</html> 是一個根節(jié)點
  • 節(jié)點之間存在某種關(guān)系平斩,比如 h1 和 p 互為鄰居,他們是相鄰的兄弟(sibling)節(jié)點
  • h1 是 body 的直接子(children)節(jié)點咽块,還是 html 的子孫(descendants)節(jié)點
  • body 是 p 的父(parent)節(jié)點绘面,html 是 p 的祖輩(parents)節(jié)點
  • 嵌套在標(biāo)簽之間的字符串是該節(jié)點下的一個特殊子節(jié)點,比如 “hello, world” 也是一個節(jié)點侈沪,只不過沒名字揭璃。

使用 BeautifulSoup

構(gòu)建一個 BeautifulSoup 對象需要兩個參數(shù),第一個參數(shù)是將要解析的 HTML 文本字符串亭罪,第二個參數(shù)告訴 BeautifulSoup 使用哪個解析器來解析 HTML瘦馍。

解析器負(fù)責(zé)把 HTML 解析成相關(guān)的對象,而 BeautifulSoup 負(fù)責(zé)操作數(shù)據(jù)(增刪改查)应役】鄱眨”html.parser” 是Python內(nèi)置的解析器哲银,”lxml” 則是一個基于c語言開發(fā)的解析器,它的執(zhí)行速度更快呻惕,不過它需要額外安裝

通過 BeautifulSoup 對象就可以定位到 HTML 中的任何一個標(biāo)簽節(jié)點荆责。

from bs4 import BeautifulSoup  
text = """
<html>  
    <head>
     <title >hello, world</title>
    </head>
    <body>
        <h1>BeautifulSoup</h1>
        <p class="bold">如何使用BeautifulSoup</p>
        <p class="big" id="key1"> 第二個p標(biāo)簽</p>
        <a >python</a>
    </body>
</html>  
"""
soup = BeautifulSoup(text, "html.parser")

# title 標(biāo)簽
>>> soup.title
<title>hello, world</title>

# p 標(biāo)簽
>>> soup.p
<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>

# p 標(biāo)簽的內(nèi)容
>>> soup.p.string
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'

BeatifulSoup 將 HTML 抽象成為 4 類主要的數(shù)據(jù)類型,分別是Tag , NavigableString , BeautifulSoup亚脆,Comment 做院。每個標(biāo)簽節(jié)點就是一個Tag對象,NavigableString 對象一般是包裹在Tag對象中的字符串濒持,BeautifulSoup 對象代表整個 HTML 文檔键耕。例如:

>>> type(soup)
<class 'bs4.BeautifulSoup'>
>>> type(soup.h1)
<class 'bs4.element.Tag'>
>>> type(soup.p.string)
<class 'bs4.element.NavigableString'>

Tag

每個 Tag 都有一個名字,它對應(yīng) HTML 的標(biāo)簽名稱柑营。

>>> soup.h1.name
u'h1'
>>> soup.p.name
u'p'

標(biāo)簽還可以有屬性屈雄,屬性的訪問方式和字典是類似的,它返回一個列表對象


>>> soup.p['class']
[u'bold']

NavigableString

獲取標(biāo)簽中的內(nèi)容官套,直接使用 .stirng 即可獲取酒奶,它是一個 NavigableString 對象,你可以顯式地將它轉(zhuǎn)換為 unicode 字符串奶赔。

>>> soup.p.string
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'
>>> type(soup.p.string)
<class 'bs4.element.NavigableString'>
>>> unicode_str = unicode(soup.p.string)
>>> unicode_str
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'

基本概念介紹完惋嚎,現(xiàn)在可以正式進入主題了,如何從 HTML 中找到我們關(guān)心的數(shù)據(jù)站刑?BeautifulSoup 提供了兩種方式另伍,一種是遍歷,另一種是搜索绞旅,通常兩者結(jié)合來完成查找任務(wù)摆尝。

遍歷文檔樹

遍歷文檔樹,顧名思義因悲,就是是從根節(jié)點 html 標(biāo)簽開始遍歷堕汞,直到找到目標(biāo)元素為止,遍歷的一個缺陷是囤捻,如果你要找的內(nèi)容在文檔的末尾臼朗,那么它要遍歷整個文檔才能找到它,速度上就慢了蝎土。因此還需要配合第二種方法视哑。

通過遍歷文檔樹的方式獲取標(biāo)簽節(jié)點可以直接通過 .標(biāo)簽名的方式獲取,例如:

獲取 body 標(biāo)簽:

>>> soup.body
<body>\n<h1>BeautifulSoup</h1>\n<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>\n</body>

獲取 p 標(biāo)簽

>>> soup.body.p
<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>

獲取 p 標(biāo)簽的內(nèi)容

>>> soup.body.p.string
\u5982\u4f55\u4f7f\u7528BeautifulSoup

前面說了誊涯,內(nèi)容也是一個節(jié)點挡毅,這里就可以用 .string 的方式得到。遍歷文檔樹的另一個缺點是只能獲取到與之匹配的第一個子節(jié)點暴构,例如跪呈,如果有兩個相鄰的 p 標(biāo)簽時段磨,第二個標(biāo)簽就沒法通過 .p 的方式獲取,這是需要借用 next_sibling 屬性獲取相鄰且在后面的節(jié)點耗绿。此外苹支,還有很多不怎么常用的屬性,比如:.contents 獲取所有子節(jié)點误阻,.parent 獲取父節(jié)點债蜜,更多的參考請查看官方文檔

搜索文檔樹

搜索文檔樹是通過指定標(biāo)簽名來搜索元素究反,另外還可以通過指定標(biāo)簽的屬性值來精確定位某個節(jié)點元素寻定,最常用的兩個方法就是 find 和 find_all。這兩個方法在 BeatifulSoup 和 Tag 對象上都可以被調(diào)用精耐。

find_all()

find_all( name , attrs , recursive , text , **kwargs )

find_all 的返回值是一個 Tag 組成的列表狼速,方法調(diào)用非常靈活,所有的參數(shù)都是可選的卦停。

第一個參數(shù) name 是標(biāo)簽節(jié)點的名字向胡。

# 找到所有標(biāo)簽名為title的節(jié)點
>>> soup.find_all("title")
[<title>hello, world</title>]
>>> soup.find_all("p")
[<p class="bold">\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup</p>, 
<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]

第二個參數(shù)是標(biāo)簽的class屬性值

# 找到所有class屬性為big的p標(biāo)簽
>>> soup.find_all("p", "big")
[<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]

等效于

>>> soup.find_all("p", class_="big")
[<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]

因為 class 是 Python 關(guān)鍵字,所以這里指定為 class_沫浆。

kwargs 是標(biāo)簽的屬性名值對捷枯,例如:查找有href屬性值為 "http://foofish.net" 的標(biāo)簽

>>> soup.find_all()
[<a >python</a>]

當(dāng)然滚秩,它還支持正則表達(dá)式

>>> import re
>>> soup.find_all(href=re.compile("^http"))
[<a >python</a>]

屬性除了可以是具體的值专执、正則表達(dá)式之外,它還可以是一個布爾值(True/Flase)郁油,表示有屬性或者沒有該屬性本股。

>>> soup.find_all(id="key1")
[<p class="big" id="key1"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]
>>> soup.find_all(id=True)
[<p class="big" id="key1"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]

遍歷和搜索相結(jié)合查找,先定位到 body 標(biāo)簽桐腌,縮小搜索范圍拄显,再從 body 中找 a 標(biāo)簽。

>>> body_tag = soup.body
>>> body_tag.find_all("a")
[<a >python</a>]

find()

find 方法跟 find_all 類似案站,唯一不同的地方是躬审,它返回的單個 Tag 對象而非列表,如果沒找到匹配的節(jié)點則返回 None蟆盐。如果匹配多個 Tag承边,只返回第0個。


>>> body_tag.find("a")
<a >python</a>
>>> body_tag.find("p")
<p class="bold">\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup</p>

get_text()

獲取標(biāo)簽里面內(nèi)容石挂,除了可以使用 .string 之外博助,還可以使用 get_text 方法,不同的地方在于前者返回的一個 NavigableString 對象痹愚,后者返回的是 unicode 類型的字符串富岳。

>>> p1 = body_tag.find('p').get_text()
>>> type(p1)
<type 'unicode'>
>>> p1
u'\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup'

>>> p2 = body_tag.find("p").string
>>> type(p2)
<class 'bs4.element.NavigableString'>
>>> p2
u'\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup'
>>>

實際場景中我們一般使用 get_text 方法獲取標(biāo)簽中的內(nèi)容蛔糯。

總結(jié)

BeatifulSoup 是一個用于操作 HTML 文檔的 Python 庫,初始化 BeatifulSoup 時窖式,需要指定 HTML 文檔字符串和具體的解析器蚁飒。BeatifulSoup 有3類常用的數(shù)據(jù)類型,分別是 Tag萝喘、NavigableString飒箭、和 BeautifulSoup。查找 HTML元素有兩種方式蜒灰,分別是遍歷文檔樹和搜索文檔樹弦蹂,通常快速獲取數(shù)據(jù)需要二者結(jié)合强窖。最后是一個實踐項目:用 Requests 和 BeatifulSoup 把廖雪峰的教程轉(zhuǎn)換成 PDF 電子書凸椿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翅溺,隨后出現(xiàn)的幾起案子脑漫,更是在濱河造成了極大的恐慌,老刑警劉巖咙崎,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件优幸,死亡現(xiàn)場離奇詭異,居然都是意外死亡褪猛,警方通過查閱死者的電腦和手機网杆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伊滋,“玉大人碳却,你說我怎么就攤上這事⌒ν” “怎么了昼浦?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長筒主。 經(jīng)常有香客問我关噪,道長,這世上最難降的妖魔是什么乌妙? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任使兔,我火速辦了婚禮,結(jié)果婚禮上冠胯,老公的妹妹穿的比我還像新娘火诸。我一直安慰自己,他們只是感情好荠察,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布置蜀。 她就那樣靜靜地躺著奈搜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盯荤。 梳的紋絲不亂的頭發(fā)上馋吗,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音秋秤,去河邊找鬼宏粤。 笑死,一個胖子當(dāng)著我的面吹牛灼卢,可吹牛的內(nèi)容都是我干的绍哎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼鞋真,長吁一口氣:“原來是場噩夢啊……” “哼崇堰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涩咖,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤海诲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后檩互,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體特幔,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年闸昨,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚯斯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡零院,死狀恐怖溉跃,靈堂內(nèi)的尸體忽然破棺而出村刨,到底是詐尸還是另有隱情告抄,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布嵌牺,位于F島的核電站打洼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逆粹。R本人自食惡果不足惜募疮,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望僻弹。 院中可真熱鬧阿浓,春花似錦、人聲如沸蹋绽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至退敦,卻和暖如春粘咖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侈百。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工瓮下, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钝域。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓讽坏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親例证。 傳聞我的和親對象是個殘疾皇子震缭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

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