问题

当使用 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()

看来您需要混合提供的答案:

  1. 使用 urllib 获取页面
  2. 寻找 <meta> 使用美丽汤或其他方法标记
  3. 如果不存在元标记,请检查 urllib 返回的标头
  4. 如果仍然没有给您答案,请使用通用编码检测器。

老实说,我不相信你会找到比这更好的东西。

事实上,如果您进一步阅读在其他答案的评论中链接到的常见问题解答,这就是检测器库的作者所提倡的。

如果您相信常见问题解答,这就是浏览器所做的(按照您原始问题中的要求),因为检测器是 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 进行此操作: 统一码,该死

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top