web-dev-qa-db-ja.com

ピクルファイルが大きすぎて読み込めません

私が抱えている問題は、開こうとしている非常に大きなピクルスファイル(2.6 Gb)があるのですが、開くたびにメモリエラーが発生することです。すべての情報を格納するためにデータベースを使用する必要があったのに、今では手遅れであることに気付きました。ピクルファイルには、インターネットからクロールされた米国議会レコードの日付とテキストが含まれています(実行に約2週間かかりました)。 pickleファイルにインクリメンタルにダンプした情報にアクセスする方法、またはpickleファイルをsqlデータベースに変換する方法、またはすべてのデータを再入力せずに開くことができる方法はありますか?議会の記録を再クロールしてデータベースにデータを入力するために、さらに2週間を費やす必要はありません。

あなたの助けをありがとう

編集*

オブジェクトがピクルス化される方法のコード:

def save_objects(objects): 
    with open('objects.pkl', 'wb') as output: 
        pickle.dump(objects, output, pickle.HIGHEST_PROTOCOL)

def Main():   
    Links()
    file = open("datafile.txt", "w")
    objects=[]
    with open('links2.txt', 'rb') as infile:
        for link in infile: 
            print link
            title,text,date=Get_full_text(link)
            article=Doccument(title,date,text)
            if text != None:
                write_to_text(date,text)
                objects.append(article)
                save_objects(objects)

これはエラーのあるプログラムです:

def Main():
    file= open('objects1.pkl', 'rb') 
    object = pickle.load(file)
17

少し漬け物になっているようです! ;-)。うまくいけば、この後、あなたは決してピクルを決して使用しないでしょう。それは非常に良いデータストレージフォーマットではありません。

とにかく、この答えについては、あなたのDocumentクラスがこのように見えると仮定しています。そうでない場合は、実際のDocumentクラスでコメントしてください:

class Document(object): # <-- object part is very important! If it's not there, the format is different!
    def __init__(self, title, date, text): # assuming all strings
        self.title = title
        self.date = date
        self.text = text

とにかく、私はこのクラスでいくつかの簡単なテストデータを作成しました。

d = [Document(title='foo', text='foo is good', date='1/1/1'), Document(title='bar', text='bar is better', date='2/2/2'), Document(title='baz', text='no one likes baz :(', date='3/3/3')]

形式2pickle.HIGHEST_PROTOCOL for Python 2.x)で漬けます

>>> s = pickle.dumps(d, 2)
>>> s
'\x80\x02]q\x00(c__main__\nDocument\nq\x01)\x81q\x02}q\x03(U\x04dateq\x04U\x051/1/1q\x05U\x04textq\x06U\x0bfoo is goodq\x07U\x05titleq\x08U\x03fooq\tubh\x01)\x81q\n}q\x0b(h\x04U\x052/2/2q\x0ch\x06U\rbar is betterq\rh\x08U\x03barq\x0eubh\x01)\x81q\x0f}q\x10(h\x04U\x053/3/3q\x11h\x06U\x13no one likes baz :(q\x12h\x08U\x03bazq\x13ube.'

そして、それをpickletoolsで逆アセンブルします:

>>> pickletools.dis(s)
    0: \x80 PROTO      2
    2: ]    EMPTY_LIST
    3: q    BINPUT     0
    5: (    MARK
    6: c        GLOBAL     '__main__ Document'
   25: q        BINPUT     1
   27: )        EMPTY_Tuple
   28: \x81     NEWOBJ
   29: q        BINPUT     2
   31: }        EMPTY_DICT
   32: q        BINPUT     3
   34: (        MARK
   35: U            SHORT_BINSTRING 'date'
   41: q            BINPUT     4
   43: U            SHORT_BINSTRING '1/1/1'
   50: q            BINPUT     5
   52: U            SHORT_BINSTRING 'text'
   58: q            BINPUT     6
   60: U            SHORT_BINSTRING 'foo is good'
   73: q            BINPUT     7
   75: U            SHORT_BINSTRING 'title'
   82: q            BINPUT     8
   84: U            SHORT_BINSTRING 'foo'
   89: q            BINPUT     9
   91: u            SETITEMS   (MARK at 34)
   92: b        BUILD
   93: h        BINGET     1
   95: )        EMPTY_Tuple
   96: \x81     NEWOBJ
   97: q        BINPUT     10
   99: }        EMPTY_DICT
  100: q        BINPUT     11
  102: (        MARK
  103: h            BINGET     4
  105: U            SHORT_BINSTRING '2/2/2'
  112: q            BINPUT     12
  114: h            BINGET     6
  116: U            SHORT_BINSTRING 'bar is better'
  131: q            BINPUT     13
  133: h            BINGET     8
  135: U            SHORT_BINSTRING 'bar'
  140: q            BINPUT     14
  142: u            SETITEMS   (MARK at 102)
  143: b        BUILD
  144: h        BINGET     1
  146: )        EMPTY_Tuple
  147: \x81     NEWOBJ
  148: q        BINPUT     15
  150: }        EMPTY_DICT
  151: q        BINPUT     16
  153: (        MARK
  154: h            BINGET     4
  156: U            SHORT_BINSTRING '3/3/3'
  163: q            BINPUT     17
  165: h            BINGET     6
  167: U            SHORT_BINSTRING 'no one likes baz :('
  188: q            BINPUT     18
  190: h            BINGET     8
  192: U            SHORT_BINSTRING 'baz'
  197: q            BINPUT     19
  199: u            SETITEMS   (MARK at 153)
  200: b        BUILD
  201: e        APPENDS    (MARK at 5)
  202: .    STOP

複雑に見えます!しかし、実際には、それほど悪くはありません。 pickleは基本的にスタックマシンであり、表示される各ALL_CAPS識別子はopcodeであり、内部の「スタック」を何らかの方法で操作してデコードします。いくつかの複雑な構造を解析しようとした場合、これはより重要になりますが、幸いにも、本質的にタプルの単純なリストを作成しています。この「コード」が行っていることは、スタック上に一連のオブジェクトを作成し、スタック全体をリストにプッシュすることです。

注意する必要があるのは、散在している「BINPUT」/「BINGET」オペコードです。基本的に、これらは「メモ化」のためのものであり、データのフットプリントを減らすために、pickleは文字列をBINPUT <id>で保存し、それらが再度表示される場合は、再ダンプするのではなく、単にBINGET <id>を使用してキャッシュから取得します。

また、別の合併症! SHORT_BINSTRINGだけではありません。256バイトを超える文字列には通常のBINSTRINGがあり、いくつかの楽しいunicodeバリアントもあります。 Python 2 with all ASCII stringsを使用していると仮定します。これも正しい仮定でない場合はコメントしてください。

OK、「\ 81」バイト(NEWOBJ)に達するまでファイルをストリーミングする必要があります。次に、 '('(MARK)文字に到達するまで前方にスキャンする必要があります。次に、 'u'(SETITEMS)に到達するまで、キーと値の文字列のペアを読み取ります-各フィールドに1つずつ、合計3つのペアが必要です。

だから、これをやってみましょう。以下は、ストリーミング方式でピクルスデータを読み取るためのスクリプトです。私はこの回答のために一緒にハッキングしただけなので、完璧には程遠いです、そしてあなたはそれをたくさん修正する必要がありますが、それは良いスタートです。

pickledata = '\x80\x02]q\x00(c__main__\nDocument\nq\x01)\x81q\x02}q\x03(U\x04dateq\x04U\x051/1/1q\x05U\x04textq\x06U\x0bfoo is goodq\x07U\x05titleq\x08U\x03fooq\tubh\x01)\x81q\n}q\x0b(h\x04U\x052/2/2q\x0ch\x06T\x14\x05\x00\x00bar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterq\rh\x08U\x03barq\x0eubh\x01)\x81q\x0f}q\x10(h\x04U\x053/3/3q\x11h\x06U\x13no one likes baz :(q\x12h\x08U\x03bazq\x13ube.'

# simulate a file here
import StringIO
picklefile = StringIO.StringIO(pickledata)

import pickle # just for opcode names
import struct # binary unpacking

def try_memo(f, v, cache):
    opcode = f.read(1)
    if opcode == pickle.BINPUT:
        cache[f.read(1)] = v
    Elif opcode == pickle.LONG_BINPUT:
        print 'skipping LONG_BINPUT to save memory, LONG_BINGET will probably not be used'
        f.read(4)
    else:
        f.seek(f.tell() - 1) # rewind

def try_read_string(f, opcode, cache):
    if opcode in [ pickle.SHORT_BINSTRING, pickle.BINSTRING ]:
        length_type = 'b' if opcode == pickle.SHORT_BINSTRING else 'i'
        str_length = struct.unpack(length_type, f.read(struct.calcsize(length_type)))[0]
        value = f.read(str_length)
        try_memo(f, value, memo_cache)
        return value
    Elif opcode == pickle.BINGET:
        return memo_cache[f.read(1)]
    Elif opcide == pickle.LONG_BINGET:
        raise Exception('Unexpected LONG_BINGET? Key ' + f.read(4))
    else:
        raise Exception('Invalid opcode ' + opcode + ' at pos ' + str(f.tell()))

memo_cache = {}
while True:
    c = picklefile.read(1)
    if c == pickle.NEWOBJ:
        while picklefile.read(1) != pickle.MARK:
            pass # scan forward to field instantiation
        fields = {}
        while True:
            opcode = picklefile.read(1)
            if opcode == pickle.SETITEMS:
                break
            key = try_read_string(picklefile, opcode, memo_cache)
            value = try_read_string(picklefile, picklefile.read(1), memo_cache)
            fields[key] = value
        print 'Document', fields
        # insert to sqllite
    Elif c == pickle.STOP:
        break

これは、ピクル形式2(長い文字列を持つように変更)でテストデータを正しく読み取ります。

$ python picklereader.py
Document {'date': '1/1/1', 'text': 'foo is good', 'title': 'foo'}
Document {'date': '2/2/2', 'text': 'bar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is betterbar is better', 'title': 'bar'}
Document {'date': '3/3/3', 'text': 'no one likes baz :(', 'title': 'baz'}

幸運を!

34
vgel

データを段階的に漬けませんでした。データをモノリシックかつ繰り返しピクルスにしました。ループを回るたびに、すべての出力データを破棄し(open(...,'wb')は出力ファイルを破棄し)、すべてのデータを再度書き直しました。さらに、プログラムが停止して新しい入力データで再起動した場合、古い出力データは失われました。

ピクル化しているときにobjectsがメモリ不足エラーを引き起こさなかった理由はわかりません。pickle.load()が作成するオブジェクトと同じサイズに成長したためです。

これは、pickleファイルを段階的に作成する方法です。

def save_objects(objects): 
    with open('objects.pkl', 'ab') as output:  # Note: `ab` appends the data
        pickle.dump(objects, output, pickle.HIGHEST_PROTOCOL)

def Main():
    ...
    #objects=[] <-- lose the objects list
    with open('links2.txt', 'rb') as infile:
        for link in infile: 
            ... 
            save_objects(article)

次に、pickleファイルを次のようにインクリメンタルに読み取ることができます。

import pickle
with open('objects.pkl', 'rb') as pickle_file:
    try:
        while True:
            article = pickle.load(pickle_file)
            print article
    except EOFError:
        pass

私が考えることができる選択肢は次のとおりです。

  • CPickleをお試しください。役立つかもしれません。
  • ストリーミングピクルスを試す
  • 大量のRAMを備えた64ビット環境でピクルスファイルを読み取る
  • 元のデータを再クロールします。今回は実際にデータを増分的に保存するか、データベースに保存します。 pickle出力ファイルを絶えず再書き込みする非効率性がないと、今回はクロールが大幅に速くなる可能性があります。
10
Robᵩ

最近、非常に似たようなケースがありました-11 GBピクルス。自分のインクリメンタルローダーを実装したり、既存のローダーを調整したりするのに十分な時間がなかったため、マシンに段階的にロードしようとしませんでした。

私がしたことは、クラウドホスティングプロバイダーで十分なメモリを備えた大きなインスタンスを起動し(数時間などの短時間だけ起動した場合、価格はそれほど高くありません)、そのファイルをSSH(SCP)を介してそのサーバーにアップロードし、単にロードしただけです。そのインスタンスでそれを分析し、そこでより適切な形式に書き直します。

プログラミングソリューションではありませんが、時間効果があります(少ない労力)。

0
Zbyszek