1. 程式人生 > >Python 處理HTML/XML——Beautiful Soup4

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")

        #####未完待續####