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

无论你如何解码它,它仍然是坏的。

您的数据因连续犯了两个错误而受到破坏:解码不当,然后重新编码不当再次。正确的做法是将字节从服务器直接写入磁盘,而不是在任何时候将它们转换为字符串。