如何在 python 中下载具有正确字符集的任何(!)网页?
-
18-09-2019 - |
题
问题
当使用 python 屏幕抓取网页时,必须知道页面的字符编码。 如果你的字符编码错误,那么你的输出将会混乱。
人们通常使用一些基本技术来检测编码。他们要么使用标头中的字符集,要么使用元标记中定义的字符集,或者使用 编码检测器 (它不关心元标记或标头)。仅使用其中一种技术,有时您将无法获得与在浏览器中相同的结果。
浏览器是这样做的:
- 元标记始终优先(或 xml 定义)
- 当元标记中没有定义字符集时,将使用标头中定义的编码
- 如果根本没有定义编码,那么就该进行编码检测了。
(出色地...至少我相信大多数浏览器都是这样做的。文档确实很少。)
我正在寻找的是一个可以像浏览器那样决定页面字符集的库。 我确信我不是第一个需要适当解决此问题的人。
解决方案 (我还没试过……)
根据 美丽汤的文档.
Beautiful Soup 按优先级顺序尝试以下编码,将您的文档转换为 Unicode:
- 您作为脱词编码论点传递给汤构造器的编码。
- 在文档本身中发现的编码:例如,在 XML 声明或(对于 HTML 文档)http-equiv META 标记中。如果 Beautiful Soup 在文档中发现这种编码,它会从头开始重新解析文档,并尝试新的编码。唯一的例外是,如果您明确指定了编码,并且该编码实际上有效:那么它将忽略在文档中找到的任何编码。
- 通过查看文件的前几个字节来嗅探编码。如果在此阶段检测到编码,则将是UTF-*编码之一,EBCDIC或ASCII。
- 如果您安装了Chardet库的编码。
- UTF-8
- Windows-1252
解决方案
我会用 html5库 为了这。
其他提示
当您使用 urllib 或 urllib2 下载文件时,您可以查明是否传输了字符集标头:
fp = urllib2.urlopen(request)
charset = fp.headers.getparam('charset')
您可以使用 BeautifulSoup 来定位 HTML 中的元元素:
soup = BeatifulSoup.BeautifulSoup(data)
meta = soup.findAll('meta', {'http-equiv':lambda v:v.lower()=='content-type'})
如果两者都不可用,浏览器通常会回退到用户配置并结合自动检测。正如 rajax 所建议的,您可以使用 chardet 模块。如果您有可用的用户配置告诉您页面应该是中文(比如说),您也许可以做得更好。
使用 通用编码检测器:
>>> import chardet
>>> chardet.detect(urlread("http://google.cn/"))
{'encoding': 'GB2312', 'confidence': 0.99}
另一种选择是只使用 wget:
import os
h = os.popen('wget -q -O foo1.txt http://foo.html')
h.close()
s = open('foo1.txt').read()
看来您需要混合提供的答案:
- 使用 urllib 获取页面
- 寻找
<meta>
使用美丽汤或其他方法标记 - 如果不存在元标记,请检查 urllib 返回的标头
- 如果仍然没有给您答案,请使用通用编码检测器。
老实说,我不相信你会找到比这更好的东西。
事实上,如果您进一步阅读在其他答案的评论中链接到的常见问题解答,这就是检测器库的作者所提倡的。
如果您相信常见问题解答,这就是浏览器所做的(按照您原始问题中的要求),因为检测器是 Firefox 嗅探代码的端口。
Scrapy 下载页面并检测其正确的编码,这与 requests.get(url).text 或 urlopen 不同。为此,它尝试遵循类似浏览器的规则 - 这是最好的规则,因为网站所有者有动力让他们的网站在浏览器中运行。Scrapy 需要获取 HTTP 标头, <meta>
标签、BOM 标记以及帐户中编码名称的差异。
基于内容的猜测(chardet、UnicodeDammit)本身并不是正确的解决方案,因为它可能会失败;它应该只作为最后的手段,当标题或 <meta>
或 BOM 标记不可用或未提供任何信息。
你不必使用Scrapy来获取其编码检测功能;它们(以及其他一些东西)在一个名为 w3lib 的单独库中发布: https://github.com/scrapy/w3lib.
获取页面编码和 unicode 正文使用 w3lib.encoding.html_to_unicode 函数,具有基于内容的猜测后备:
import chardet
from w3lib.encoding import html_to_unicode
def _guess_encoding(data):
return chardet.detect(data).get('encoding')
detected_encoding, html_content_unicode = html_to_unicode(
content_type_header,
html_content_bytes,
default_encoding='utf8',
auto_detect_fun=_guess_encoding,
)
与其尝试获取页面然后弄清楚浏览器将使用的字符集,为什么不直接使用浏览器来获取页面并检查它使用的字符集。
from win32com.client import DispatchWithEvents
import threading
stopEvent=threading.Event()
class EventHandler(object):
def OnDownloadBegin(self):
pass
def waitUntilReady(ie):
"""
copypasted from
http://mail.python.org/pipermail/python-win32/2004-June/002040.html
"""
if ie.ReadyState!=4:
while 1:
print "waiting"
pythoncom.PumpWaitingMessages()
stopEvent.wait(.2)
if stopEvent.isSet() or ie.ReadyState==4:
stopEvent.clear()
break;
ie = DispatchWithEvents("InternetExplorer.Application", EventHandler)
ie.Visible = 0
ie.Navigate('http://kskky.info')
waitUntilReady(ie)
d = ie.Document
print d.CharSet
BeautifulSoup 使用 UnicodeDammit 进行此操作: 统一码,该死