如何在python中下载任何(!)网页和正确的字符集?

时间:2009-09-30 00:42:00

标签: python character-encoding screen-scraping urllib2 urllib

问题

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

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

浏览器这样做:

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

(嗯......至少这是我认为大多数浏览器都这样做的方式。文档非常缺乏。)

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

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

根据Beautiful Soup's documentation

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

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

7 个答案:

答案 0 :(得分:36)

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

答案 1 :(得分:14)

使用Universal Encoding Detector

>>> 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()

答案 2 :(得分:4)

看起来你需要一个混合的答案:

  1. 使用urllib
  2. 获取页面
  3. 使用漂亮的汤或其他方法查找<meta>标签
  4. 如果不存在元标记,请检查urllib返回的标题
  5. 如果仍然没有给出答案,请使用通用编码检测器。
  6. 老实说,我不相信你会找到比这更好的东西。

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

    如果您相信常见问题解答,这就是浏览器所做的事情(根据原始问题的要求),因为探测器是firefox嗅探代码的端口。

答案 3 :(得分:3)

我会使用html5lib

答案 4 :(得分:2)

与request.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,
)

答案 5 :(得分: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

答案 6 :(得分:1)

BeautifulSoup使用UnicodeDammit:Unicode, Dammit