Python : Beautiful Soup修改文档树
修改文檔樹
Beautiful Soup的強項是文檔樹的搜索,但同時也可以方便的修改文檔樹
修改tag的名稱和屬性
在 Attributes 的章節中已經介紹過這個功能,但是再看一遍也無妨. 重命名一個tag,改變屬性的值,添加或刪除屬性:
soup = BeautifulSoup(‘Extremely bold’)
tag = soup.b
tag.name = “blockquote”
tag[‘class’] = ‘verybold’
tag[‘id’] = 1
tag
Extremely bold
del tag[‘class’]
del tag[‘id’]
tag
Extremely bold
修改 .string
給tag的 .string 屬性賦值,就相當于用當前的內容替代了原來的內容:
markup = ‘I linked to example.com’
soup = BeautifulSoup(markup)
tag = soup.a
tag.string = “New link text.”
tag
New link text.
注意: 如果當前的tag包含了其它tag,那么給它的 .string 屬性賦值會覆蓋掉原有的所有內容包括子tag
append()
Tag.append() 方法想tag中添加內容,就好像Python的列表的 .append() 方法:
soup = BeautifulSoup(“Foo”)
soup.a.append(“Bar”)
soup
FooBar
soup.a.contents
[u’Foo’, u’Bar’]
BeautifulSoup.new_string() 和 .new_tag()
如果想添加一段文本內容到文檔中也沒問題,可以調用Python的 append() 方法或調用工廠方法 BeautifulSoup.new_string() :
soup = BeautifulSoup("")
tag = soup.b
tag.append(“Hello”)
new_string = soup.new_string(" there")
tag.append(new_string)
tag
Hello there.
tag.contents
[u’Hello’, u’ there’]
如果想要創建一段注釋,或 NavigableString 的任何子類,將子類作為 new_string() 方法的第二個參數傳入:
from bs4 import Comment
new_comment = soup.new_string(“Nice to see you.”, Comment)
tag.append(new_comment)
tag
Hello there
tag.contents
[u’Hello’, u’ there’, u’Nice to see you.’]
這是Beautiful Soup 4.2.1 中新增的方法
創建一個tag最好的方法是調用工廠方法 BeautifulSoup.new_tag() :
soup = BeautifulSoup("")
original_tag = soup.b
new_tag = soup.new_tag(“a”, href=“http://www.example.com”)
original_tag.append(new_tag)
original_tag
new_tag.string = “Link text.”
original_tag
Link text.
第一個參數作為tag的name,是必填,其它參數選填
insert()
Tag.insert() 方法與 Tag.append() 方法類似,區別是不會把新元素添加到父節點 .contents 屬性的最后,而是把元素插入到指定的位置.與Python列表總的 .insert() 方法的用法下同:
markup = ‘I linked to example.com’
soup = BeautifulSoup(markup)
tag = soup.a
tag.insert(1, "but did not endorse ")
tag
I linked to but did not endorse example.com
tag.contents
[u’I linked to ‘, u’but did not endorse’, example.com]
insert_before() 和 insert_after()
insert_before() 方法在當前tag或文本節點前插入內容:
soup = BeautifulSoup(“stop”)
tag = soup.new_tag(“i”)
tag.string = “Don’t”
soup.b.string.insert_before(tag)
soup.b
Don’tstop
insert_after() 方法在當前tag或文本節點后插入內容:
soup.b.i.insert_after(soup.new_string(" ever "))
soup.b
Don’t ever stop
soup.b.contents
[Don’t, u’ ever ‘, u’stop’]
clear()
Tag.clear() 方法移除當前tag的內容:
markup = ‘I linked to example.com’
soup = BeautifulSoup(markup)
tag = soup.a
tag.clear()
tag
extract()
PageElement.extract() 方法將當前tag移除文檔樹,并作為方法結果返回:
markup = ‘I linked to example.com’
soup = BeautifulSoup(markup)
a_tag = soup.a
i_tag = soup.i.extract()
a_tag
I linked to
i_tag
example.com
print(i_tag.parent)
None
這個方法實際上產生了2個文檔樹: 一個是用來解析原始文檔的 BeautifulSoup 對象,另一個是被移除并且返回的tag.被移除并返回的tag可以繼續調用 extract 方法:
my_string = i_tag.string.extract()
my_string
u’example.com’
print(my_string.parent)
None
i_tag
decompose()
Tag.decompose() 方法將當前節點移除文檔樹并完全銷毀:
markup = ‘I linked to example.com’
soup = BeautifulSoup(markup)
a_tag = soup.a
soup.i.decompose()
a_tag
I linked to
replace_with()
PageElement.replace_with() 方法移除文檔樹中的某段內容,并用新tag或文本節點替代它:
markup = ‘I linked to example.com’
soup = BeautifulSoup(markup)
a_tag = soup.a
new_tag = soup.new_tag(“b”)
new_tag.string = “example.net”
a_tag.i.replace_with(new_tag)
a_tag
I linked to example.net
replace_with() 方法返回被替代的tag或文本節點,可以用來瀏覽或添加到文檔樹其它地方
wrap()
PageElement.wrap() 方法可以對指定的tag元素進行包裝 [8] ,并返回包裝后的結果:
soup = BeautifulSoup(“
I wish I was bold.
”)soup.p.string.wrap(soup.new_tag(“b”))
I wish I was bold.
soup.p.wrap(soup.new_tag(“div”))
I wish I was bold.
該方法在 Beautiful Soup 4.0.5 中添加
unwrap()
Tag.unwrap() 方法與 wrap() 方法相反.將移除tag內的所有tag標簽,該方法常被用來進行標記的解包:
markup = ‘I linked to example.com’
soup = BeautifulSoup(markup)
a_tag = soup.a
a_tag.i.unwrap()
a_tag
I linked to example.com
與 replace_with() 方法相同, unwrap() 方法返回被移除的tag
輸出
格式化輸出
prettify() 方法將Beautiful Soup的文檔樹格式化后以Unicode編碼輸出,每個XML/HTML標簽都獨占一行
markup = ‘I linked to example.com’
soup = BeautifulSoup(markup)
soup.prettify()
‘\n \n \n \n \n…’
print(soup.prettify())
I linked to
example.com
BeautifulSoup 對象和它的tag節點都可以調用 prettify() 方法:
print(soup.a.prettify())
I linked to
example.com
壓縮輸出
如果只想得到結果字符串,不重視格式,那么可以對一個 BeautifulSoup 對象或 Tag 對象使用Python的 unicode() 或 str() 方法:
str(soup)
‘I linked to example.com’
unicode(soup.a)
u’I linked to example.com’
str() 方法返回UTF-8編碼的字符串,可以指定 編碼 的設置.
還可以調用 encode() 方法獲得字節碼或調用 decode() 方法獲得Unicode.
輸出格式
Beautiful Soup輸出是會將HTML中的特殊字符轉換成Unicode,比如“&lquot;”:
soup = BeautifulSoup("“Dammit!” he said.")
unicode(soup)
u’\u201cDammit!\u201d he said.’
如果將文檔轉換成字符串,Unicode編碼會被編碼成UTF-8.這樣就無法正確顯示HTML特殊字符了:
str(soup)
‘\xe2\x80\x9cDammit!\xe2\x80\x9d he said.’
get_text()
如果只想得到tag中包含的文本內容,那么可以嗲用 get_text() 方法,這個方法獲取到tag中包含的所有文版內容包括子孫tag中的內容,并將結果作為Unicode字符串返回:
markup = ‘\nI linked to example.com\n’
soup = BeautifulSoup(markup)
soup.get_text()
u’\nI linked to example.com\n’
soup.i.get_text()
u’example.com’
可以通過參數指定tag的文本內容的分隔符:
soup.get_text("|")
u’\nI linked to |example.com|\n’
還可以去除獲得文本內容的前后空白:
soup.get_text("|", strip=True)
u’I linked to|example.com’
或者使用 .stripped_strings 生成器,獲得文本列表后手動處理列表:
[text for text in soup.stripped_strings]
[u’I linked to’, u’example.com’]
指定文檔解析器
如果僅是想要解析HTML文檔,只要用文檔創建 BeautifulSoup 對象就可以了.Beautiful Soup會自動選擇一個解析器來解析文檔.但是還可以通過參數指定使用那種解析器來解析當前文檔.
BeautifulSoup 第一個參數應該是要被解析的文檔字符串或是文件句柄,第二個參數用來標識怎樣解析文檔.如果第二個參數為空,那么Beautiful Soup根據當前系統安裝的庫自動選擇解析器,解析器的優先數序: lxml, html5lib, Python標準庫.在下面兩種條件下解析器優先順序會變化:
要解析的文檔是什么類型: 目前支持, “html”, “xml”, 和 “html5”
指定使用哪種解析器: 目前支持, “lxml”, “html5lib”, 和 “html.parser”
安裝解析器 章節介紹了可以使用哪種解析器,以及如何安裝.
如果指定的解析器沒有安裝,Beautiful Soup會自動選擇其它方案.目前只有 lxml 解析器支持XML文檔的解析,在沒有安裝lxml庫的情況下,創建 beautifulsoup 對象時無論是否指定使用lxml,都無法得到解析后的對象
解析器之間的區別
Beautiful Soup為不同的解析器提供了相同的接口,但解析器本身時有區別的.同一篇文檔被不同的解析器解析后可能會生成不同結構的樹型文檔.區別最大的是HTML解析器和XML解析器,看下面片段被解析成HTML結構:
BeautifulSoup("")
因為空標簽不符合HTML標準,所以解析器把它解析成
同樣的文檔使用XML解析如下(解析XML需要安裝lxml庫).注意,空標簽依然被保留,并且文檔前添加了XML頭,而不是被包含在標簽內:
BeautifulSoup("", “xml”)
<?xml version="1.0" encoding="utf-8"?>
HTML解析器之間也有區別,如果被解析的HTML文檔是標準格式,那么解析器之間沒有任何差別,只是解析速度不同,結果都會返回正確的文檔樹.
但是如果被解析文檔不是標準格式,那么不同的解析器返回結果可能不同.下面例子中,使用lxml解析錯誤格式的文檔,結果
標簽被直接忽略掉了:BeautifulSoup("
", “lxml”)使用html5lib庫解析相同文檔會得到不同的結果:
BeautifulSoup("
", “html5lib”)html5lib庫沒有忽略掉
標簽,而是自動補全了標簽,還給文檔樹添加了標簽.使用pyhton內置庫解析結果如下:
BeautifulSoup("
", “html.parser”)與lxml [7] 庫類似的,Python內置庫忽略掉了
標簽,與html5lib庫不同的是標準庫沒有嘗試創建符合標準的文檔格式或將文檔片段包含在標簽內,與lxml不同的是標準庫甚至連標簽都沒有嘗試去添加.因為文檔片段“
”是錯誤格式,所以以上解析方式都能算作”正確”,html5lib庫使用的是HTML5的部分標準,所以最接近”正確”.不過所有解析器的結構都能夠被認為是”正常”的.不同的解析器可能影響代碼執行結果,如果在分發給別人的代碼中使用了 BeautifulSoup ,那么最好注明使用了哪種解析器,以減少不必要的麻煩.
編碼
任何HTML或XML文檔都有自己的編碼方式,比如ASCII 或 UTF-8,但是使用Beautiful Soup解析后,文檔都被轉換成了Unicode:
markup = “
Sacr\xc3\xa9 bleu!
”soup = BeautifulSoup(markup)
soup.h1
Sacré bleu!
soup.h1.string
u’Sacr\xe9 bleu!’
這不是魔術(但很神奇),Beautiful Soup用了 編碼自動檢測 子庫來識別當前文檔編碼并轉換成Unicode編碼. BeautifulSoup 對象的 .original_encoding 屬性記錄了自動識別編碼的結果:
soup.original_encoding
‘utf-8’
編碼自動檢測 功能大部分時候都能猜對編碼格式,但有時候也會出錯.有時候即使猜測正確,也是在逐個字節的遍歷整個文檔后才猜對的,這樣很慢.如果預先知道文檔編碼,可以設置編碼參數來減少自動檢查編碼出錯的概率并且提高文檔解析速度.在創建 BeautifulSoup 對象的時候設置 from_encoding 參數.
下面一段文檔用了ISO-8859-8編碼方式,這段文檔太短,結果Beautiful Soup以為文檔是用ISO-8859-7編碼:
markup = b"
\xed\xe5\xec\xf9
"soup = BeautifulSoup(markup)
soup.h1
νεμω
soup.original_encoding 'ISO-8859-7' 通過傳入 from_encoding 參數來指定編碼方式:soup = BeautifulSoup(markup, from_encoding=“iso-8859-8”)
soup.h1
????
soup.original_encoding 'iso8859-8' 少數情況下(通常是UTF-8編碼的文檔中包含了其它編碼格式的文件),想獲得正確的Unicode編碼就不得不將文檔中少數特殊編碼字符替換成特殊Unicode編碼,“REPLACEMENT CHARACTER” (U+FFFD, �) [9] . 如果Beautifu Soup猜測文檔編碼時作了特殊字符的替換,那么Beautiful Soup會把 UnicodeDammit 或 BeautifulSoup 對象的 .contains_replacement_characters 屬性標記為 True .這樣就可以知道當前文檔進行Unicode編碼后丟失了一部分特殊內容字符.如果文檔中包含�而 .contains_replacement_characters 屬性是 False ,則表示�就是文檔中原來的字符,不是轉碼失敗.輸出編碼
通過Beautiful Soup輸出文檔時,不管輸入文檔是什么編碼方式,輸出編碼均為UTF-8編碼,下面例子輸入文檔是Latin-1編碼:
markup = b’’’
Sacr\xe9 bleu!
'''soup = BeautifulSoup(markup)
print(soup.prettify())
Sacré bleu!
注意,輸出文檔中的標簽的編碼設置已經修改成了與輸出編碼一致的UTF-8.
如果不想用UTF-8編碼輸出,可以將編碼方式傳入 prettify() 方法:
print(soup.prettify(“latin-1”))
…
還可以調用 BeautifulSoup 對象或任意節點的 encode() 方法,就像Python的字符串調用 encode() 方法一樣:
soup.p.encode(“latin-1”)
‘
Sacr\xe9 bleu!
’soup.p.encode(“utf-8”)
‘
Sacr\xc3\xa9 bleu!
’如果文檔中包含當前編碼不支持的字符,那么這些字符將唄轉換成一系列XML特殊字符引用,下面例子中包含了Unicode編碼字符SNOWMAN:
markup = u"\N{SNOWMAN}"
snowman_soup = BeautifulSoup(markup)
tag = snowman_soup.b
SNOWMAN字符在UTF-8編碼中可以正常顯示(看上去像是?),但有些編碼不支持SNOWMAN字符,比如ISO-Latin-1或ASCII,那么在這些編碼中SNOWMAN字符會被轉換成“☃”:
print(tag.encode(“utf-8”))
?
print tag.encode(“latin-1”)
?
print tag.encode(“ascii”)
?
Unicode, dammit! (靠!)
編碼自動檢測 功能可以在Beautiful Soup以外使用,檢測某段未知編碼時,可以使用這個方法:
from bs4 import UnicodeDammit
dammit = UnicodeDammit(“Sacr\xc3\xa9 bleu!”)
print(dammit.unicode_markup)
Sacré bleu!
dammit.original_encoding
‘utf-8’
如果Python中安裝了 chardet 或 cchardet 那么編碼檢測功能的準確率將大大提高.輸入的字符越多,檢測結果越精確,如果事先猜測到一些可能編碼,那么可以將猜測的編碼作為參數,這樣將優先檢測這些編碼:
dammit = UnicodeDammit(“Sacr\xe9 bleu!”, [“latin-1”, “iso-8859-1”])
print(dammit.unicode_markup)
Sacré bleu!
dammit.original_encoding
‘latin-1’
編碼自動檢測 功能中有2項功能是Beautiful Soup庫中用不到的
智能引號
使用Unicode時,Beautiful Soup還會智能的把引號 [10] 轉換成HTML或XML中的特殊字符:
markup = b"
I just \x93love\x94 Microsoft Word\x92s smart quotes
"UnicodeDammit(markup, [“windows-1252”], smart_quotes_to=“html”).unicode_markup
u’
I just “love” Microsoft Word’s smart quotes
’UnicodeDammit(markup, [“windows-1252”], smart_quotes_to=“xml”).unicode_markup
u’
I just “love” Microsoft Word’s smart quotes
’也可以把引號轉換為ASCII碼:
UnicodeDammit(markup, [“windows-1252”], smart_quotes_to=“ascii”).unicode_markup
u’
I just “love” Microsoft Word’s smart quotes
’很有用的功能,但是Beautiful Soup沒有使用這種方式.默認情況下,Beautiful Soup把引號轉換成Unicode:
UnicodeDammit(markup, [“windows-1252”]).unicode_markup
u’
I just \u201clove\u201d Microsoft Word\u2019s smart quotes
’矛盾的編碼
有時文檔的大部分都是用UTF-8,但同時還包含了Windows-1252編碼的字符,就像微軟的智能引號 [10] 一樣.一些包含多個信息的來源網站容易出現這種情況. UnicodeDammit.detwingle() 方法可以把這類文檔轉換成純UTF-8編碼格式,看個簡單的例子:
snowmen = (u"\N{SNOWMAN}" * 3)
quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")
doc = snowmen.encode(“utf8”) + quote.encode(“windows_1252”)
這段文檔很雜亂,snowmen是UTF-8編碼,引號是Windows-1252編碼,直接輸出時不能同時顯示snowmen和引號,因為它們編碼不同:
print(doc)
???�I like snowmen!�
print(doc.decode(“windows-1252”))
a??a??a??“I like snowmen!”
如果對這段文檔用UTF-8解碼就會得到 UnicodeDecodeError 異常,如果用Windows-1252解碼就回得到一堆亂碼.幸好, UnicodeDammit.detwingle() 方法會吧這段字符串轉換成UTF-8編碼,允許我們同時顯示出文檔中的snowmen和引號:
new_doc = UnicodeDammit.detwingle(doc)
print(new_doc.decode(“utf8”))
???“I like snowmen!”
UnicodeDammit.detwingle() 方法只能解碼包含在UTF-8編碼中的Windows-1252編碼內容,但這解決了最常見的一類問題.
在創建 BeautifulSoup 或 UnicodeDammit 對象前一定要先對文檔調用 UnicodeDammit.detwingle() 確保文檔的編碼方式正確.如果嘗試去解析一段包含Windows-1252編碼的UTF-8文檔,就會得到一堆亂碼,比如: a??a??a??“I like snowmen!”.
UnicodeDammit.detwingle() 方法在Beautiful Soup 4.1.0版本中新增
解析部分文檔
如果僅僅因為想要查找文檔中的標簽而將整片文檔進行解析,實在是浪費內存和時間.最快的方法是從一開始就把標簽以外的東西都忽略掉. SoupStrainer 類可以定義文檔的某段內容,這樣搜索文檔時就不必先解析整篇文檔,只會解析在 SoupStrainer 中定義過的文檔. 創建一個 SoupStrainer 對象并作為 parse_only 參數給 BeautifulSoup 的構造方法即可.
SoupStrainer
SoupStrainer 類接受與典型搜索方法相同的參數:name , attrs , recursive , text , **kwargs 。下面舉例說明三種 SoupStrainer 對象:
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)
再拿“愛麗絲”文檔來舉例,來看看使用三種 SoupStrainer 對象做參數會有什么不同:
html_doc = “”"
The Dormouse's storyThe Dormouse's story
Once upon a time there were three little sisters; and their names were Elsie, Lacie and Tillie; and they lived at the bottom of a well.
...
"""print(BeautifulSoup(html_doc, “html.parser”, parse_only=only_a_tags).prettify())
Elsie
Lacie
Tillie
print(BeautifulSoup(html_doc, “html.parser”, parse_only=only_tags_with_id_link2).prettify())
Lacie
print(BeautifulSoup(html_doc, “html.parser”, parse_only=only_short_strings).prettify())
Elsie
,
Lacie
and
Tillie
…
還可以將 SoupStrainer 作為參數傳入 搜索文檔樹 中提到的方法.這可能不是個常用用法,所以還是提一下:
soup = BeautifulSoup(html_doc)
soup.find_all(only_short_strings)
[u’\n\n’, u’\n\n’, u’Elsie’, u’,\n’, u’Lacie’, u’ and\n’, u’Tillie’,
u’\n\n’, u’…’, u’\n’]
總結
以上是生活随笔為你收集整理的Python : Beautiful Soup修改文档树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人眼立体成像原理
- 下一篇: python图片logo_Python