首页 文章

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

提问于
浏览
33

问题

When screen-scraping a webpage using python one has to know the character encoding of the page. 如果你得到的字符编码错误,你的输出就会搞砸了 .

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

浏览器这样做:

  • 元标记始终优先(或xml定义)

  • 当元标记中没有定义字符集时,将使用标头中定义的编码

  • 如果根本没有定义编码,那么就是编码检测的时间 .

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

What I'm looking for is a library that can decide the character set of a page the way a browser would. 我'm sure I'我不是第一个需要妥善解决这个问题的人 .

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

根据Beautiful Soup's documentation .

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

  • 作为fromEncoding参数传递给汤构造函数的编码 .

  • 在文档本身中发现的编码:例如,在XML声明中或(对于HTML文档)的http-equiv META标记 . 如果Beautiful Soup在文档中找到这种编码,它会从头开始再次解析文档并尝试新编码 . 唯一的例外是如果您明确指定了编码,并且该编码实际上有效:那么它将忽略它在文档中找到的任何编码 .

  • 通过查看文件的前几个字节来嗅探编码 . 如果在此阶段检测到编码,则它将是UTF- *编码,EBCDIC或ASCII之一 .

  • chardet库嗅探的编码(如果已安装) .

  • UTF-8

  • Windows-1252

7 回答

  • 3

    而不是试图获取一个页面,然后找出浏览器将使用的charset,为什么不只是使用浏览器来获取页面并检查它使用什么charset ..

    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
    
  • 2

    当您使用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

    BeautifulSoup使用UnicodeDammit进行此操作:Unicode, Dammit

  • 4

    与request.get(url).text或urlopen不同,Scrapy会下载页面并检测其正确的编码 . 为此,它尝试遵循类似浏览器的规则 - 这是最好的规则,因为网站所有者有动力使他们的网站在浏览器中工作 . Scrapy需要在帐户中使用HTTP标头, <meta> 标签,BOM标记和编码名称的差异 .

    基于内容的猜测(chardet,UnicodeDammit)本身并不是一个正确的解决方案,因为它可能会失败;当 Headers 或 <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,
    )
    
  • 37

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

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

    • 使用urllib获取页面

    • 使用美丽的汤或其他方法查找 <meta> 标签

    • 如果不存在元标记,请检查urllib返回的标头

    • 如果仍然没有给你答案,请使用通用编码检测器 .

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

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

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

  • 15

    我会为此使用html5lib .

相关问题