Python: Maximale recursiediepte overschreden tijdens het bellen van een Python-object

Ik heb een crawler gebouwd die op ongeveer 5m pagina’s moest lopen (door de URL-ID te verhogen) en vervolgens de pagina’s die de info ‘I nodig hebben.

Na gebruik van een algoritme die op de URL’s (200K) loopt en de goede en slechte resultaten heeft opgeslagen, vond ik dat het ik veel tijd verspil. Ik kon zien dat er een paar terugkerende subtrahends zijn die ik kan gebruiken om de volgende geldige URL te controleren.

U kunt de subtrahends vrij snel zien (een beetje ex ‘van de paarse eerste “goede ID’s”) –

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

Na het kruipen van ongeveer 200 km-URL’s die me slechts 14K goede resultaten gaven, wist ik dat ik mijn tijd verspilde en het moet optimaliseren, dus ik heb een aantal statistieken en bouwde een functie die de URL’s met 8 \ 18 wordt gebouwd \ 17 \ 8 (top terugkerende subtrahends) enz. ‘.

Dit is de functie –

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)

wat het in feite doet, is -checkNextID(ID) krijgt de eerste id waarvan ik weet dat die de gegevens min 8 bevat, dus de eerste iteratie komt overeen met de eerste “if isValid”-clausule (isValid(ID + 8) retourneert True) .

lastResultis een variabele die de laatst bekende url-id opslaat, dus we gaan door totdat numOfRuns is

isValid()is een functie die een ID + een van de subtrahends krijgt en True retourneert als de url bevat wat ik nodig heb en een soup-object van de url opslaat in een globale varibale met de naam – ‘ curRes‘, het retourneert False als de url niet de gegevens bevat die ik nodig heb.

parseHTMLis een functie die het soup-object (curRes) ophaalt, de gegevens die ik nodig heb ontleedt en de gegevens vervolgens opslaat in een csv en vervolgens True retourneert.

als isValid() True retourneert, zullen we parseHTML() aanroepen en vervolgens proberen de volgende ID+de subtrahenden te controleren (door checkNextID(ID + subtrahends) aan te roepen), als geen van hen zal retourneren wat ik zoek Ik verhoog het met 1 en controleer het opnieuw totdat ik de volgende geldige url vind.

je kunt de rest van de code hier

zien

na het uitvoeren van de code kreeg ik ongeveer 950~ goede resultaten en plotseling was er een uitzondering opgetreden –

“Er is iets misgegaan: maximale recursiediepte overschreden tijdens het aanroepen van a
Python-object”

Ik kon op WireShark zien dat het scipt bleef hangen op id – 510009541 (ik begon mijn script met 510000003), het script probeerde de url met die ID een paar keer te krijgen voordat ik de fout opmerkte en stopte het.

Ik was echt opwindend om te zien dat ik dezelfde resultaten kreeg, maar 25x-40x keer sneller dan mijn oude script, met minder HTTP-verzoeken, het is heel precies, ik heb slechts 1 resultaat gemist voor 1000 goede resultaten, namelijk gevonden door ik, het is onmogelijk om 5 miljoen keer te rummen, ik had mijn oude script 30 uur draaien en kreeg 14-15K resultaten toen mijn nieuwe script me 960~ resultaten gaf in 5-10 minuten.

Ik heb gelezen over stapelbeperkingen, maar er moet een oplossing zijn voor het algoritme dat ik probeer te implementeren in Python (ik kan niet teruggaan naar mijn oude “algoritme”, het zal nooit einde).

Bedankt!


Antwoord 1, autoriteit 100%

dit verandert de recursie in een lus:

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)

Antwoord 2, autoriteit 98%

Python heeft geen geweldige ondersteuning voor recursie vanwege het ontbreken van TRE (Staartrecursie-eliminatie).

Dit betekent dat elke aanroep van uw recursieve functie een functieaanroepstack zal creëren en omdat er een limiet is voor de stapeldiepte (standaard is dit 1000) die u kunt uitchecken door sys.getrecursionlimit(u kunt dit natuurlijk wijzigen met sys.setrecursionlimitmaar het wordt niet aanbevolen) zal je programma uiteindelijk crashen wanneer het deze limiet bereikt.

Omdat een ander antwoord je al een veel leukere manier heeft gegeven om dit in jouw geval op te lossen (wat is om recursie te vervangen door een eenvoudige lus), is er een andere oplossing als je nog steeds recursie wilt gebruiken, namelijk om een van de veel recepten voor het implementeren van TRE in python zoals deze één.

NB:Mijn antwoord is bedoeld om u meer inzicht te geven in waarom u de fout krijgt, en ik adviseer u niet om de TRE te gebruiken zoals ik al heb uitgelegd, omdat in uw geval een lus veel beter en gemakkelijk te lezen zijn.


Antwoord 3, autoriteit 92%

U kunt de capaciteit van de stapel als volgt vergroten:

import sys
sys.setrecursionlimit(10000)

Antwoord 4, autoriteit 11%

In plaats van recursie uit te voeren, kunnen de delen van de code met checkNextID(ID + 18)en dergelijke worden vervangen door ID+=18, en als u dan alle gevallen van return 0, dan zou het hetzelfde moeten doen, maar als een eenvoudige lus. Je moet dan een return 0aan het einde zetten en je variabelen niet-globaal maken.


Antwoord 5, autoriteit 11%

U kunt de recursiediepte en thread-stackgrootte vergroten.

import sys, threading
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27)  # new thread will get stack of such size

Other episodes