フツーって言うなぁ!

フツーなサラリーマンのフツーな嘆き.

PythonでcPickleを使用する際に,"maximum recursion depth exceeded"というエラーが出る

久しぶりにPythonの話をする気がします.
BeautifulSoupと(c)Pickleについてです.

問題

BeautifulSoupによってHTMLから取得した文字列を,cPickle*1を用いて保存しようとしました.

import filecache # ファイルキャッシュ用のプログラム(cPickleのラッパ.他人が書いたものなので見せられない)
from bs4 import BeautifulSoup # BeautifulSoupのバージョンは4
# (略)

class CiteSeerXCrawler(object):

    # (略)

    def get_abstract(self, url):
        '''
        入力としてURLを受け取り,CiteSeerXの論文詳細ページから,アブストラクトを返す
        '''
        abstract = ''

        html = self._get_html(url) # HTMLの取得

        soup = BeautifulSoup(html, 'html.parser') # BeautifulSoup

        if soup.find('div', {'id': 'abstract'}):
            soup_abstract = soup.find('div', {'id': 'abstract'}).p
            abstract = soup_abstract.string # BeautifulSoupにより取得した文字列 (*)
            # (**)
            return abstract

class GoogleScholarArticleCrawler(object):

    # (略)

    def get_abstract(self, cluster_id):
        '''
        Cluster_idを受け取り,対応する論文のアブストラクトを返す
        '''
        abspath = os.path.dirname(os.path.abspath(__file__))
        abstract = filecache.Client(abspath + '/abstract/')

        cache = abstract.get(str(cluster_id)) # キャッシュから取り出す
        if cache is not None and cache['status'] == 'OK':
            return cache['data']
        else:
            art = self.get_bibliography(cluster_id)

            result = {'status': '', 'data': []}

            # CiteSeerXによる引用論文の取得
            if art["title"][0] is not None:
                abst = ''
                c = CiteSeerXCrawler()
                search_results = c.search_with_title(art['title'][0], num=1)
                time.sleep(1)
                if len(search_results) > 0:
                    abst = c.get_abstract(search_results[0])
                    result['data'].append(abst) # 取得した文字列を追加

                if len(result['data']) > 0:
                    result['status'] = 'OK'
                    abstract.set(str(cluster_id), result) # キャッシュに保存
                else:
                    result['status'] = 'NG'

            return result['data']

すると,以下のようなエラーを吐いて,中身の無いファイルを出力してしまいます.

Traceback (most recent call last):  
  File "lib/crawler/google_scholar_abstract.py", line 15, in <module>  
    abstract = g.get_abstract(sys.argv[1])  
  File "/home/lethe/LiteratureSearchEngine/lib/crawler/google_scholar_article_crawler.py", line 233, in get_abstract  
    abstract.set(str(cluster_id), result)  
  File "/home/lethe/LiteratureSearchEngine/lib/crawler/filecache.py", line 46, in set
    pickle.dump(value)  
  File "/usr/lib/python2.7/copy_reg.py", line 74, in _reduce_ex  
    getstate = self.__getstate__  
RuntimeError: maximum recursion depth exceeded

解決策

BeautifulSoupの結果のstringを文字列と思い込んだのがまずかったらしい.

上のソースコードの(**)の部分に

print type(soup_abstract.string)

を挿入して,この変数の型を調べてみると,strではなく,

<class 'bs4.element.NavigableString'>

という,BeautifulSoup固有と思われる型が返ってきました.
つまり,strでもunicodeでもないということ.

python - pickle.dump meet RuntimeError: maximum recursion depth exceeded in cmp - Stack Overflow

ここらへんを見てる限り,BeautifulSoupのオブジェクトをそのままpickleに詰め込むのはまずいみたい.

(*)の部分を

abstract = unicode(soup_abstract.string)

にするとうまく動くらしいです.


最初,原因がBeautifulSoupにあるとわからず,必死にファイルキャッシュのコードを見直していて,半日潰しました…

悔しいので載せておきます.

*1:ざっと調べた限り,pickleについても同様の問題があるかと思われます