如何在Python中解析大于100GB的文件?

时间:2014-10-22 08:27:58

标签: python parsing large-files

我有大约100 Gb大小的文本文件,格式如下(行和ips和域的重复记录):

domain|ip 
yahoo.com|89.45.3.5
bbc.com|45.67.33.2
yahoo.com|89.45.3.5
myname.com|45.67.33.2
etc.

我正在尝试使用以下python代码解析它们但我仍然遇到内存错误。有人知道解析这些文件的更优化方法吗? (时间对我来说是一个重要因素)

files = glob(path)
for filename in files:
    print(filename)
    with open(filename) as f:
        for line in f:
            try:
                domain = line.split('|')[0]
                ip = line.split('|')[1].strip('\n')
                if ip in d:
                    d[ip].add(domain)
                else:
                    d[ip] = set([domain])
            except:
                print (line)
                pass

    print("this file is finished")

for ip, domains in d.iteritems():
    for domain in domains:
        print("%s|%s" % (ip, domain), file=output)

3 个答案:

答案 0 :(得分:3)

Python对象比磁盘上的相同值占用更多内存;引用计数中有一点开销,并且在集合中还要考虑每个值的缓存哈希值。

不要将所有这些对象都读入(Python)内存;请改用数据库。 Python附带了一个SQLite数据库库,用于将文件转换为数据库。然后,您可以从以下内容构建输出文件:

import csv
import sqlite3
from itertools import islice

conn = sqlite3.connect('/tmp/ipaddresses.db')
conn.execute('CREATE TABLE IF NOT EXISTS ipaddress (domain, ip)')
conn.execute('''\
    CREATE UNIQUE INDEX IF NOT EXISTS domain_ip_idx 
    ON ipaddress(domain, ip)''')

for filename in files:
    print(filename)
    with open(filename, 'rb') as f:
        reader = csv.reader(f, delimiter='|')
        cursor = conn.cursor()
        while True:
            with conn:
                batch = list(islice(reader, 10000))
                if not batch:
                    break
                cursor.executemany(
                    'INSERT OR IGNORE INTO ipaddress VALUES(?, ?)',
                    batch)

conn.execute('CREATE INDEX IF NOT EXISTS ip_idx ON ipaddress(ip)')
with open(outputfile, 'wb') as outfh:
    writer = csv.writer(outfh, delimiter='|')
    cursor = conn.cursor()
    cursor.execute('SELECT ip, domain from ipaddress order by ip')
    writer.writerows(cursor)

它以10000的批量处理您的输入数据,并生成一个索引,以便在插入后对进行排序。制作索引需要一些时间,但它们都适合您的可用内存。

在开始时创建的UNIQUE索引确保只插入唯一的域 - IP地址对(因此每个ip地址只跟踪唯一的域); INSERT OR IGNORE语句会跳过数据库中已存在的任何对。

简短演示,只提供您提供的示例输入:

>>> import sqlite3
>>> import csv
>>> import sys
>>> from itertools import islice
>>> conn = sqlite3.connect('/tmp/ipaddresses.db')
>>> conn.execute('CREATE TABLE IF NOT EXISTS ipaddress (domain, ip)')
<sqlite3.Cursor object at 0x106c62730>
>>> conn.execute('''\
...     CREATE UNIQUE INDEX IF NOT EXISTS domain_ip_idx 
...     ON ipaddress(domain, ip)''')
<sqlite3.Cursor object at 0x106c62960>
>>> reader = csv.reader('''\
... yahoo.com|89.45.3.5
... bbc.com|45.67.33.2
... yahoo.com|89.45.3.5
... myname.com|45.67.33.2
... '''.splitlines(), delimiter='|')
>>> cursor = conn.cursor()
>>> while True:
...     with conn:
...         batch = list(islice(reader, 10000))
...         if not batch:
...             break
...         cursor.executemany(
...             'INSERT OR IGNORE INTO ipaddress VALUES(?, ?)',
...             batch)
... 
<sqlite3.Cursor object at 0x106c62810>
>>> conn.execute('CREATE INDEX IF NOT EXISTS ip_idx ON ipaddress(ip)')
<sqlite3.Cursor object at 0x106c62960>
>>> writer = csv.writer(sys.stdout, delimiter='|')
>>> cursor = conn.cursor()
>>> cursor.execute('SELECT ip, domain from ipaddress order by ip')
<sqlite3.Cursor object at 0x106c627a0>
>>> writer.writerows(cursor)
45.67.33.2|bbc.com
45.67.33.2|myname.com
89.45.3.5|yahoo.com

答案 1 :(得分:3)

另一种更简单的解决方案可能是使用sort(1)实用程序:

sort input -u -t\| -k2 -T . --batch-size=50 --buffer-size=1G > output 

这将按第二列对文件进行排序,其中列由|分隔; -T将临时文件的目录设置为当前目录,默认为/tmp/,通常是内存设备。 -u标志删除重复项,其他标志可能(或可能不会)提高性能。

我用5.5GB文件测试了这个,我的笔记本电脑花了大约200秒;我不知道排名与其他解决方案的排名有多好。使用其他--batch-size--buffer-size

,您也可以获得更好的效果

无论如何,这当然是最简单的解决方案,因为它根本不需要编程:)

答案 2 :(得分:1)

在考虑使用多处理之前,我会将线分成不同的间隔。

  • 计算文件内的行数
  l= len(files.readlines())
  #l= sum(1 for _ in files)

然后,将您的工作划分到不同的阶段,并处理数据,并考虑两个方面:

  1. 将数据加载/存储到文件(使用DB,CVS,Json ..),无论您觉得它有用。
  2. 将数据处理工作划分到不同的阶段,每次增加您处理的行数,直到完成工作(重用您编写的代码);
  3. nbrIterations = l // step

    • 将代码打包到一个给出数字间隔的函数中,并且每次都增加它。
     def dataProcessing(numberOfLine) :
    
        if (numberOfLine>l):
            print("this file is finished")
            return False
        else: 
    
            files = glob(path)
            for filename in files:
                print(filename)
                with open(filename) as f:
                    for line in f:
                        if line>numberOfLine and line numberOfLine<numberOfLine+step:
    
                            domain = line.split('|')[0]
                            ip = line.split('|')[1].strip('\n')
                            if ip in d:
                                d[ip].add(domain)
                            else:
                                d[ip] = set([domain])
    
                        for ip, domains in d.iteritems():
                            for domain in domains:
                                # Better to store it in another file (or load to DB) using Pandas(load it to CSV) or DB Connector to load it to DB
                                print("%s|%s" % (ip, domain), file=output)
            return True
    
    • 定义您的“步骤”,以便您可以浏览文件行
    while dataProcessing(numberOfLine): 
      numberOfLine+=step
    

    您的第二个选择是探索多重处理的可能性(取决于您的机器性能)。