12

以前,我一直在使用下面的代码片段清理数据

import unicodedata, re, io

all_chars = (unichr(i) for i in xrange(0x110000))
control_chars = ''.join(c for c in all_chars if unicodedata.category(c)[0] == 'C')
cc_re = re.compile('[%s]' % re.escape(control_chars))
def rm_control_chars(s): # see http://www.unicode.org/reports/tr44/#General_Category_Values
    return cc_re.sub('', s)

cleanfile = []
with io.open('filename.txt', 'r', encoding='utf8') as fin:
    for line in fin:
        line =rm_control_chars(line)
        cleanfile.append(line)

我要保留的文件中有换行符。

以下记录了cc_re.sub('', s)替换前几行所花费的时间(第一列是花费的时间,第二列是len(s)):

0.275146961212 251
0.672796010971 614
0.178567171097 163
0.200030088425 180
0.236430883408 215
0.343492984772 313
0.317672967911 290
0.160616159439 142
0.0732028484344 65
0.533437013626 468
0.260229110718 236
0.231380939484 204
0.197766065598 181
0.283867120743 258
0.229172945023 208

正如@ashwinichaudhary 建议的那样,使用s.translate(dict.fromkeys(control_chars))和同时使用的日志输出:

0.464188098907 252
0.366552114487 615
0.407374858856 164
0.322507858276 181
0.35142993927 216
0.319973945618 314
0.324357032776 291
0.371646165848 143
0.354818105698 66
0.351796150208 469
0.388131856918 237
0.374715805054 205
0.363368988037 182
0.425950050354 259
0.382766962051 209

但是对于我的 1GB 文本来说,代码真的很慢。有没有其他方法可以清除受控字符?

4

6 回答 6

7

通过字符找到了一个解决方案工作字符,我使用 100K 文件对其进行了基准标记:

import unicodedata, re, io
from time import time

# This is to generate randomly a file to test the script

from string import lowercase
from random import random

all_chars = (unichr(i) for i in xrange(0x110000))
control_chars = [c for c in all_chars if unicodedata.category(c)[0] == 'C']
chars = (list(u'%s' % lowercase) * 115117) + control_chars

fnam = 'filename.txt'

out=io.open(fnam, 'w')

for line in range(1000000):
    out.write(u''.join(chars[int(random()*len(chars))] for _ in range(600)) + u'\n')
out.close()


# version proposed by alvas
all_chars = (unichr(i) for i in xrange(0x110000))
control_chars = ''.join(c for c in all_chars if unicodedata.category(c)[0] == 'C')
cc_re = re.compile('[%s]' % re.escape(control_chars))
def rm_control_chars(s):
    return cc_re.sub('', s)

t0 = time()
cleanfile = []
with io.open(fnam, 'r', encoding='utf8') as fin:
    for line in fin:
        line =rm_control_chars(line)
        cleanfile.append(line)
out=io.open(fnam + '_out1.txt', 'w')
out.write(''.join(cleanfile))
out.close()
print time() - t0

# using a set and checking character by character
all_chars = (unichr(i) for i in xrange(0x110000))
control_chars = set(c for c in all_chars if unicodedata.category(c)[0] == 'C')
def rm_control_chars_1(s):
    return ''.join(c for c in s if not c in control_chars)

t0 = time()
cleanfile = []
with io.open(fnam, 'r', encoding='utf8') as fin:
    for line in fin:
        line = rm_control_chars_1(line)
        cleanfile.append(line)
out=io.open(fnam + '_out2.txt', 'w')
out.write(''.join(cleanfile))
out.close()
print time() - t0

输出是:

114.625444174
0.0149750709534

我尝试了一个 1Gb 的文件(仅用于第二个),它持续了 186 秒。

我还编写了同一脚本的另一个版本,速度稍快(176 秒),内存效率更高(对于不适合 RAM 的非常大的文件):

t0 = time()
out=io.open(fnam + '_out5.txt', 'w')
with io.open(fnam, 'r', encoding='utf8') as fin:
    for line in fin:
        out.write(rm_control_chars_1(line))
out.close()
print time() - t0
于 2015-05-20T10:44:24.380 回答
5

与 UTF-8 一样,所有控制字符都编码为 1 个字节(与 ASCII 兼容)并且低于 32,我建议使用这段快速代码:

#!/usr/bin/python
import sys

ctrl_chars = [x for x in range(0, 32) if x not in (ord("\r"), ord("\n"), ord("\t"))]
filename = sys.argv[1]

with open(filename, 'rb') as f1:
  with open(filename + '.txt', 'wb') as f2:
    b = f1.read(1)
    while b != '':
      if ord(b) not in ctrl_chars:
        f2.write(b)
      b = f1.read(1)

够好吗?

于 2015-05-26T14:35:20.417 回答
4

这必须在python中吗?在开始使用 python 读取文件之前如何清理文件。使用 sed 无论如何都会逐行处理它。

请参阅使用 sed删除控制字符。

如果您将其输出到另一个文件,您可以打开它。我不知道它会有多快。您可以在 shell 脚本中执行此操作并对其进行测试。根据这个页面- sed 是每秒 82M 个字符。

希望能帮助到你。

于 2015-05-26T20:41:13.613 回答
3

如果你想让它移动得非常快?将您的输入分成多个块,将该数据处理代码包装为一种方法,并使用 Python 的multiprocessing包将其并行化,写入一些常见的文本文件。逐个字符地处理这样的事情是最简单的方法,但它总是需要一段时间。

https://docs.python.org/3/library/multiprocessing.html

于 2015-05-26T22:51:17.540 回答
1

我很惊讶没有人提到mmap可能正好适合这里。

注意:如果它有用,我会将其作为答案,并抱歉我现在没有时间实际测试和比较它。

您将文件加载到内存中(有点),然后您实际上可以re.sub()在对象上运行 a 。这有助于消除 IO 瓶颈,并允许您在立即写回字节之前就地更改字节。

在此之后,您可以尝试使用 str.translate() 与 re.sub() 并包括任何进一步的优化,例如双缓冲 CPU 和 IO 或使用多个 CPU 内核/线程。

但它看起来像这样;

import mmap

f = open('test.out', 'r')
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

mmap 文档的一个很好的摘录是;

..您可以在大多数需要字符串的地方使用 mmap 对象;例如,您可以使用 re 模块搜索内存映射文件。由于它们是可变的,因此您可以通过执行 obj[index] = 'a',.. 来更改单个字符

于 2015-05-27T07:26:29.253 回答
0

我会尝试几件事。

首先,使用替换所有正则表达式进行替换。

其次,设置一个具有已知控制字符范围的正则表达式字符类,而不是
一个单独的控制字符类。
(这是在引擎没有将其优化到范围
的情况下。范围需要在程序集级别上有两个条件,
而不是在类中的每个字符上单独条件)

第三,由于您要删除字符,请
在类后添加一个贪婪量词。
这将消除在每个单个字符匹配后进入替换子例程的必要性,而是
根据需要获取所有相邻的字符。

我不知道正则表达式构造的 python 语法,也不知道
Unicode 中的所有控制代码,但结果看起来
像这样:

[\u0000-\u0009\u000B\u000C\u000E-\u001F\u007F]+

最多的时间是将结果复制到另一个字符串。
最少的时间将是找到所有控制代码,这
将是微不足道的。

在所有条件相同的情况下,正则表达式(如上所述)是最快的方法。

于 2015-05-20T17:20:50.510 回答