使用Beautiful Soup时,无需任何猜测即可正确检测编码

时间:2015-12-06 09:27:48

标签: python character-encoding beautifulsoup

我正致力于改进Python IRC bot的字符编码支持,该机器人检索通道中提到其URL的页面的标题。

我正在使用的当前流程如下:

  1. Requests

    r = requests.get(url, headers={ 'User-Agent': '...' })
    
  2. Beautiful Soup

    soup = bs4.BeautifulSoup(r.text, from_encoding=r.encoding)
    
  3. title = soup.title.string.replace('\n', ' ').replace(...)等。
  4. 指定from_encoding=r.encoding是一个好的开始,因为它允许我们在解析页面时从charset标题中注意Content-Type

    如果面临这种情况,则会在其<meta http-equiv … charset=…">标题中指定<meta charset="…">charset代替({1}}的页面。

    我目前看到的方法如下:

    1. 解析页面时无条件使用Unicode, Dammit。这是默认设置,但它似乎对我测试过的任何页面都无效。
    2. 在解析页面之前或之后无条件地使用ftfy。我不喜欢这个选项,因为它基本上依赖于我们(通常)拥有完美信息的任务的猜测。
    3. 编写代码以查找适当的Content-Type标记,尝试注意我们在那里找到的任何编码,然后再回到请求'<meta>,可能与之前的选项结合使用。我觉得这个选项很理想,但如果它已经存在,我宁愿不写这个代码。
    4. TL; DR有正确的方法吗?使用与浏览器使用类似的技术,使美丽的汤正确地注意到网页上任意HTML页面的字符编码?

2 个答案:

答案 0 :(得分:2)

您似乎更希望在文档中声明的编码优先于HTTP标头中声明的编码。 UnicodeDammit(由BeautifulSoup在内部使用)如果您只是从头部传递编码,则反过来执行此操作。您可以通过从文档中读取已声明的编码并将其传递给第一个来解决此问题。大致(未经测试!):

r = requests.get(url, headers={ 'User-Agent': '...' })

is_html = content_type_header.split(';', 1)[0].lower().startswith('text/html')
declared_encoding = UnicodeDammit.find_declared_encoding(r.text, is_html=is_html)

encodings_to_try = [r.encoding]
if declared_encoding is not None:
    encodings_to_try.insert(0, declared_encoding)
soup = bs4.BeautifulSoup(r.text, from_encoding=encodings_to_try)

title = soup.title...

答案 1 :(得分:1)

与更通用的模块ftfy不同,Unicode,Dammit采用的方法正是我正在寻找的(参见bs4/dammit.py)。它会检查任何<meta>标记提供的信息,而不是对问题进行更多盲目的猜测。

但是,当使用r.text时,请求会尝试通过自动解码charset标题中Content-Type的网页来提供帮助,然后回退到ISO 8859-1不存在的页面,但Unicode,Dammit不会触及已经在unicode字符串中的任何标记!

我选择的解决方案是使用r.content代替:

  1. r = requests.get(url, headers={ 'User-Agent': '...' })
  2. soup = bs4.BeautifulSoup(r.content)
  3. title = soup.title.string.replace('\n', ' ').replace(...)等。
  4. 我能看到的唯一缺点是,charset只有Content-Type的网页会受到Unicode,Dammit的猜测,因为BeautifulSoup from_encoding=r.encoding参数将覆盖Unicode,完全是Dammit。