HTML编码与lxml解析
我正在尝试解决一些编码问题,这些问题是在用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: 은 —’
显然,第一个示例有问题,因为缺少了<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
标签。这里的解决方案来自这个链接,它能正确识别第一个示例为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))
然而在这个链接中,它说:
[Y]ou will get errors when you try [to parse] HTML data in a unicode string that specifies a charset in a meta tag of the header. You should generally avoid converting XML/HTML data to unicode before passing it into the parsers. It is both slower and error prone.
如果我试着按照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.
第一个示例现在可以正常工作,但第三个示例由于<?xml version="1.0" encoding="utf-8"?>
标签而出现错误。
有没有一种正确的方法来处理所有这些情况?有没有比下面的解决方案更好的方法?
dammit = UnicodeDammit(raw_html)
try:
doc = fromstring(dammit.unicode_markup)
except ValueError:
doc = fromstring(raw_html)
2 个回答
这个问题可能是因为 <meta charset>
是一个比较新的标准(如果我没记错的话,是HTML5引入的,之前其实并没有广泛使用)。
在 lxml.html
这个库更新之前,你需要特别处理这种情况。
如果你只关心 ISO-8859-* 和 UTF-8 的编码,并且可以忽略掉一些不兼容 ASCII 的编码(比如 UTF-16 或者东亚的传统字符集),你可以对字节字符串进行正则表达式替换,把新的 <meta charset>
替换成旧的 http-equiv
格式。
否则,如果你需要一个更好的解决方案,最好的办法就是自己修补这个库(顺便把修复的代码贡献给大家)。你可以问问 lxml 的开发者,他们是否有针对这个特定问题的半成品代码,或者他们是否在他们的bug追踪系统中记录了这个问题。
lxml
这个库在处理Unicode(也就是我们常说的中文、日文等字符)时,有一些问题和讨论。所以现在最好是使用字节(bytes),同时明确指定字符编码,这样会更稳妥:
#!/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: 은 —’