MemoryError対策でなんとか読出OK

2019年2月13日Python, プログラミング

AmazonAPIをPythonからうまく叩けていると思っていたプログラムですが、ずーっと動かしているとMemmoryErrorというもので止まってしまいました。

MemoryErrorの原因

エラー箇所を探っていくと、どうやらBeautifulSoup4の動作中にMemoryErrorを吐いていました。

ソースの中にprintを細かく仕込んでみていったのですが、結局、Pythonで設定している使用メモリ制限を1GBまであげたらエラー吐かなくなりました。そのあたり、どのようなメカニズムで動いているのか今後調べていきたいです。

具体的な変更箇所
# soft は初期メモリ量と、追加時のメモリ量。
softmem = 512 * 1024 * 1024 # 64MB
# hard は上限値。
hardmem = 1024 * 1024 * 1024 # 512MB
# メモリの上限を設定する。単位はバイト。
resource.setrlimit(resource.RLIMIT_DATA, (softmem, hardmem))

とりあえず、特定ノードだけはエラーなくMySQLに登録できました。件数としては1900件くらい。

そのあと、全体を収集しようと思ったらMemory Errorを吐いてまた止まりましたorz

いろいろ調べていくとpythonでデータ処理をする場合は割とメモリ問題がでてくるみたいですね。今まで気にしたことなかったので、盲点でした。

pythonメモリ処理

下記サイトを参考に、使い終わった変数を消してガベージコレクションを実行しています。

やはりうまくいかないorz

BeautifulSoup4の処理を try:~Exception MemoryError:で挟んだら、スキップされてsoupオブジェクト無いとエラーを吐かれました。やはりBeautifulSoupが原因か。

もうちょっと原因究明には時間がかかりそうです。

kindleUnlimited蔵書取得でMemoryError

昨日の続きで未だに手詰まっています。蔵書を取得しているときに、BeautifulSoupに突っ込んだタイミングでMemoryErrorを吐いて落ちます。tryで捕まえられたのですが、どうしようもありませんでしたorz

BeautifulSoup自体は何度も使っているので、それ以外の辞書にデータを突っ込みすぎて落ちているのでは?と考えて、実行しているときの辞書のサイズをログに出してみました。

import sys
sys.getsizeof(dic)

これをlog.txtに吐き出して確認してみます。ん・・・毎回表示される値が一緒だorz

len(dic)のほうが良いのか?

といったところで寝る時間になってしまった。

kindleUnlimited蔵書取得でMemoryError 2

ここ数日間悩まされていたMemoryErrorですが、辞書型データがやっぱり溜まりすぎてるんじゃなかいと思って調査書けました。昨日はgetsizeofで辞書型のサイズを取得しようと思ったのですが、どうやらこれでは正しい値が取れない模様。辞書型をすべて撮ろうと思うと下のようなコードが必要になるようです。

import sys
from itertools import chain
from collections import deque

def compute_object_size(o, handlers={}):
  dict_handler = lambda d: chain.from_iterable(d.items())
  all_handlers = {tuple: iter,
    list: iter,
    deque: iter,
    dict: dict_handler,
    set: iter,
    frozenset: iter,
  }
  all_handlers.update(handlers) # user handlers take precedence
  seen = set() # track which object id's have already been seen
  default_size = sys.getsizeof(0) # estimate sizeof object without __sizeof__

def sizeof(o):
  if id(o) in seen: # do not double count the same object
    return 0
  seen.add(id(o))
  s = sys.getsizeof(o, default_size)
  for typ, handler in all_handlers.items():
    if isinstance(o, typ):
      s += sum(map(sizeof, handler(o)))
      break
    return s
  return sizeof(o)

今回はこのコードを書いたファイルを「memcheck.py」として保存し、複数のファイルから呼び出して使うことにしました。使い方は、使いたいファイルで 変数dic を調べたい場合は下記のように書きます。

import memcheck
print(memcheck.compute_object_size(dic))

これで関数が終わる直前に変数サイズを出力してlog.txtに書き出したら、増えに増える状況でした。

MemoryErrorの対策

ということで、再帰処理してなかなか辞書型をMySQLに書き込まなかったところを適当なところでMySQLに書き込んで、メモリ解放してやることにしました。これをやることで今まで読み込めなかった部分まで読み込むことができるようになりました!

BeautifulSoup4でエラーが出ていたものの、原因はそれまでの辞書型に費やしたメモリだったみたいです。

今後のススメ方

今回のMemoryErrorの件でソースがどのように動いているか大分わかったので、今回のファイルは一度保存し、別フォルダでKindleUnlimitedExtenderにカスタマイズをしていこうと思います。最終的には、AWSで運用してみたいとか考えています。

AWS使用料金をざっくり計算

いくらくらいかかるかをざっくり計算するツールが有りました。今後使ってみたいと思います。

MySQL型サイズエラー

MySQLの型サイズでエラーが出ていたので、TEXT型に変更しました。

あと、データベースで取得する情報もシュリンクしました。ただ、これでも調べた結果をもとに追加有無を判断してるため、オーバーヘッドが非常に大きいのがネックです。何かいい方法は無いものか・・・

一応、Powerで絞れるみたいだが、いい条件がなさげです。

とりあえず抜き出し完成

なんだかんだでKindleUnlimitedで書籍版があるものだけをピックアップしてMySQLにぶちこむプログラムですが、できました!

取得したデータは3万件くらいで、8時間くらい掛かった計算になります。あとはこのデータを表示する部分を作って公開していきたいです。目標は今週末!がんばるぞー!!