HTML编码和lxml解析

Posted

技术标签:

【中文标题】HTML编码和lxml解析【英文标题】:HTML encoding and lxml parsing 【发布时间】:2013-02-24 11:27:25 【问题描述】:

我正在尝试最终解决一些因尝试使用 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 和缺少的 &lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt; 标记显然存在问题。 here 的解决方案将正确地将示例 1 识别为 utf-8,因此它在功能上等同于我的原始代码。

lxml 文档出现冲突:

来自here 的示例似乎建议我们应该使用 UnicodeDammit 将标记编码为 un​​icode。

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

但是 here 它说:

[Y]当您尝试 [解析] 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 现在可以正常工作,但示例 3 由于 &lt;?xml version="1.0" encoding="utf-8"?&gt; 标记而导致错误。

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

dammit = UnicodeDammit(raw_html)
try:
    doc = fromstring(dammit.unicode_markup)
except ValueError:
    doc = fromstring(raw_html)

【问题讨论】:

【参考方案1】:

问题可能源于&lt;meta charset&gt; 是一个相对较新的标准(如果我没记错的话是 HTML5,或者它之前并没有真正使用过。)

lxml.html 库更新以反映它之前,您需要特别处理这种情况。

如果您只关心 ISO-8859-* 和 UTF-8,并且可以舍弃不兼容 ASCII 的编码(例如 UTF-16 或东亚传统字符集),则可以进行正则表达式替换在字节字符串上,将较新的 &lt;meta charset&gt; 替换为较旧的 http-equiv 格式。

否则,如果您需要适当的解决方案,最好的办法是自己修补库(并在您使用它时提供修复。)您可能想询问 lxml 开发人员是否有任何半生不熟的代码为这个特定的错误四处寻找,或者他们是否首先在他们的错误跟踪系统上跟踪错误。

【讨论】:

+1 因为 lxml 可能不理解 。我会看看我能做些什么来添加一个补丁,还没有深入研究他们的代码。【参考方案2】:

lxml 具有与处理 Unicode 相关的 several issues。在明确指定字符编码时最好使用字节(目前):

#!/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 学习了 标签之后,我也可以看到它处理其他边缘情况。发布此内容后,我发现 [this][***.com/questions/11938924/… 指向了类似的方向。

以上是关于HTML编码和lxml解析的主要内容,如果未能解决你的问题,请参考以下文章

Python爬虫编程思想(39):使用lxml解析HTML与XML

Python 之lxml解析模块

使用非utf-8编码在Python中解析XML

python3解析库lxml

Python操作XML和HTML,LXML类库的使用

lxml库