Python 處理HTML/XML——Beautiful Soup4
Beautiful Soup 是一個可以從HTML或XML檔案中提取資料的Python庫.
本文為Beautiful Soup屬性方法總結,更多例子請查閱官方文件
$ pip install beautifulsoup4
#debian或Ubuntu下可以
$ apt-get install Python-bs4
載入BeautifulSoup庫
>>> from bs4 import BeautifulSoup >>> a = Beautiful('<html><head></head><body></body></html>') #建立BeautifulSoup物件
文件解析器
Beautiful Soup會自動選擇一個解析器來解析文件,也可以通過引數來制定使用哪種解析器
第一次使用BeautifulSoup建構函式如果沒有指定解析器,會出現提示,無需擔心,後續呼叫就不會出現該提示
BeartifulSoup第一個引數為被解析的文件字串或檔案控制代碼,第二個引數來表示選擇什麼解析器,
如果第二個引數為空,則根據當前系統安裝的庫自動選擇解析器,lxml,html5lib,python標準庫
>>> BeautifulSoup(markup, "html.parser") >>> BeautifulSoup(markup, "lxml") >>> BeautifulSoup(markup, "lxml-xml") #使用lxml庫解析xml文件 >>> BeautifulSoup(markup, "xml") >>> BeautifulSoup(markup, "html5lib")
推薦使用lxml作為解析器,速度快,容錯能力強,效率高
安裝lxml
$ pip install lxml
安裝html5lib
$ pip install html5lib
Beautiful Soup將複雜HTML文件轉換成一個複雜的樹形結構,每個節點都是Python物件,所有物件可以歸納為4種: Tag , NavigableString , BeautifulSoup , Comment .
BeautifulSoup類
BeautifulSoup物件表示一個文件的全部內容,大部分時候,可以把它當作 Tag 物件,它支援遍歷文件樹和搜尋文件樹中描述的大部分的方法.
它的name屬性返回字串'[document]'
BeautifulSoup沒有標籤屬性,所以無法使用字典索引或attrs屬性
NavigableSoup類
字串通常被包含在tag物件內,而這些字串使用NavigableSoup類用來包裝
標籤與標籤之間的字元串同樣會被作為文件的節點,包裝為NavigableSoup物件
NabigableSoup物件支援遍歷和搜尋的大部分屬性方法 但是不能包含其他物件,所以不支援.contents,.strings或find()等針對子節點的方法
在tag中包含的NavigableSoup物件可以通過replace_with()方法來替換字串
可以通過unicode()函式將NaviableSoup物件轉會成Unicode字串,通常在BeautifulSoup之外使用時進行轉換
Comment類
Comment物件是一個特殊型別的NavigableString物件,包含文件中的註釋
Beautiful Soup還定義了一些其他型別CData , ProcessingInstruction , Declaration , Doctype用於處理XML文件,這些類也都是NavigableString類的子類
Tag物件
tag.name 獲取標籤名字,可以更改該屬性
tag[attr_name] 獲取該標籤的attr_name屬性,可以更改和新增attr_name屬性的值
tag.attrs 獲取該標籤的所有屬性,返回一個字典
如果屬性為多值屬性,則返回一個列表,但是如果該屬性在html中沒有被定義為多值屬性,則返回字串,
對於xml,tag不包含多值屬性,返回的都是字串
Tag物件屬性遍歷文件樹
子節點
tag.tag_name 使用tag的名稱tag_name屬性來獲取相應第一個匹配的後代節點tag物件
>>> a = bs('<html id="df"><body id="df"> <div> <p>body元素後代節點第一個p</p> </div> <p>body子節點第一個</p></body></html>')
>>> a.body.p
<p>body元素後代節點第一個p</p>
tag.contents 將tag的子節點以列表的方式輸出
>>> a = bs('<html id="df"><body id="df"> <p>sdfd</p><div><p></p></div></body></html>')
>>> a.body.contents
['',<p>sdfd</p>, <div><p></p></div>]
注意空白符同樣會作為子節點輸出tag.children 生成器屬性,可以對tag的子節點進行迴圈
tag.descendants 生成器屬性,可以對所有tag的後代節點進行遞迴迴圈
tag.string 如果tag只有一個NavigableString型別子節點,則string屬性可以得到子節點
如果tag僅有一個子節點,那麼輸出和該子節點的輸出一樣
如果tag包含多個子節點,tag就不確定.string方法由誰來呼叫
tag.strings 如果tag中包含多個字串,可以使用.strings來迴圈獲取
tag.stripped_strings 輸出的字串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多餘空白內容
段首和段末的空白會被刪除
父節點
tag.parent 獲取tag元素的父節點
tag.parents 生成器屬性,遞迴獲取所有父輩節點,最後返回None
兄弟節點
tag.next_sibling 查詢下一個同級節點,同級最後一個節點沒有該屬性
tag.previous_sibling 查詢上一個同級節點,同級第一個節點沒有該屬性
tag.netx_siblings 生成器屬性可以對當前節點的兄弟節點迭代輸出
tag.previous_siblings
實際兄弟節點也許並不是下一個標籤元素,而是文字節點NavigableString節點或者註釋Comment物件
HTML解析器會根據標籤的開啟關閉和字串來解析文件
<html><head><title>The Dormouse's story</title></head>
<p class="title"><b>The Dormouse's story</b></p>
針對上面的欄位HTML解析器會開啟html標籤,開啟head標籤,開啟title標籤,新增字串,關閉title標籤,關閉head標籤,開啟p標籤...
tag.next_element 根據解析過程返回下一個被解析的物件(字串或tag),結果可能於.next_sibling相同,但通常不一樣
next_element屬性會先返回後代節點,然後返回兄弟節點
>>> a = BeautifulSoup('<html id="df"><body id="df"><div><p>xixi</p></div><p>sdfd</p></body></html>')
>>> a.div.next_sibling
<p>sdfd</p>
>>> a.div.next_element
<p>xixi</p>
tag.previous_element 根據解析過程返回上一個被解析物件
tag.next_elements 生成器屬性
tag.previous_elements
Tag物件方法搜尋文件樹
搜尋過濾器,用於搜尋方法的引數來過濾搜尋結果
字串
正則表示式 如果傳入正則表示式作為引數,Beautiful Soup會通過正則表示式的match()來匹配內容.
列表 將與列表中任一元素匹配的內容返回
True 可以匹配任何值
方法 定義一個方法,方法只接受一個元素引數,如果這個方法返回 True 表示當前元素匹配並且被找到,如果不是則反回 False
>>> def has_class(tag):
... return tag.has_attr('class')
...
>>> a = bs('<html id="df"><body id="df"><div><p class="1">xixi</p></div><p>sdfd</p></body></html>')
>>> a.find(has_class)
<p class="1">xixi</p>
tag.find_all(name, attrs, recursive, text ,limit, **kwargs)
搜尋當前tag的所有tag子節點,並判斷是否符合過濾器的條件
name 引數可以查詢所有名字為name的tag,字串物件會被自動忽略掉
text 引數用於搜尋文件中字串內容,可以和其他引數混合使用來過濾
soup.find_all("a", text="Elsie")
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
recursive 引數設為False,表示只搜尋tag的直接子節點
**kwargs 關鍵字引數
如果指定的名字引數不是搜尋內建的引數名,搜尋時會把該引數作為name引數的屬性,引數的值可以是任何型別的過濾器
對於標籤的class屬性,可以使用class_引數,因為class為Python的保留字
對於多值屬性可以使用字串過濾器來全值完全匹配
>>> css_soup.find_all("p", class_="body strikeout")
attrs 引數定義一個字典引數作為name引數的屬性搜尋,可以搜尋包含特殊屬性的tag
limit 引數會限制返回的數量,否則如果文件很大的話搜尋會很慢
tag.find (name, attrs, recursive, text , **kwargs)
等同於.find_all(...,limit=1),不過帶有limit=1引數的find_all()方法返回的值是一個包含一個元素的列表,而find()方法返回單個tag物件
當find_all()方法找不到目標時返回空列表,find()方法找不到目標,返回None
tag.find_parents(...)
tag.find_parent(...)
用來搜尋當前節點的父輩節點,這兩個方法實際是對.parents屬性的迭代
tag.find_next_siblings(...)
tag.find_next_sibling(...)
通過.next_siblings屬性對當前tag之後的所有兄弟節點進行迭代
tag.find_previous_siblings(...)
tag.find_previous_sibling(...)
通過.previous_siblings屬性對當前tag之前的所有兄弟節點進行迭代
tag.find_all_next(...)
tag.find_next(...)
通過.next_elements屬性對當前tag之後的tag和字串進行迭代
tag.find_all_previous(...)
tag.find_previous(...)
通過.previous_elements屬性對當前節點前面的tag和字串進行迭代
CSS選擇器
.select()
傳入字串引數可以使用CSS選擇器的語法找到對應tag
>>> soup.select("body a")
>>> soup.select("head > title")
>>> soup.select(".sister")
>>> soup.select("#link1")
>>> soup.select('a[href$="tillie"]')
解析部分文件
如果僅僅因為想要查詢文件中的<a>標籤而將整片文件進行解析,實在是浪費記憶體和時間
可以使用SoupStrainer類定義某段內容,這樣搜尋文件時,就不必先解析全部文件了
建立一個SoupStrainer物件並作為parse_only引數傳遞給BeautifulSoup()構造方法
SoupStrainer類接受與搜尋方法相同的引數
SoupStrainer(name, attrs,recursive,text,**kwargs)
>>> from bs4 import SoupStrainer
>>> only_a_tags = SoupStrainer("a")
>>> only_tags_with_id_link2 = SoupStrainer(id="link2")
>>> def is_short_string(string):
... return len(string) < 10
...
>>> only_short_strings = SoupStrainer(text=is_short_string)
>>> BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags)
修改文件樹
tag.name 更改name屬性可以重新命名tag
tag[attr_name] 新增刪除更改tag的屬性值
tag.string 給string屬性賦值相當於替代使用當前內容替代原來內容
beautifulsoup.new_string(string, class_name)
BeautifulSoup物件方法,建立一個NavigableString物件
可傳入NavigableString的任何子類作為第二個引數,構建相應物件,比如Comment
beautifulsoup.new_tag(name, **kwargs)
BeautifulSoup物件方法,建立一個Tag物件,第一個引數為標籤的名字,其他關鍵字引數為標籤屬性
tag.append() 向tag中新增內容
tag.insert() 與列表的insert方法類似,在指定位置插入內容
>>> markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
>>> soup = BeautifulSoup(markup)
>>>soup.a.tag.insert(1, "but did not endorse ")
# <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
tag.insert_before() 在當前tag或文字節點前插入元素
tag.insert_after() 在當前tag或文字節點後插入元素
>>> soup = BeautifulSoup("<b>stop</b>")
>>> tag = soup.new_tag("i")
>>> tag.string = "Don't"
>>> soup.b.string.insert_before(tag)
>>> soup.b
<b><i>Don't</i>stop</b>
tag.clear() 移除當前tag中的內容
tag.extract() 將當前tag移除文件樹,並作為方法結果返回,返回的依然是Tag物件
tag.decompose() 將當前節點移除文件樹並完全銷燬
tag.replace_with() 移除文件樹中的某段內容,並用新tag或文字節點替代它
tag.wrap() 對指定的tag元素進行包裝,並返回包裝後的結果
>>> soup = BeautifulSoup("<p>I wish I was bold.</p>")
>>> soup.p.string.wrap(soup.new_tag("b"))
<b>I wish I was bold.</b>
tag.unwrap() 移除tag的當前tag.name標籤,該方法常被用來進行標記的解包
>>> bs = BeautifulSoup('<a href="http://example.com/">I linked to <i>example.com<i></i></i></a>')
>>> bs.i.unwrap()
<i></i>
>>> bs
<html><body><a href="http://example.com/">I linked to example.com<i></i></a></body></html>
輸出
tag.prettify() 將文件樹格式化後以Unicode編碼輸出,每個XML/HTML標籤都獨佔一行
>>> a = Beautiful('<html id="df">\n<body id="df"><div><p class="1">xixi</p></div><p>sdfd</p></body>\n</html>')
>>> a.prettify()
'<html id="df">\n <body id="df">\n <div>\n <p class="1">\n xixi\n </p>\n </div>\n <p>\n sdfd\n </p>\n </body>\n</html>'
如果只想得到結果字串,不重視格式,那麼可以對一個 BeautifulSoup 物件或 Tag 物件使用Python的 unicode() 或 str() 函式
>>> a
<html id="df">
<body id="df"><div><p class="1">xixi</p></div><p>sdfd</p></body>
</html>
tag .get_text() 獲取到tag中包含的所有文字內容包括子孫tag中的內容,並將結果作為Unicode字串返回
>>> a = bs('<html id="df"><body id="df"><div><p class="1">\nxixi</p></div><p>\nsdfd</p></body></html>')
>>> a.get_text()
'\nxixi\nsdfd'
>>> a.get_text('|') #使用引數來指定文字之間的分隔符
'\nxixi|\nsdfd'
>>> a.get_text('|',strip=True) #使用strip引數去除文字內容中的前後空白符
'xixi|sdfd'
編碼
任何HTML/XML文件都有自己的編碼方式,但是使用Beautiful Soup解析後文檔轉換為Unicode編碼.orginal_encoding屬性記錄了自動識別的編碼
BeautifulSoup構造方法時可傳入from_encoding引數指定編碼方式
soup = BeautifulSoup(markup, from_encoding="iso-8859-8")
#####未完待續####