Beautiful Soup 4 編碼

2021-05-21 15:26 更新

任何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)檢測`_ 子庫來識(shí)別當(dāng)前文檔編碼并轉(zhuǎn)換成Unicode編碼. ?BeautifulSoup? 對(duì)象的 ?.original_encoding? 屬性記錄了自動(dòng)識(shí)別編碼的結(jié)果:

soup.original_encoding
'utf-8'

`編碼自動(dòng)檢測`_ 功能大部分時(shí)候都能猜對(duì)編碼格式,但有時(shí)候也會(huì)出錯(cuò).有時(shí)候即使猜測正確,也是在逐個(gè)字節(jié)的遍歷整個(gè)文檔后才猜對(duì)的,這樣很慢.如果預(yù)先知道文檔編碼,可以設(shè)置編碼參數(shù)來減少自動(dòng)檢查編碼出錯(cuò)的概率并且提高文檔解析速度.在創(chuàng)建 ?BeautifulSoup? 對(duì)象的時(shí)候設(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編碼, 但不知道具體編碼. 可以先自己猜測, 猜測錯(cuò)誤(依舊是亂碼)時(shí), 可以把錯(cuò)誤編碼作為 ?exclude_encodings? 參數(shù), 這樣文檔就不會(huì)嘗試使用這種編碼了解碼了. 譯者備注: 在沒有指定編碼的情況下, BS會(huì)自己猜測編碼, 把不正確的編碼排除掉, 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猜測文檔編碼時(shí)作了特殊字符的替換,那么Beautiful Soup會(huì)把 ?UnicodeDammit? 或 ?BeautifulSoup? 對(duì)象的 ?.contains_replacement_characters? 屬性標(biāo)記為 True .這樣就可以知道當(dāng)前文檔進(jìn)行Unicode編碼后丟失了一部分特殊內(nèi)容字符.如果文檔中包含?而 ?.contains_replacement_characters? 屬性是 ?False? ,則表示?就是文檔中原來的字符,不是轉(zhuǎn)碼失敗.

輸出編碼

通過Beautiful Soup輸出文檔時(shí),不管輸入文檔是什么編碼方式,輸出編碼均為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? 對(duì)象或任意節(jié)點(diǎn)的 ?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字符會(huì)被轉(zhuǎn)換成“&#9731”:

print(tag.encode("utf-8"))
# <b>?</b>

print tag.encode("latin-1")
# <b>&#9731;</b>

print tag.encode("ascii")
# <b>&#9731;</b>

Unicode, Dammit! (亂碼, 靠!)

譯者備注: UnicodeDammit 是BS內(nèi)置庫, 主要用來猜測文檔編碼.

`編碼自動(dòng)檢測`_ 功能可以在Beautiful Soup以外使用,檢測某段未知編碼時(shí),可以使用這個(gè)方法:

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'

`編碼自動(dòng)檢測`_ 功能中有2項(xiàng)功能是Beautiful Soup庫中用不到的

智能引號(hào)

使用Unicode時(shí),Beautiful Soup還會(huì)智能的把引號(hào)轉(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 &ldquo;love&rdquo; Microsoft Word&rsquo;s smart quotes</p>'

UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
# u'<p>I just &#x201C;love&#x201D; Microsoft Word&#x2019;s smart quotes</p>'

也可以把引號(hào)轉(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把引號(hào)轉(zhuǎn)換成Unicode:

UnicodeDammit(markup, ["windows-1252"]).unicode_markup
# u'<p>I just \u201clove\u201d Microsoft Word\u2019s smart quotes</p>'

矛盾的編碼

有時(shí)文檔的大部分都是用UTF-8,但同時(shí)還包含了Windows-1252編碼的字符,就像微軟的智能引號(hào)一樣. 一些包含多個(gè)信息的來源網(wǎng)站容易出現(xiàn)這種情況. ?UnicodeDammit.detwingle()? 方法可以把這類文檔轉(zhuǎn)換成純UTF-8編碼格式,看個(gè)簡單的例子:

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編碼,引號(hào)是Windows-1252編碼,直接輸出時(shí)不能同時(shí)顯示snowmen和引號(hào),因?yàn)樗鼈兙幋a不同:

print(doc)
# ????I like snowmen!?

print(doc.decode("windows-1252"))
# a??a??a??“I like snowmen!”

如果對(duì)這段文檔用UTF-8解碼就會(huì)得到 ?UnicodeDecodeError? 異常,如果用Windows-1252解碼就回得到一堆亂碼. 幸好, ?UnicodeDammit.detwingle()? 方法會(huì)吧這段字符串轉(zhuǎn)換成UTF-8編碼,允許我們同時(shí)顯示出文檔中的snowmen和引號(hào):

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? 對(duì)象前一定要先對(duì)文檔調(diào)用 ?UnicodeDammit.detwingle()? 確保文檔的編碼方式正確.如果嘗試去解析一段包含Windows-1252編碼的UTF-8文檔,就會(huì)得到一堆亂碼,比如: a??a??a??“I like snowmen!”.

?UnicodeDammit.detwingle()? 方法在Beautiful Soup 4.1.0版本中新增


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)