私が抱えている問題は、開こうとしている非常に大きなピクルスファイル(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)
少し漬け物になっているようです! ;-)。うまくいけば、この後、あなたは決してピクルを決して使用しないでしょう。それは非常に良いデータストレージフォーマットではありません。
とにかく、この答えについては、あなたの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')]
形式2
(pickle.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'}
幸運を!
データを段階的に漬けませんでした。データをモノリシックかつ繰り返しピクルスにしました。ループを回るたびに、すべての出力データを破棄し(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
私が考えることができる選択肢は次のとおりです。
最近、非常に似たようなケースがありました-11 GBピクルス。自分のインクリメンタルローダーを実装したり、既存のローダーを調整したりするのに十分な時間がなかったため、マシンに段階的にロードしようとしませんでした。
私がしたことは、クラウドホスティングプロバイダーで十分なメモリを備えた大きなインスタンスを起動し(数時間などの短時間だけ起動した場合、価格はそれほど高くありません)、そのファイルをSSH(SCP)を介してそのサーバーにアップロードし、単にロードしただけです。そのインスタンスでそれを分析し、そこでより適切な形式に書き直します。
プログラミングソリューションではありませんが、時間効果があります(少ない労力)。