Python 处理 UCS-2 (UTF-16?) 的笨拙方法转为 ASCII

2 投票
3 回答
6964 浏览
提问于 2025-04-16 13:18

我对这个问题有点无从下手,所以请原谅我用词不当。

我在Windows XP上用Python 2.7来运行这个程序。

我找到了一段Python代码,它可以读取一个日志文件,做一些处理,然后显示结果。

觉得这信息不够详细?好吧,我给你一个简化版:

#!/usr/bin/python

import re
import sys

class NotSupportedTOCError(Exception):
    pass

def filter_toc_entries(lines):
    while True:
        line = lines.next()
        if re.match(r""" \s* 
                   .+\s+ \| (?#track)
                \s+.+\s+ \| (?#start)
                \s+.+\s+ \| (?#length)
                \s+.+\s+ \| (?#start sec)
                \s+.+\s*$   (?#end sec)
                """, line, re.X):
            lines.next()
            break

    while True:
        line = lines.next()
        m = re.match(r"""
            ^\s*
            (?P<num>\d+)
            \s*\|\s*
            (?P<start_time>[0-9:.]+)
            \s*\|\s*
            (?P<length_time>[0-9:.]+)
            \s*\|\s*
            (?P<start_sector>\d+)
            \s*\|\s*
            (?P<end_sector>\d+)
            \s*$
            """, line, re.X)
        if not m:
            break
        yield m.groupdict()

def calculate_mb_toc_numbers(eac_entries):
    eac = list(eac_entries)
    num_tracks = len(eac)

    tracknums = [int(e['num']) for e in eac]
    if range(1,num_tracks+1) != tracknums:
        raise NotSupportedTOCError("Non-standard track number sequence: %s", tracknums)

    leadout_offset = int(eac[-1]['end_sector']) + 150 + 1
    offsets = [(int(x['start_sector']) + 150) for x in eac]
    return [1, num_tracks, leadout_offset] + offsets

f = open(sys.argv[1])

mb_toc_urlpart = "%20".join(str(x) for x in calculate_mb_toc_numbers(filter_toc_entries(f)))

print mb_toc_urlpart

这段代码在处理“简单”的文本日志文件时运行得很好(我想说是ASCII格式,虽然这可能不太准确,比如Notepad++显示的是ANSI格式)。

但是,这个脚本在某些日志文件上就不工作了(在这些情况下,Notepad++显示的是“UCS-2 Little Endian”)。

我遇到了以下错误:

Traceback (most recent call last):
  File "simple.py", line 55, in <module>
    mb_toc_urlpart = "%20".join(str(x) for x in calculate_mb_toc_numbers(filter_
toc_entries(f)))
  File "simple.py", line 49, in calculate_mb_toc_numbers
    leadout_offset = int(eac[-1]['end_sector']) + 150 + 1
IndexError: list index out of range

这个日志 可以正常工作

这个日志 就不行了

我觉得是编码问题导致脚本出错,因为如果我在命令提示符下这样做:

type ascii.log > scrubbed.log

然后在scrubbed.log上运行脚本,脚本就能正常工作(这对我来说没问题,因为没有丢失重要信息,而且我不需要写回文件,只是打印到控制台)。

一种解决办法是在把日志文件传给Python之前先“清理”一下(比如用上面提到的类型管道技巧生成一个临时文件,然后让脚本在那个文件上运行),但我希望Python能“忽略”编码问题,如果可能的话。我也不太确定怎么检测脚本正在读取什么类型的日志文件,以便采取相应的措施。

我在看 这个这个,但我还是觉得眼花缭乱,所以虽然这可能是我长期的解决方案,但我在想有没有什么临时的办法可以用。

3 个回答

0

Python 2.x 期望普通字符串是 ASCII 编码(或者至少是一个字节)。你可以试试这个:

在你的 Python 源文件的最顶部加上这段代码:

from __future__ import unicode_literals

然后把所有的 str 改成 unicode

[编辑]

正如 Ignacio Vazquez-Abrams 所说,可以试试用 codecs.open() 来打开输入文件。

6

codecs.open() 这个函数可以让你用特定的编码方式打开一个文件,并且它会生成 unicode 字符串。你可以尝试几种编码方式,从最可能用到的到最不可能用到的(或者这个工具可能总是生成 UTF-16LE,但这可不太可能哦,哈哈)。

另外,“Python中的Unicode,完全解密”

3

works.log 这个文件看起来是用 ASCII 编码的:

>>> data = open('works.log', 'rb').read()
>>> all(d < '\x80' for d in data)
True

breaks.log 这个文件看起来是用 UTF-16LE 编码的——它的开头有两个字节 '\xff\xfe'。在 breaks.log 中,没有任何字符超出了 ASCII 的范围:

>>> data = open('breaks.log', 'rb').read()
>>> data[:2]
'\xff\xfe'
>>> udata = data.decode('utf16')
>>> all(d < u'\x80' for d in udata)
True

如果这就是唯一的两种可能性,你可以用以下的方法来解决问题。把你的主代码从:

f = open(sys.argv[1])
mb_toc_urlpart = "%20".join(
    str(x) for x in calculate_mb_toc_numbers(filter_toc_entries(f)))
print mb_toc_urlpart

改成这样:

f = open(sys.argv[1], 'rb')
data = f.read()
f.close()
if data[:2] == '\xff\xfe':
    data = data.decode('utf16').encode('ascii')
# ilines is a generator which produces newline-terminated strings
ilines = (line + '\n' for line in data.splitlines())
mb_toc_urlpart = "%20".join(
    str(x) for x in calculate_mb_toc_numbers(filter_toc_entries(ilines))        )
print mb_toc_urlpart

撰写回答