HTML编码和lxml解析

10
我希望能够解决使用lxml爬取HTML时出现的编码问题。以下是我遇到的三个HTML示例文档:

1.

<!DOCTYPE html>
<html lang='en'>
<head>
   <title>Unicode Chars: 은 —’</title>
   <meta charset='utf-8'>
</head>
<body></body>
</html>

2.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko-KR" lang="ko-KR">
<head>
    <title>Unicode Chars: 은 —’</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body></body>
</html>

3.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Unicode Chars: 은 —’</title>
</head>
<body></body>
</html>

我的基本脚本:

from lxml.html import fromstring
...

doc = fromstring(raw_html)
title = doc.xpath('//title/text()')[0]
print title

结果如下:

Unicode Chars: ì ââ
Unicode Chars:  —’
Unicode Chars:  —’

显然,样本1存在问题,缺少<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />标签。从这里的解决方法将正确识别样本1为utf-8,因此在功能上等同于我的原始代码。

lxml文档似乎存在冲突:

这里的示例中,似乎建议我们使用UnicodeDammit将标记编码为Unicode。

from BeautifulSoup import UnicodeDammit

def decode_html(html_string):
    converted = UnicodeDammit(html_string, isHTML=True)
    if not converted.unicode:
        raise UnicodeDecodeError(
            "Failed to detect encoding, tried [%s]",
            ', '.join(converted.triedEncodings))
    # print converted.originalEncoding
    return converted.unicode

root = lxml.html.fromstring(decode_html(tag_soup))

然而,在这里它说:

如果你尝试在指定了标题meta标签中的字符集的unicode字符串中解析HTML数据,则会出现错误。通常应避免将XML / HTML数据转换为Unicode,然后将其传递给解析器。这既慢又容易出错。

如果我尝试按照lxml文档中的第一个建议进行操作,我的代码现在是:

from lxml.html import fromstring
from bs4 import UnicodeDammit
...
dammit = UnicodeDammit(raw_html)
doc = fromstring(dammit.unicode_markup)
title = doc.xpath('//title/text()')[0]
print title

我现在得到以下结果:

Unicode Chars: 은 —’
Unicode Chars: 은 —’
ValueError: Unicode strings with encoding declaration are not supported.

现在样例1已经可以正常工作,但由于<?xml version="1.0" encoding="utf-8"?>标签的原因,样例3会导致错误。

是否有一种正确的方法来处理所有这些情况?是否有比以下方法更好的解决方案?

dammit = UnicodeDammit(raw_html)
try:
    doc = fromstring(dammit.unicode_markup)
except ValueError:
    doc = fromstring(raw_html)
2个回答

18

lxml在处理Unicode方面存在多个问题。目前最好使用字节并显式指定字符编码:

#!/usr/bin/env python
import glob
from lxml import html
from bs4 import UnicodeDammit

for filename in glob.glob('*.html'):
    with open(filename, 'rb') as file:
        content = file.read()
        doc = UnicodeDammit(content, is_html=True)

    parser = html.HTMLParser(encoding=doc.original_encoding)
    root = html.document_fromstring(content, parser=parser)
    title = root.find('.//title').text_content()
    print(title)

输出

Unicode Chars:  —’
Unicode Chars:  —’
Unicode Chars:  —’

我认为这现在看起来是最好的解决方案;即使lxml学习了<meta charset>标签,我也可以看到它处理其他边缘情况。在发布这篇文章后,我发现[这个][https://dev59.com/Kmct5IYBdhLWcg3wpO7H]指引我朝着类似的方向前进。 - bismark

3
问题可能源于 <meta charset> 标签是一个相对较新的标准(如果我没记错,是HTML5标准,或者此前根本没有使用过它)。在 lxml.html 库更新以反映这个标签之前,您需要特殊处理该情况。如果您只关心 ISO-8859-* 和 UTF-8,并且可以扔掉不兼容 ASCII 编码(如 UTF-16 或东亚传统字符集)的内容,则可以对字节字符串进行正则表达式替换,将较新的 <meta charset> 替换为较旧的 http-equiv 格式。否则,如果需要一个正确的解决方案,最好自己修补库(并在此过程中贡献修复方法)。您可能想问一下 lxml 开发人员是否已经针对此特定错误拥有半成品代码,或者他们首先是否正在跟踪这个错误。

+1 是因为lxml可能无法理解<meta charset>。我会尝试添加一个补丁,但还没有深入研究他们的代码。 - bismark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接