这是我最近遇到过几次的问题。以下是我最近的经历:
尝试浏览https://www.scape.sc/release.php?id=48,包含日语文本的页面。此页面中的日语完全乱码,显示为 unicode 方块字符、符号和各种拉丁重音字符。即使在 html 源代码中也是如此,因此我认为这不是字体选择的问题。
网站使用我所理解的这篇 webhint.io 文章声明字符集的方法已经过时了<META Http-equiv="Content-Type" Content="text/html; charset=utf8">
。尽管文章确实提到,这在今天应该不成问题了。
当我在浏览器中访问它时,原始 html 看起来是这样的:
<TR><TD>2.</TD><TD>記憶ã¨ç©º</TD><TD> <I>(kioku to sora)</I></TD></TR>
过去,我发现在 Internet Archive 的 Wayback Machine 上搜索存在此问题的网站的旧版本可以正确显示日文字符。就我目前的情况而言也是如此。
以下两个来自 Wayback Machine 的示例,第一个链接来自 2016 年的捕获,页面源代码和呈现的页面都使用有效/未损坏的日语字符。第二个来自 2023 年,显示的乱码文本与我在自己的机器上看到的一样,这让我更加确信这不是我的问题。
2016 年的原始 html:
<tr><td>2.</td><td>記憶と空</td><td> <i>(kioku to sora)</i></td></tr>
2023 年的原始 html:
<tr><td>2.</td><td>記憶ã¨ç©º</td><td> <i>(kioku to sora)</i></td></tr>
我怀疑这是网站管理员的错误,可能是在 2016 年到现在的某个时间使用文本编辑器对网站进行更改时出现了字符集不匹配的情况。这听起来合理吗?有没有办法恢复“损坏的”unicode,避免依赖 Wayback Machine 上旧的网站捕获?
总结:网站过去包含有效的 Unicode,但现在不再如此。为什么会出现这样的问题?最终用户可以反转/使问题文本清晰易读吗?
答案1
文本是双重编码UTF-8。也就是说,UTF-8 数据被误解为旧式单字节编码之一(可能是 Windows-1252),然后再次从该编码转换为 UTF-8。(例如,記
UTF-8 中表示的相同字节也表示記
在 Windows-1252 中,并且这三个字符再次存储为 UTF-8。)
换句话说,这是网站管理员的错误。(实际上,我的猜测是他们升级了 MySQL 数据库服务器,因为当前版本处理 UTF-8 Unicode 字符串,而网站时代的 MySQL 4.x 过去处理“latin1”,这或多或少是原始字节值。这方面的一些证据是,侧边栏链接-közi-
显示不是双重编码,手写的艺术家页面也不是。在 MySQL 中,可以在数据库端、PHP 客户端甚至每个连接上设置编码;它是好简单最终导致不匹配并获取双重编码的文本,尤其是在较旧的 MySQL 配置中。)
浏览器通常不具备处理此类损坏的任何功能;据它们所知,字符集声明 100% 正确,输入数据错误。扩展或“用户脚本”(GreaseMonkey 样式)可能有效;您也可能能够从本地保存的页面中恢复文本。
恢复文本的大致过程如下:
- 获取原始 HTML。
- 将其传递给
iconv
其他编码转换器,指定 UTF-8 作为输入并指定 Windows-1252(或其他候选旧式代码页)作为输出。 - 输出现在应该是常规的 UTF-8。
在这种情况下,常规的 iconv 有点过于严格,Python 的 cp1252 编码也是如此,因为它们都拒绝使用 cp1252 中“未定义”的字符槽(例如将 U+0081 转换回字节 0x81),因此编码器需要稍微定制一下:
#!/bin/python3
import argparse
import codecs
import encodings.cp1252
# Patch Python runtime to replace U+FFFE ("undefined" indicator) with
# direct mappings to byte values, e.g. so that U+0081 becomes \x81
# instead of reporting an error.
tab = encodings.cp1252.decoding_table
tab = [tab[i].replace("\uFFFE", chr(i)) for i in range(256)]
tab = "".join(tab)
encodings.cp1252.decoding_table = tab
encodings.cp1252.encoding_table = codecs.charmap_build(tab)
parser = argparse.ArgumentParser()
parser.add_argument("file", nargs="+")
args = parser.parse_args()
for arg in args.file:
print("Processing:", arg)
with open(arg, "rb") as fh:
buf = fh.read()
# Undo double-encoding; the result of encode(cp1252) will
# actually be normal UTF-8.
buf = buf.decode("utf-8").encode("cp1252")
with open(arg + ".fixed", "wb") as fh:
fh.write(buf)
请注意,这会损坏-közi-
侧边栏链接,因为它根本没有经过双重编码。
该网站使用了我从这篇 webhint.io 文章中了解到的过时的字符集声明方法
该网站使用的方法完全适合其编写的时代。它并不完全“过时”,只是“不再是最方便的选择”,但仍然 100% 支持——与其余的部分页面的 HTML 4.01 标记(而文章讨论的是 HTML 5)。
无论如何,声明都是正确的;HTML 确实是用 UTF-8 编码的。它是什么采用 UTF-8 编码,这是实际的问题。
答案2
当前网站的文本实际上可能使用的是较旧的编码,例如 Shift JIS、EUC 或 ISO-2022-JP,而不是它声称的 UTF-8。
所以,如果情况确实如此,那么数据并没有损坏,只是不符合所宣传的编码。
一个提供识别某些日语编码提示的网站是https://www.sljfaq.org/afaq/encodings.html
有多种工具可以更改编码,例如 iconv 和 recode。我会先尝试这些。