web-dev-qa-db-ja.com

Python:Pythonオブジェクトを呼び出しているときに最大再帰深度を超えました

(URL IDを増やすことで)約500万ページで実行する必要があるクローラーを作成し、必要な情報を含むページを解析しました。

uRL(200K)で実行し、良い結果と悪い結果を保存したアルゴリズムを使用した後、私は多くの時間を無駄にしていることがわかりました。次の有効なURLを確認するために使用できるサブトラヘンドがいくつかあることがわかりました。

subtrahendsを非常に速く見ることができます(いくつかの最初の「良いID」の小さな例)-

510000011 # +8
510000029 # +18
510000037 # +8
510000045 # +8
510000052 # +7
510000060 # +8
510000078 # +18
510000086 # +8
510000094 # +8
510000102 # +8
510000110 # etc'
510000128
510000136
510000144
510000151
510000169
510000177
510000185
510000193
510000201

約20万のURLをクロールした後、14Kの良い結果しか得られなかったので、時間を無駄にして最適化する必要があることがわかったので、いくつかの統計を実行し、8\18\17 \でIDを増やしながらURLをチェックする関数を作成しました8(トップのサブトラヘンドを返す)など。

これは機能です-

def checkNextID(ID):
    global numOfRuns, curRes, lastResult
    while ID < lastResult:
        try:
            numOfRuns += 1
            if numOfRuns % 10 == 0:
                time.sleep(3) # sleep every 10 iterations
            if isValid(ID + 8):
                parseHTML(curRes)
                checkNextID(ID + 8)
                return 0
            if isValid(ID + 18):
                parseHTML(curRes)
                checkNextID(ID + 18)
                return 0
            if isValid(ID + 7):
                parseHTML(curRes)
                checkNextID(ID + 7)
                return 0
            if isValid(ID + 17):
                parseHTML(curRes)
                checkNextID(ID + 17)
                return 0
            if isValid(ID+6):
                parseHTML(curRes)
                checkNextID(ID + 6)
                return 0
            if isValid(ID + 16):
                parseHTML(curRes)
                checkNextID(ID + 16)
                return 0
            else:
                checkNextID(ID + 1)
                return 0
        except Exception, e:
            print "somethin went wrong: " + str(e)

基本的には-checkNextID(ID)は、データから8を引いたものを含む最初のidを取得するため、最初の反復は最初の「if isValid」節に一致します(isValid(ID + 8)はTrueを返します)。

lastResultは最後の既知のURL IDを保存する変数なので、numOfRunsが

isValid()は、ID + subtrahendsの1つを取得し、URLに必要なものが含まれ、そのスープオブジェクトを保存する場合にTrueを返す関数です-'curRes'という名前のグローバル変数へのURL。URLに必要なデータが含まれていない場合はFalseを返します。

parseHTMLは、スープオブジェクト(curRes)を取得し、必要なデータを解析してからデータをcsvに保存し、Trueを返す関数です。

isValid()がTrueを返す場合、parseHTML()を呼び出してから、次のID + subtrahendsをチェックしようとします(checkNextID(ID + subtrahends)を呼び出して、それらのどれもが私が探しているものを返さない場合) 1で増やし、次の有効なURLが見つかるまでもう一度確認します。

あなたはコードの残りを見ることができます here

コードを実行した後、約950〜の良い結果が得られ、突然例外が発生しました-

「何らかの問題が発生しました。Pythonオブジェクト]を呼び出しているときに最大再帰深度を超えました」

WireSharkで、sciptがid-510009541(510000003でスクリプトを開始した)でスタックしていることがわかりました。スクリプトは、エラーに気づいて停止する前に、そのIDのURLを数回取得しようとしました。

同じ結果が得られたのを見ると本当に興奮しましたが、HTTPリクエストが少ない古いスクリプトよりも25倍から40倍高速で、非常に正確で、1000の良い結果に対して1つの結果だけを逃しました。 500万回鳴らすことは不可能で、古いスクリプトを30時間実行し、新しいスクリプトで5〜10分で960〜の結果が得られたときに、14〜15Kの結果を得ました。

スタックの制限について読みましたが、実装しようとしているアルゴリズムの解決策がPython(古いに戻ることはできません)アルゴリズム」、終了しません)。

ありがとう!

31
YSY

これにより、再帰がループになります。

def checkNextID(ID):
    global numOfRuns, curRes, lastResult
    while ID < lastResult:
        try:
            numOfRuns += 1
            if numOfRuns % 10 == 0:
                time.sleep(3) # sleep every 10 iterations
            if isValid(ID + 8):
                parseHTML(curRes)
                ID = ID + 8
            Elif isValid(ID + 18):
                parseHTML(curRes)
                ID = ID + 18
            Elif isValid(ID + 7):
                parseHTML(curRes)
                ID = ID + 7
            Elif isValid(ID + 17):
                parseHTML(curRes)
                ID = ID + 17
            Elif isValid(ID+6):
                parseHTML(curRes)
                ID = ID + 6
            Elif isValid(ID + 16):
                parseHTML(curRes)
                ID = ID + 16
            else:
                ID = ID + 1
        except Exception, e:
            print "somethin went wrong: " + str(e)
13
Dan D.

Pythonは、TREがないため、再帰をサポートしていません( Tail Recursion Elimination )。

つまり、再帰関数を呼び出すたびに関数呼び出しスタックが作成され、スタックの深さに制限があるため(デフォルトでは1000)、 sys.getrecursionlimit (もちろん sys.setrecursionlimit を使用して変更できますが、推奨されません)この制限に達するとプログラムがクラッシュしてしまいます。

他の答えはすでにあなたの場合にこれを解決する方法のためのはるかに良い方法を提供しています(単純なループで再帰を置き換えることです)まだ再帰を使用したい場合は別の解決策がありますpythonこのように one でTREを実装します。

NB:私の答えは、エラーが発生する理由についてより多くの洞察を与えることを意味します。あなたの場合、ループははるかに良く、読みやすいので説明しました。

34
mouad

次の方法でスタックの容量を増やすことができます。

import sys
sys.setrecursionlimit(10000)
18
coderjack

再帰を行う代わりに、コードのcheckNextID(ID + 18)などの部分をID+=18に置き換え、return 0のすべてのインスタンスを削除した場合、同じことを行う必要があります。しかし、単純なループとして。次に、return 0を最後に配置して、変数を非グローバルにする必要があります。

2
murgatroid99