任何HTML或XML文檔都有自己的編碼方式,比如ASCII 或 UTF-8,但是使用Beautiful Soup解析后,文檔都被轉(zhuǎn)換成了Unicode:
markup = "<h1>Sacr\xc3\xa9 bleu!</h1>"
soup = BeautifulSoup(markup)
soup.h1
# <h1>Sacré bleu!</h1>
soup.h1.string
# u'Sacr\xe9 bleu!'
這不是魔術(shù)(但很神奇),Beautiful Soup用了 `編碼自動檢測`_ 子庫來識別當(dāng)前文檔編碼并轉(zhuǎn)換成Unicode編碼. ?BeautifulSoup
? 對象的 ?.original_encoding
? 屬性記錄了自動識別編碼的結(jié)果:
soup.original_encoding
'utf-8'
`編碼自動檢測`_ 功能大部分時候都能猜對編碼格式,但有時候也會出錯.有時候即使猜測正確,也是在逐個字節(jié)的遍歷整個文檔后才猜對的,這樣很慢.如果預(yù)先知道文檔編碼,可以設(shè)置編碼參數(shù)來減少自動檢查編碼出錯的概率并且提高文檔解析速度.在創(chuàng)建 ?BeautifulSoup
? 對象的時候設(shè)置 ?from_encoding
? 參數(shù).
下面一段文檔用了ISO-8859-8編碼方式,這段文檔太短,結(jié)果Beautiful Soup以為文檔是用ISO-8859-7編碼:
markup = b"<h1>\xed\xe5\xec\xf9</h1>"
soup = BeautifulSoup(markup)
soup.h1
<h1>νεμω</h1>
soup.original_encoding
'ISO-8859-7'
通過傳入 ?from_encoding
? 參數(shù)來指定編碼方式:
soup = BeautifulSoup(markup, from_encoding="iso-8859-8")
soup.h1
<h1>????</h1>
soup.original_encoding
'iso8859-8'
如果僅知道文檔采用了Unicode編碼, 但不知道具體編碼. 可以先自己猜測, 猜測錯誤(依舊是亂碼)時, 可以把錯誤編碼作為 ?exclude_encodings
? 參數(shù), 這樣文檔就不會嘗試使用這種編碼了解碼了. 譯者備注: 在沒有指定編碼的情況下, BS會自己猜測編碼, 把不正確的編碼排除掉, BS就更容易猜到正確編碼.
soup = BeautifulSoup(markup, exclude_encodings=["ISO-8859-7"])
soup.h1
<h1>????</h1>
soup.original_encoding
'WINDOWS-1255'
猜測結(jié)果是 Windows-1255 編碼, 猜測結(jié)果可能不夠準(zhǔn)確, 但是 Windows-1255 編碼是 ISO-8859-8 的擴(kuò)展集, 所以猜測結(jié)果已經(jīng)十分接近了, 并且不影響使用. (?exclude_encodings
? 參數(shù)是 4.4.0版本的新功能)
少數(shù)情況下(通常是UTF-8編碼的文檔中包含了其它編碼格式的文件),想獲得正確的Unicode編碼就不得不將文檔中少數(shù)特殊編碼字符替換成特殊Unicode編碼,“REPLACEMENT CHARACTER” (U+FFFD, ?). 如果Beautifu Soup猜測文檔編碼時作了特殊字符的替換,那么Beautiful Soup會把 ?UnicodeDammit
? 或 ?BeautifulSoup
? 對象的 ?.contains_replacement_characters
? 屬性標(biāo)記為 True .這樣就可以知道當(dāng)前文檔進(jìn)行Unicode編碼后丟失了一部分特殊內(nèi)容字符.如果文檔中包含?而 ?.contains_replacement_characters
? 屬性是 ?False
? ,則表示?就是文檔中原來的字符,不是轉(zhuǎn)碼失敗.
通過Beautiful Soup輸出文檔時,不管輸入文檔是什么編碼方式,輸出編碼均為UTF-8編碼,下面例子輸入文檔是Latin-1編碼:
markup = b'''
<html>
<head>
<meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" />
</head>
<body>
<p>Sacr\xe9 bleu!</p>
</body>
</html>
'''
soup = BeautifulSoup(markup)
print(soup.prettify())
# <html>
# <head>
# <meta content="text/html; charset=utf-8" http-equiv="Content-type" />
# </head>
# <body>
# <p>
# Sacré bleu!
# </p>
# </body>
# </html>
注意,輸出文檔中的<meta>標(biāo)簽的編碼設(shè)置已經(jīng)修改成了與輸出編碼一致的UTF-8.
如果不想用UTF-8編碼輸出,可以將編碼方式傳入 ?prettify()
? 方法:
print(soup.prettify("latin-1"))
# <html>
# <head>
# <meta content="text/html; charset=latin-1" http-equiv="Content-type" />
# ...
還可以調(diào)用 ?BeautifulSoup
? 對象或任意節(jié)點的 ?encode()
? 方法,就像Python的字符串調(diào)用 ?encode()
? 方法一樣:
soup.p.encode("latin-1")
# '<p>Sacr\xe9 bleu!</p>'
soup.p.encode("utf-8")
# '<p>Sacr\xc3\xa9 bleu!</p>'
如果文檔中包含當(dāng)前編碼不支持的字符,那么這些字符將唄轉(zhuǎn)換成一系列XML特殊字符引用,下面例子中包含了Unicode編碼字符SNOWMAN:
markup = u"<b>\N{SNOWMAN}</b>"
snowman_soup = BeautifulSoup(markup)
tag = snowman_soup.b
SNOWMAN字符在UTF-8編碼中可以正常顯示(看上去像是?),但有些編碼不支持SNOWMAN字符,比如ISO-Latin-1或ASCII,那么在這些編碼中SNOWMAN字符會被轉(zhuǎn)換成“☃”:
print(tag.encode("utf-8"))
# <b>?</b>
print tag.encode("latin-1")
# <b>☃</b>
print tag.encode("ascii")
# <b>☃</b>
譯者備注: UnicodeDammit 是BS內(nèi)置庫, 主要用來猜測文檔編碼.
`編碼自動檢測`_ 功能可以在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
? 那么編碼檢測功能的準(zhǔn)確率將大大提高. 輸入的字符越多,檢測結(jié)果越精確,如果事先猜測到一些可能編碼, 那么可以將猜測的編碼作為參數(shù),這樣將優(yōu)先檢測這些編碼:
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還會智能的把引號轉(zhuǎn)換成HTML或XML中的特殊字符:
markup = b"<p>I just \x93love\x94 Microsoft Word\x92s smart quotes</p>"
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup
# u'<p>I just “love” Microsoft Word’s smart quotes</p>'
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
# u'<p>I just “love” Microsoft Word’s smart quotes</p>'
也可以把引號轉(zhuǎn)換為ASCII碼:
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup
# u'<p>I just "love" Microsoft Word\'s smart quotes</p>'
很有用的功能,但是Beautiful Soup沒有使用這種方式.默認(rèn)情況下,Beautiful Soup把引號轉(zhuǎn)換成Unicode:
UnicodeDammit(markup, ["windows-1252"]).unicode_markup
# u'<p>I just \u201clove\u201d Microsoft Word\u2019s smart quotes</p>'
有時文檔的大部分都是用UTF-8,但同時還包含了Windows-1252編碼的字符,就像微軟的智能引號一樣. 一些包含多個信息的來源網(wǎng)站容易出現(xiàn)這種情況. ?UnicodeDammit.detwingle()
? 方法可以把這類文檔轉(zhuǎn)換成純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()
? 方法會吧這段字符串轉(zhuǎn)換成UTF-8編碼,允許我們同時顯示出文檔中的snowmen和引號:
new_doc = UnicodeDammit.detwingle(doc)
print(new_doc.decode("utf8"))
# ???“I like snowmen!”
?UnicodeDammit.detwingle()
? 方法只能解碼包含在UTF-8編碼中的Windows-1252編碼內(nèi)容,但這解決了最常見的一類問題.
在創(chuàng)建 ?BeautifulSoup
? 或 ?UnicodeDammit
? 對象前一定要先對文檔調(diào)用 ?UnicodeDammit.detwingle()
? 確保文檔的編碼方式正確.如果嘗試去解析一段包含Windows-1252編碼的UTF-8文檔,就會得到一堆亂碼,比如: a??a??a??“I like snowmen!”.
?UnicodeDammit.detwingle()
? 方法在Beautiful Soup 4.1.0版本中新增
更多建議: