56

約 5M ページで (URL ID を増やして) 実行し、必要な情報を含むページを解析するクローラーを作成しました。

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

減数を非常に速く見ることができます (いくつかの最初の「良い 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

約 200K の URL をクロールした後、14K の良い結果しか得られませんでした。時間を無駄にしており、最適化する必要があることがわかったので、いくつかの統計を実行し、ID を 8\18\17\ で増やしながら 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 + サブトラヘンドの 1 つを取得し、URL に必要なものが含まれている場合は True を返し、URL のスープ オブジェクトを名前付きのグローバル変数 - ' curRes ' に保存する関数です。URL が含まれていない場合は False を返します。必要なデータが含まれていません。

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

isValid() が True を返した場合は、parseHTML() を呼び出してから、次の ID + サブトラヘンドをチェックしようとします (checkNextID(ID + サブトラヘンド) を呼び出すことによって)。 1 ずつ増やして、次の有効な URL が見つかるまでもう一度確認してください。

ここで残りのコードを見ることができます

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

「問題が発生しました: Python オブジェクトの呼び出し中に最大再帰深度を超えました」

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

同じ結果が得られたのを見て本当に興奮しましたが、古いスクリプトよりも 25 倍から 40 倍高速で、HTTP リクエストが少なく、非常に正確です。古いスクリプトを 30 時間実行したところ、新しいスクリプトで 5 ~ 10 分で 960 ~ の結果が得られたときに 14 ~ 15,000 の結果が得られました。

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

ありがとう!

4

6 に答える 6

53

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

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

他の答えは、あなたのケースでこれを解決する方法(単純なループで再帰を置き換えることです)をすでに提供しているので、再帰を使用したい場合は、多くのレシピの1つを使用する別の解決策がありますこのようにPythonでTREを実装します

注意:私の回答は、なぜエラーが発生するのかについてより多くの洞察を与えることを目的としています。すでに説明したように、TRE を使用することをお勧めしません。あなたの場合、ループの方がはるかに優れていて読みやすいからです。

于 2011-07-24T20:42:34.883 に答える
41

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

import sys
sys.setrecursionlimit(10000)
于 2013-03-06T07:28:40.423 に答える
18

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

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)
于 2011-07-24T20:22:45.107 に答える
4

再帰の深さとスレッド スタック サイズを増やすことができます。

import sys, threading
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27)  # new thread will get stack of such size
于 2021-04-11T11:42:23.677 に答える
2

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

于 2011-07-24T20:23:06.530 に答える