Python lxml 和字符串编码问题
Python lxml & string encoding issue
我正在使用 lxml 从 html 文档中提取文本,但我无法从文本中获取某些字符以正确呈现。这可能是一件愚蠢的事情,但我似乎无法找到解决方案...
这是 html 的简化版本:
<html>
<head>
<meta content="text/html" charset="UTF-8"/>
</head>
<body>
<p>DAÑA – bis'e</p> <!---that's an N dash and the single quote is curly--->
</body
</html
代码的简化版本:
import lxml.html as LH
htmlfile = "path/to/file"
tree = LH.parse(htmlfile)
root = tree.getroot()
for para in root.iter("p"):
print(para.text)
我终端的输出有那些带有字符错误的小方框(例如,
应该是“-E”),但是如果我从那里复制粘贴到这里,它看起来像:
>>> DAÃO bisâe
如果我在终端中执行一个简单的 echo
+ 问题字符,它们会正确呈现,所以我认为这不是问题所在。
html 编码为 UTF-8(已用 docinfo
检查)。我在代码的不同地方尝试了 .encode() 和 .decode() 。我还尝试了带有 utf-8 声明的 lxml.etree.tostring() (但是 .iter() 不起作用('bytes' 对象没有属性 'iter'),或者如果我把它放在代码中的端节点,.text 不起作用('bytes' 对象没有属性 'text'))。
任何想法出了什么问题and/or如何解决?
我发现 unidecode
包可以很好地将非 ASCII 字符转换为最接近的 ASCII。
from unidecode import unidecode
def check_ascii(in_string):
if in_string.isascii(): # Available in python 3.7+
return in_string
else:
return unidecode(in_string) # Converts non-ascii characters to the closest ascii
然后,如果您认为某些文本可能包含非 ascii 字符,您可以将其传递给上述函数。在您的情况下,在提取 html 标签之间的文本后,您可以将其传递给:
for para in root.iter("p"):
print(check_ascii(para.text))
您可以在此处找到有关该软件包的详细信息:https://pypi.org/project/Unidecode/
使用正确的编码打开文件(我在这里假设为 UTF-8,查看 HTML 文件以确认)。
import lxml.html as LH
with open("path/to/file", encoding="utf8") as f:
tree = LH.parse(f)
root = tree.getroot()
for para in root.iter("p"):
print(para.text)
背景说明 你是如何到达现在的位置的。
来自服务器的传入数据:
Bytes (hex) Decoded as Result String Comment
44 41 C3 91 4F UTF-8 DAÑO proper decode
44 41 C3 91 4F Latin-1 DAÃ▯O improper decode
字节不应该被解码为 Latin-1,这是一个错误。
C3 91
表示 UTF-8 中的一个字符(Ñ),但它是 Latin-1 中的两个字符(Ã 和字节 91)。但是第91字节是unused in Latin-1,所以没有字符可以显示。我用 ▯ 让它可见。文本编辑器可能会完全跳过它,而是显示 DAÃO
,或者一个奇怪的框,或者一个错误标记。
将解码不正确的字符串写入文件时:
String Encoded as Result Bytes (hex) Comment
DAÃ▯O UTF-8 44 41 C3 83 C2 91 4F weird box preserved as C2 91
此时不应将字符串编码为 UTF-8,这也是一个错误。
Ã
已转换为 C3 83
,这对于 UTF-8 中的此字符是正确的。请注意字节序列现在如何与您在评论中告诉我的相匹配 (\xc3\x83\xc2\x91
)。
读取该文件时:
Bytes (hex) Decoded as Result String Comment
44 41 C3 83 C2 91 4F UTF-8 DAÃ▯O unprintable character is retained
44 41 C3 83 C2 91 4F Latin-1 DAÃÂ▯O unprintable character is retained
无论你如何解码它,它仍然是坏的。
您的数据因连续犯了两个错误而受到破坏:解码不当,然后重新编码不当再次。正确的做法是将字节从服务器直接写入磁盘,而不是在任何时候将它们转换为字符串。
我正在使用 lxml 从 html 文档中提取文本,但我无法从文本中获取某些字符以正确呈现。这可能是一件愚蠢的事情,但我似乎无法找到解决方案...
这是 html 的简化版本:
<html>
<head>
<meta content="text/html" charset="UTF-8"/>
</head>
<body>
<p>DAÑA – bis'e</p> <!---that's an N dash and the single quote is curly--->
</body
</html
代码的简化版本:
import lxml.html as LH
htmlfile = "path/to/file"
tree = LH.parse(htmlfile)
root = tree.getroot()
for para in root.iter("p"):
print(para.text)
我终端的输出有那些带有字符错误的小方框(例如,
应该是“-E”),但是如果我从那里复制粘贴到这里,它看起来像:
>>> DAÃO bisâe
如果我在终端中执行一个简单的 echo
+ 问题字符,它们会正确呈现,所以我认为这不是问题所在。
html 编码为 UTF-8(已用 docinfo
检查)。我在代码的不同地方尝试了 .encode() 和 .decode() 。我还尝试了带有 utf-8 声明的 lxml.etree.tostring() (但是 .iter() 不起作用('bytes' 对象没有属性 'iter'),或者如果我把它放在代码中的端节点,.text 不起作用('bytes' 对象没有属性 'text'))。
任何想法出了什么问题and/or如何解决?
我发现 unidecode
包可以很好地将非 ASCII 字符转换为最接近的 ASCII。
from unidecode import unidecode
def check_ascii(in_string):
if in_string.isascii(): # Available in python 3.7+
return in_string
else:
return unidecode(in_string) # Converts non-ascii characters to the closest ascii
然后,如果您认为某些文本可能包含非 ascii 字符,您可以将其传递给上述函数。在您的情况下,在提取 html 标签之间的文本后,您可以将其传递给:
for para in root.iter("p"):
print(check_ascii(para.text))
您可以在此处找到有关该软件包的详细信息:https://pypi.org/project/Unidecode/
使用正确的编码打开文件(我在这里假设为 UTF-8,查看 HTML 文件以确认)。
import lxml.html as LH
with open("path/to/file", encoding="utf8") as f:
tree = LH.parse(f)
root = tree.getroot()
for para in root.iter("p"):
print(para.text)
背景说明 你是如何到达现在的位置的。
来自服务器的传入数据:
Bytes (hex) Decoded as Result String Comment 44 41 C3 91 4F UTF-8 DAÑO proper decode 44 41 C3 91 4F Latin-1 DAÃ▯O improper decode
字节不应该被解码为 Latin-1,这是一个错误。
C3 91
表示 UTF-8 中的一个字符(Ñ),但它是 Latin-1 中的两个字符(Ã 和字节 91)。但是第91字节是unused in Latin-1,所以没有字符可以显示。我用 ▯ 让它可见。文本编辑器可能会完全跳过它,而是显示 DAÃO
,或者一个奇怪的框,或者一个错误标记。
将解码不正确的字符串写入文件时:
String Encoded as Result Bytes (hex) Comment DAÃ▯O UTF-8 44 41 C3 83 C2 91 4F weird box preserved as C2 91
此时不应将字符串编码为 UTF-8,这也是一个错误。
Ã
已转换为 C3 83
,这对于 UTF-8 中的此字符是正确的。请注意字节序列现在如何与您在评论中告诉我的相匹配 (\xc3\x83\xc2\x91
)。
读取该文件时:
Bytes (hex) Decoded as Result String Comment 44 41 C3 83 C2 91 4F UTF-8 DAÃ▯O unprintable character is retained 44 41 C3 83 C2 91 4F Latin-1 DAÃÂ▯O unprintable character is retained
无论你如何解码它,它仍然是坏的。
您的数据因连续犯了两个错误而受到破坏:解码不当,然后重新编码不当再次。正确的做法是将字节从服务器直接写入磁盘,而不是在任何时候将它们转换为字符串。