【问题标题】:Python lxml & string encoding issuePython lxml & 字符串编码问题
【发布时间】:2020-04-04 12:23:05
【问题描述】:

我正在使用 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”),但是如果我从那里复制粘贴到这里,它看起来像:

&gt;&gt;&gt; DAÃO bisâe

如果我在终端中执行简单的echo + 问题字符,它们会正确呈现,所以我认为这不是问题。

html 编码为 UTF-8(检查 docinfo)。我已经在代码的各个地方尝试过 .encode() 和 .decode() 。我还尝试了带有 utf-8 声明的 lxml.etree.tostring()(但是 .iter() 不起作用('bytes' 对象没有属性 'iter'),或者如果我把它放在代码,.text 不起作用('bytes' 对象没有属性 'text'))。

任何想法出了什么问题和/或如何解决?

【问题讨论】:

  • 我自己编写了几个基本网站,奇怪的是这些字符,ñ - 等,首先在源 html 中。我希望它们不会在浏览器中正确呈现,但确实如此。也许这与问题有关。

标签: python character-encoding lxml


【解决方案1】:

使用正确的编码打开文件(我这里假设为 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)

背景说明说明您是如何到达当前位置的。

来自服务器的传入数据:

字节(十六进制)解码为结果字符串注释 44 41 C3 91 4F UTF-8 DAÑO 正确解码 44 41 C3 91 4F Latin-1 DAÃ▯O 解码错误

这些字节不应该被解码为 Latin-1,这是一个错误。

C3 91 表示 UTF-8 中的一个字符(Ñ),但它是 Latin-1 中的两个字符(Ã,和字节 91)。但是字节 91 是unused in Latin-1,所以没有字符可以显示。我拿了▯让它可见。文本编辑器可能会完全跳过它,而是显示DAÃO,或者一个奇怪的框,或者一个错误标记。

将解码错误的字符串写入文件时:

字符串编码为结果字节(十六进制) DAÃ▯O UTF-8 44 41 C3 83 C2 91 4F 奇怪的盒子保存为 C2 91

此时字符串不应该被编码为 UTF-8,这也是一个错误。

à 被转换为 C3 83,这对于 UTF-8 中的这个字符是正确的。请注意字节序列现在与您在 cmets (\xc3\x83\xc2\x91) 中告诉我的内容相匹配。

读取该文件时:

字节(十六进制)解码为结果字符串注释 44 41 C3 83 C2 91 4F UTF-8 DAÃ▯O 不可打印字符被保留 44 41 C3 83 C2 91 4F Latin-1 DAÃÂ▯O 保留不可打印字符

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

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

【讨论】:

  • 不,这并不奇怪。 HTML 支持所选文件编码可以表示的任何字符。 ñ&amp;ntilde; &amp;ntilde; 100%同样的事情,但后者可能更容易输入,因为您没有键盘布局键入实际ñ,或者选择的文件编码不能表示实际ñ(例如,将 ñ 转换为 ASCII 编码文件)。
  • 很难说你的文件是什么编码的(它可能已经被破坏了,这取决于文件最初是如何产生的)。只有查看相关位置的字节才能澄清这一点。
  • 你刮掉了它们?如何?你是如何拯救他们的?文本编辑器检测到什么文件编码?另外,告诉我违规位置的字节序列。
  • 是的,文件已经损坏。你能分享你下载它的 URL 和你用来保存它的命令行吗?
  • 这是我认为在下载过程中发生的事情。最初数据是正确的 UTF-8,被解释为 Latin-1,被写入文件为 UTF-8,因此最终出现乱码。以 UTF-8 格式读取文件无法解决此问题。很可能由于保存文件时的错误,错误就在您身边,这就是为什么知道您是如何做到这一点很重要的。 (可以在读取文件的同时修复文件,但首先不要破坏它更明智)
【解决方案2】:

我发现 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/

【讨论】:

  • 感谢您的回答。这只在摆脱那些丑陋的盒子的意义上才有效,但字符渲染仍然是错误的。 Ñ 变成 A,' 变成 a,ñ 变成 A+-,ndash 完全消失。
  • 您不希望将文件转换为 ASCII。
  • 我明白了,很抱歉,那可能是原始来源的问题。 @Tomalak 的想法不是将文件转换为 ASCII,而是提取的文本
猜你喜欢
  • 1970-01-01
  • 2014-01-08
  • 2012-02-22
  • 1970-01-01
  • 1970-01-01
  • 2013-04-21
  • 1970-01-01
  • 2022-10-23
  • 1970-01-01
相关资源
最近更新 更多