13

我正在尝试加载一个 html 页面并输出文本,即使我正确获取了网页,BeautifulSoup 也会以某种方式破坏编码。

资源:

# -*- coding: utf-8 -*-
import requests
from BeautifulSoup import BeautifulSoup

url = "http://www.columbia.edu/~fdc/utf8/"
r = requests.get(url)

encodedText = r.text.encode("utf-8")
soup = BeautifulSoup(encodedText)
text =  str(soup.findAll(text=True))
print text.decode("utf-8")

摘录输出:

...Odenw\xc3\xa4lderisch...

这应该是Odenwälderisch

4

3 回答 3

43

你犯了两个错误;您错误地处理了编码,并且您将结果列表视为可以安全地转换为字符串而不会丢失信息的东西。

首先,不要使用response.text!这不是 BeautifulSoup 的错,您正在重新编码Mojibake。当服务器没有明确指定编码时,该requests库将默认为内容类型的 Latin-1 编码,因为 HTTP 标准规定这是默认值。text/*

请参阅高级文档的编码部分

Requests 唯一不会这样做的情况是,如果 HTTP 标头中不存在显式字符集并且Content-Type头包含text. 在这种情况下,RFC 2616 指定默认字符集必须是ISO-8859-1. 在这种情况下,请求遵循规范。如果你需要不同的编码,你可以手动设置Response.encoding属性,或者使用 raw Response.content

大胆强调我的。

而是传入response.content原始数据:

soup = BeautifulSoup(r.content)

我看到您正在使用 BeautifulSoup 3。您真的想升级到 BeautifulSoup 4;版本 3 已于 2012 年停产,并包含几个错误。安装beautifulsoup4项目,并使用from bs4 import BeautifulSoup.

BeautifulSoup 4 通常可以很好地确定解析时使用的正确编码,无论是从 HTML<meta>标记还是对所提供字节的统计分析。如果服务器确实提供了字符集,您仍然可以将其从响应中传递给 BeautifulSoup,但如果requests使用默认值,请先进行测试:

encoding = r.encoding if 'charset' in r.headers.get('content-type', '').lower() else None
parser = 'html.parser'  # or lxml or html5lib
soup = BeautifulSoup(r.content, parser, from_encoding=encoding)

最后但同样重要的是,使用 BeautifulSoup 4,您可以使用以下方法从页面中提取所有文本soup.get_text()

text = soup.get_text()
print text

相反,您将结果列表(的返回值soup.findAll())转换为字符串。这永远不会起作用,因为 Python 中的容器使用repr()列表中的每个元素来生成调试字符串,而对于字符串,这意味着您可以获得任何非可打印 ASCII 字符的转义序列。

于 2016-04-25T06:29:44.410 回答
6

这不是 BeautifulSoup 的错。encodedText在使用 BeautifulSoup 之前,您可以通过打印出来看到这一点:非 ASCII 字符已经是乱码了。

这里的问题是您混淆了字节和字符。为了更好地了解差异,请阅读Joel 的一篇文章,但要点是字节是字节(8 位的组,没有附加任何进一步的含义),而字符是组成文本字符串的东西。编码将字符转换为字节,解码将字节转换回字符。

查看requests文档显示它r.text是由字符组成的,而不是字节。您不应该对其进行编码。如果您尝试这样做,您将创建一个字节字符串,当您尝试将其视为字符时,将会发生不好的事情。

有两种方法可以解决这个问题:

  1. 正如 Martijn 建议r.content的那样,使用存储在 中的原始未解码字节。然后你可以自己解码它们,把它们变成字符。
  2. 让我们requests进行解码,但只需确保它使用正确的编解码器。由于您知道在这种情况下是 UTF-8,因此您可以设置r.encoding = 'utf-8'. 如果你在访问之前这样做,那么r.text当你访问时r.text,它就会被正确解码,你会得到一个字符串。您根本不需要弄乱字符编码。

顺便说一句,Python 3 使维护字符串和字节字符串之间的区别变得更加容易,因为它需要您使用不同类型的对象来表示它们。

于 2016-04-25T06:48:05.020 回答
1

您的代码中有几个错误:

  1. 首先,不需要您尝试重新编码文本。Requests 可以为您提供页面的本机编码,BeautifulSoup 可以获取此信息并自行进行解码:

    # -*- coding: utf-8 -*-
    import requests
    from BeautifulSoup import BeautifulSoup
    
    url = "http://www.columbia.edu/~fdc/utf8/"
    r = requests.get(url)
    
    soup = BeautifulSoup(r.text, "html5lib")
    
  2. 其次,你有一个编码问题。您可能正在尝试在终端上可视化结果。您将得到的是文本中字符的 unicode 表示,用于不在 ASCII 集中的每个字符。您可以像这样检查结果:

    res = [item.encode("ascii","ignore") for item in soup.find_all(text=True)]
    
于 2016-04-25T07:08:35.950 回答