1. Beautiful Soup簡(jiǎn)介
Beautiful Soup是將數(shù)據(jù)從HTML和XML文件中解析出來(lái)的一個(gè)python庫(kù),它能夠提供一種符合習(xí)慣的方法去遍歷搜索和修改解析樹,這將大大減少爬蟲程序的運(yùn)行時(shí)間。
Beautiful Soup自動(dòng)將輸入文檔轉(zhuǎn)換為Unicode編碼,輸出文檔轉(zhuǎn)換為utf-8編碼。你不需要考慮編碼方式,除非文檔沒有指定一個(gè)編碼方式,這時(shí),Beautiful Soup就不能自動(dòng)識(shí)別編碼方式了。然后,你僅僅需要說(shuō)明一下原始編碼方式就可以了。
Beautiful Soup已成為和lxml、html6lib一樣出色的python解釋器,為用戶靈活地提供不同的解析策略或強(qiáng)勁的速度。
2. Beautiful Soup安裝
利用pip可以迅速安裝,目前最新版本為BeautifulSoup4。
1 $ pip install beautifulsoup4
安裝后,import一下bs4就可以使用了。
1 from bs4 import BeautifulSoup
3. 創(chuàng)建Beautiful Soup對(duì)象
我們利用以下測(cè)試文件來(lái)進(jìn)行之后的總結(jié)。
1 html = """ 2 <html><head><title>The Dormouse's story</title></head> 3 <body> 4 <p class="title" name="dromouse"><b>The Dormouse's story</b></p> 5 <p class="story">Once upon a time there were three little sisters; and their names were 6 <a class="sister" id="link1"><!-- Elsie --></a>, 7 <a class="sister" id="link2">Lacie</a> and 8 <a class="sister" id="link3">Tillie</a>; 9 and they lived at the bottom of a well.</p>10 <p class="story">...</p>11 """import之后,創(chuàng)建一個(gè)BeautifulSoup對(duì)象如下參數(shù)可以是一個(gè)抓取到的unicode格式的網(wǎng)頁(yè)html,也可以是一個(gè)已經(jīng)保存到本地的html文件test.html。
1 soup = BeautifulSoup(html)2 soup = BeautifulSoup(open('test.html'))創(chuàng)建后查看是否創(chuàng)建成功。注意:有時(shí)需要在后面加上encode('utf-8')來(lái)進(jìn)行編碼才能將soup對(duì)象正確顯示出來(lái)。
1 print soup.prettify()
4. 四種Beautiful Soup對(duì)象類型
Beautiful Soup一共有四大對(duì)象種類,包括Tag,NavigableString,BeautifulSoup和Comment。
4.1 Tag
Tag對(duì)象
Tag就是html文件中的標(biāo)簽以及標(biāo)簽之間的內(nèi)容,例如以下就是一個(gè)Tag。
1 <title>The Dormouse's story</title>可以這樣得到title這個(gè)Tag,第二行為運(yùn)行結(jié)果。
1 print soup.title2 #<title>The Dormouse's story</title>注意:如果得到的是'bs4.element.Tag'類型的對(duì)象可以繼續(xù)進(jìn)行后續(xù)的.操作,即能進(jìn)行soup對(duì)象所能進(jìn)行的操作,所以需要確保一個(gè)對(duì)象是'bs4.element.Tag'類型后再進(jìn)行后續(xù)對(duì)其的操作,例如后面將介紹的.find方法是Tag對(duì)象才擁有的。
1 print type(soup.title)2 #<class 'bs4.element.Tag'>Tag方法
.name
Tag對(duì)象的.name方法得到的是該Tag的標(biāo)簽本身名稱。
1 print soup.title.name2 #title.attrs
Tag對(duì)象的.attrs將得到標(biāo)簽中所有屬性的字典。
1 print soup.p.attrs2 #{'class': ['title'], 'name': 'dromouse'}可以對(duì)Tag對(duì)象進(jìn)行字典可以進(jìn)行的操作,例如修改,刪除,讀取等。
1 print soup.p['class']#讀?。ǚ椒ㄒ唬?/span> 2 #['title'] 3 print soup.p.get('class')#讀?。ǚ椒ǘ?/span> 4 #['title'] 5 6 soup.p['class']="newClass"#修改 7 print soup.p 8 #<p class="newClass" name="dromouse"><b>The Dormouse's story</b></p> 9 10 del soup.p['class']#刪除11 print soup.p12 #<p name="dromouse"><b>The Dormouse's story</b></p>4.2 NavigableString
標(biāo)簽內(nèi)部的內(nèi)容由.string方法可以得到,且這些內(nèi)容為'bs4.element.NavigableString'類型的對(duì)象。
1 print soup.p.string2 #The Dormouse's story3 4 print type(soup.p.string)5 #<class 'bs4.element.NavigableString'>4.3 BeautifulSoup
BeautifulSoup 對(duì)象表示的是一個(gè)文檔的全部?jī)?nèi)容.大部分時(shí)候,可以把它當(dāng)作 Tag 對(duì)象,是一個(gè)特殊的 Tag。
1 print type(soup.name)2 #<type 'unicode'>3 print soup.name 4 # [document]5 print soup.attrs 6 #{} 空字典4.4 Comment
前三種類型幾乎涵蓋了在HTML或者XML中所有的內(nèi)容,但是Comment類型是需要關(guān)心的一種,和CData,ProcessingInstruction,Declaration,Doctype一樣,它是NavigableString類型的一個(gè)子類,通過(guò)以下代碼可以簡(jiǎn)單了解它的功能。
1 markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"#標(biāo)簽中內(nèi)容為注釋 2 soup = BeautifulSoup(markup) 3 comment = soup.b.string 4 type(comment) 5 # <class 'bs4.element.Comment'> 6 comment 7 # u'Hey, buddy. Want to buy a used parser' 8 print(soup.b.prettify()) 9 # <b>10 # <!--Hey, buddy. Want to buy a used parser?-->11 # </b>注意:標(biāo)簽里的內(nèi)容實(shí)際上是注釋,但是如果我們利用 .string 來(lái)輸出它的內(nèi)容,我們發(fā)現(xiàn)它已經(jīng)把注釋符號(hào)去掉了,所以這可能會(huì)給我們帶來(lái)不必要的麻煩,需要在使用或者進(jìn)行一些操作 之前進(jìn)行類型判斷。
1 if type(soup.b.string)==bs4.element.Comment:2 ...
5. 樹的遍歷
5.1 子孫節(jié)點(diǎn)
.content
Tag對(duì)象的.content方法可以得到其子節(jié)點(diǎn)的一個(gè)列表表示。
1 print soup.head.contents 2 #[<title>The Dormouse's story</title>]當(dāng)然,既然是列表可以用索引直接得到某一項(xiàng)。
1 print soup.head.contents[0]2 #<title>The Dormouse's story</title>.children
Tag對(duì)象的.children方法得到一個(gè)其子節(jié)點(diǎn)的迭代器,可以遍歷之獲取其中的元素。
1 for child in soup.body.children:2 print child.descendants
與.content和.children只得到直接子節(jié)點(diǎn)不同,.descendants能對(duì)所有子孫節(jié)點(diǎn)迭代循環(huán),將標(biāo)簽層層剝離得到所有子節(jié)點(diǎn),同樣通過(guò)遍歷的方法得到每個(gè)子孫節(jié)點(diǎn)。
1 for child in soup.descendants:2 print child5.2 父親節(jié)點(diǎn)
.parent
Tag對(duì)象的.parent方法能得到其直接父節(jié)點(diǎn)。
.parents
用.parents屬性可以遞歸得到元素的所有父節(jié)點(diǎn)。
1 content = soup.head.title.string2 for parent in content.parents:3 print parent.name4 #title5 #head6 #html7 #[document]5.3 兄弟節(jié)點(diǎn)
.next_sibling和.next_siblings
.next_sibling得到Tag對(duì)象平級(jí)的下一個(gè)節(jié)點(diǎn),如果不存在則返回None。.next_siblings得到Tag對(duì)象平級(jí)的下面所有兄弟節(jié)點(diǎn)。
.previous_sibling和.previous_siblings
.previous_sibling得到Tag對(duì)象平級(jí)的上一個(gè)節(jié)點(diǎn),如果不存在則返回None。.next_siblings得到Tag對(duì)象平級(jí)的上面所有兄弟節(jié)點(diǎn)。
注意:由于在HTML文檔中的空白和換行也被視作是一個(gè)節(jié)點(diǎn),所以可能得到的兄弟節(jié)點(diǎn)(或者子節(jié)點(diǎn)父節(jié)點(diǎn))會(huì)是空白類型或者字符串類型而不是Tag,所以在進(jìn)行下一步操作時(shí)一定要先用type函數(shù)進(jìn)行類型的判斷。
5.4 前后節(jié)點(diǎn)
.next_element和.next_elements
與 .next_sibling和.next_siblings 不同,它并不是針對(duì)于兄弟節(jié)點(diǎn),而是在所有節(jié)點(diǎn),不分層次得到下一個(gè)節(jié)點(diǎn)和所有的后續(xù)節(jié)點(diǎn)。.next_elements的結(jié)果通過(guò)遍歷訪問。
.previous_element和.previous_elements
這兩個(gè)方法將不分層次得到上一個(gè)節(jié)點(diǎn)和所有之前的節(jié)點(diǎn)。.previous_elements的結(jié)果通過(guò)遍歷訪問。
5.4 節(jié)點(diǎn)內(nèi)容
.string
如果一個(gè)標(biāo)簽里面沒有標(biāo)簽了,那么 .string 就會(huì)返回標(biāo)簽里面的內(nèi)容。如果標(biāo)簽里面只有唯一的一個(gè)標(biāo)簽了,那么 .string 也會(huì)返回最里面的內(nèi)容。
1 print soup.head.string2 #The Dormouse's story3 print soup.title.string4 #The Dormouse's story而如果Tag包含了多個(gè)子節(jié)點(diǎn),Tag就無(wú)法確定.string 方法應(yīng)該調(diào)用哪個(gè)子節(jié)點(diǎn)的內(nèi)容,輸出結(jié)果是 None。
.strings和.stripped_strings
當(dāng)一個(gè)Tag對(duì)象有多個(gè)子節(jié)點(diǎn)時(shí),可以用.strings方法再通過(guò)遍歷獲得所有子節(jié)點(diǎn)的內(nèi)容。
1 for string in soup.strings: 2 print(repr(string)) 3 # u"The Dormouse's story" 4 # u'\n\n' 5 # u"The Dormouse's story" 6 # u'\n\n' 7 # u'Once upon a time there were three little sisters; and their names were\n' 8 # u'Elsie' 9 # u',\n'10 # u'Lacie'11 # u' and\n'12 # u'Tillie'13 # u';\nand they lived at the bottom of a well.'14 # u'\n\n'15 # u'...'16 # u'\n'用.stripped_strings方法可以得到過(guò)濾掉空格和空行的內(nèi)容。
.get_text()
如果你僅僅想要得到文檔或者標(biāo)簽的文本部分,可以使用.get_text()方法,它能以一個(gè)單一的一個(gè)Unicode串的形式返回文檔中或者Tag對(duì)象下的所有文本。
1 markup = '<a >\nI linked to <i>example.com</i>\n</a>'2 soup = BeautifulSoup(markup)3 4 soup.get_text()5 #u'\nI linked to example.com\n'6 soup.i.get_text()7 #u'example.com'你可以指定一個(gè)字符串來(lái)連接文本的位。
1 soup.get_text("|")2 #u'\nI linked to |example.com|\n'進(jìn)一步,通過(guò)strip去除掉文本每個(gè)位的頭尾空白。
1 soup.get_text("|", strip=True)2 #u'I linked to|example.com'用列表推導(dǎo)式以及.stripped_strings方法羅列出文本內(nèi)容。
1 [text for text in soup.stripped_strings]2 #[u'I linked to', u'example.com']
6. 樹的搜索
6.1 find_all(name, attrs, recursive, string, limit, **kwargs)
該方法將搜索當(dāng)前Tag對(duì)象的所有子節(jié)點(diǎn),并且按照過(guò)濾條件得到篩選后對(duì)象的列表。
name參數(shù)
1)傳字符串
最簡(jiǎn)單的方法是傳入標(biāo)簽名的字符串,可以得到所有以該字符串為標(biāo)簽名的一個(gè)列表。
1 print soup.find_all('a')2 #[<a class="sister" id="link1"><!-- Elsie --></a>, <a class="sister" id="link2">Lacie</a>, <a class="sister" id="link3">Tillie</a>]2)傳正則表達(dá)式
可以通過(guò)傳正則表達(dá)式得到符合表達(dá)式規(guī)則的Tag對(duì)象。
1 import re2 for tag in soup.find_all(re.compile("^b")):3 print(tag.name)4 # body5 # b3)傳列表
可以傳入一個(gè)字符串的列表,將匹配列表中標(biāo)簽的Tag全部返回。
1 soup.find_all(["a", "b"])2 # [<b>The Dormouse's story</b>,3 # <a class="sister" id="link1">Elsie</a>,4 # <a class="sister" id="link2">Lacie</a>,5 # <a class="sister" id="link3">Tillie</a>]4)傳True
True參數(shù)將匹配文檔中所有的節(jié)點(diǎn),但是不包括文本字符串。
1 for tag in soup.find_all(True): 2 print(tag.name) 3 # html 4 # head 5 # title 6 # body 7 # p 8 # b 9 # p10 # a11 # a12 # a13 # p5)傳入函數(shù)
可以根據(jù)函數(shù)返回值的True/False來(lái)得到匹配的節(jié)點(diǎn)。
1 def has_class_but_no_id(tag):2 return tag.has_attr('class') and not tag.has_attr('id')3 4 soup.find_all(has_class_but_no_id)5 # [<p class="title"><b>The Dormouse's story</b></p>,6 # <p class="story">Once upon a time there were...</p>,7 # <p class="story">...</p>]關(guān)鍵字參數(shù)
可以傳入一個(gè)或者多個(gè)關(guān)鍵字,BeautifulSoup會(huì)搜索當(dāng)前Tag下的每一個(gè)節(jié)點(diǎn)的該關(guān)鍵字及其對(duì)應(yīng)的值。
1 soup.find_all(href=re.compile("elsie"), id='link1')2 # [<a class="sister" id="link1">three</a>]特殊:如果希望用class及其值作為過(guò)濾條件,由于class是python的關(guān)鍵字,所以需要作如下處理。
1 soup.find_all("a", class_="sister")2 # [<a class="sister" id="link1">Elsie</a>,3 # <a class="sister" id="link2">Lacie</a>,4 # <a class="sister" id="link3">Tillie</a>]另外,有些tag屬性在搜索不能使用,比如HTML5中的 data-* 屬性,可以這樣來(lái)進(jìn)行過(guò)濾。
1 data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')2 data_soup.find_all(attrs={"data-foo": "value"})3 # [<div data-foo="value">foo!</div>]text參數(shù)
可以在文檔中搜索一些字符串內(nèi)容,與name參數(shù)的可選值一樣,可以傳字符串,列表,正則表達(dá)式和True。
1 soup.find_all(text="Elsie")2 # [u'Elsie']3 4 soup.find_all(text=["Tillie", "Elsie", "Lacie"])5 # [u'Elsie', u'Lacie', u'Tillie']6 7 soup.find_all(text=re.compile("Dormouse"))8 [u"The Dormouse's story", u"The Dormouse's story"]limit參數(shù)
可用該參數(shù)限制返回的節(jié)點(diǎn)數(shù)目,例子中本身有3個(gè)符合的節(jié)點(diǎn),僅輸出兩個(gè)。
1 soup.find_all("a", limit=2)2 # [<a class="sister" id="link1">Elsie</a>,3 # <a class="sister" id="link2">Lacie</a>]recursive參數(shù)
將該參數(shù)設(shè)為False可限制只搜索當(dāng)前Tag的直接子節(jié)點(diǎn),可以節(jié)省很多搜索時(shí)間。
1 soup.html.find_all("title")2 # [<title>The Dormouse's story</title>]3 soup.html.find_all("title", recursive=False)4 # []6.2. find( name , attrs , recursive , text , **kwargs )
它與 find_all() 方法唯一的區(qū)別是 find_all() 方法的返回結(jié)果是值包含一個(gè)元素的列表,而 find() 方法直接返回結(jié)果
6.3. find_parents()和find_parent()
find_all() 和 find() 只搜索當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn),孫子節(jié)點(diǎn)等. find_parents() 和 find_parent() 用來(lái)搜索當(dāng)前節(jié)點(diǎn)的父輩節(jié)點(diǎn),搜索方法與普通tag的搜索方法相同,搜索文檔搜索文檔包含的內(nèi)容
6.4. find_next_siblings()和find_next_sibling()
這2個(gè)方法通過(guò) .next_siblings 屬性對(duì)當(dāng) tag 的所有后面解析的兄弟 tag 節(jié)點(diǎn)進(jìn)行迭代, find_next_siblings() 方法返回所有符合條件的后面的兄弟節(jié)點(diǎn),find_next_sibling() 只返回符合條件的后面的第一個(gè)tag節(jié)點(diǎn)
6.5. find_previous_siblings()和find_previous_sibling()
這2個(gè)方法通過(guò) .previous_siblings 屬性對(duì)當(dāng)前 tag 的前面解析的兄弟 tag 節(jié)點(diǎn)進(jìn)行迭代, find_previous_siblings()方法返回所有符合條件的前面的兄弟節(jié)點(diǎn), find_previous_sibling() 方法返回第一個(gè)符合條件的前面的兄弟節(jié)點(diǎn)。
6.6. find_all_next()和find_next()
這2個(gè)方法通過(guò) .next_elements 屬性對(duì)當(dāng)前 tag 的之后的 tag 和字符串進(jìn)行迭代, find_all_next() 方法返回所有符合條件的節(jié)點(diǎn), find_next() 方法返回第一個(gè)符合條件的節(jié)點(diǎn)
6.7. find_all_previous()和find_previous()
這2個(gè)方法通過(guò) .previous_elements 屬性對(duì)當(dāng)前節(jié)點(diǎn)前面的 tag 和字符串進(jìn)行迭代, find_all_previous() 方法返回所有符合條件的節(jié)點(diǎn), find_previous()方法返回第一個(gè)符合條件的節(jié)點(diǎn)
聯(lián)系客服