35

问题

当使用 python 对网页进行屏幕抓取时,必须知道页面的字符编码。如果你得到错误的字符编码,那么你的输出就会被弄乱。

人们通常使用一些基本的技术来检测编码。它们要么使用标头中的字符集或元标记中定义的字符集,要么使用编码检测器(它不关心元标记或标头)。仅使用其中一种技术,有时您将无法获得与在浏览器中相同的结果。

浏览器这样做:

  • 元标记始终优先(或 xml 定义)
  • 当元标记中没有定义字符集时,使用标头中定义的编码
  • 如果根本没有定义编码,那么就该进行编码检测了。

(嗯......至少我相信大多数浏览器都是这样做的。文档真的很稀缺。)

我正在寻找的是一个可以像浏览器一样决定页面字符集的库。我确信我不是第一个需要适当解决这个问题的人。

解决方案(我还没有尝试过......)

根据Beautiful Soup 的文档

Beautiful Soup 按优先级顺序尝试以下编码,将您的文档转换为 Unicode:

  • 您作为 fromEncoding 参数传递给汤构造函数的编码。
  • 在文档本身中发现的编码:例如,在 XML 声明或(对于 HTML 文档)http-equiv META 标记中。如果 Beautiful Soup 在文档中发现这种编码,它会重新从头开始解析文档并尝试新的编码。唯一的例外是,如果您明确指定了编码,并且该编码确实有效:那么它将忽略它在文档中找到的任何编码。
  • 通过查看文件的前几个字节来嗅探的编码。如果在此阶段检测到编码,它将是 UTF-* 编码、EBCDIC 或 ASCII 之一。
  • chardet 库嗅探到的编码,如果您安装了它。
  • UTF-8
  • Windows-1252
4

7 回答 7

37

当你下载一个带有 urllib 或 urllib2 的文件时,你可以查明是否传输了一个 charset header:

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 模块。如果您有可用的用户配置告诉您该页面应该是中文的(例如),您可能会做得更好。

于 2009-09-30T01:04:36.460 回答
15

使用通用编码检测器

>>> 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()
于 2009-09-30T00:44:15.887 回答
4

似乎您需要混合提供的答案:

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

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

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

如果您相信常见问题解答,这就是浏览器所做的(按照您原始问题中的要求),因为检测器是 firefox 嗅探代码的一个端口。

于 2009-10-09T16:34:22.850 回答
3

我会为此使用html5lib

于 2013-03-18T16:31:10.620 回答
2

与 requests.get(url).text 或 urlopen 不同,Scrapy 下载页面并检测其正确编码。为此,它会尝试遵循类似浏览器的规则——这是最好的方法,因为网站所有者有动力让他们的网站在浏览器中运行。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,
)
于 2017-05-17T10:36:36.283 回答
1

与其尝试获取页面然后找出浏览器将使用的字符集,不如直接使用浏览器获取页面并检查它使用的字符集。

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
于 2009-09-30T18:37:16.203 回答
1

BeautifulSoup 用 UnicodeDammit 来处理这个:Unicode, Dammit

于 2012-03-18T08:05:33.937 回答