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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章