web-dev-qa-db-fra.com

Python: profondeur de récursivité maximale dépassée lors de l'appel d'un objet Python

J'ai construit un robot d'exploration qui devait fonctionner sur environ 5 millions de pages (en augmentant l'ID d'URL), puis analyse les pages qui contiennent les informations dont j'ai besoin.

après avoir utilisé un algorithme qui fonctionne sur les URL (200K) et a sauvé les bons et les mauvais résultats, j'ai trouvé que je perdais beaucoup de temps. J'ai pu voir qu'il y a quelques sous-corrections de retour que je peux utiliser pour vérifier la prochaine URL valide.

vous pouvez voir les subtrahends assez rapidement (un petit ex des quelques premiers "bons identifiants") -

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

après avoir exploré environ 200 000 URL, ce qui ne m'a donné que 14 000 bons résultats, je savais que je perdais mon temps et que je devais l'optimiser.J'ai donc exécuté des statistiques et créé une fonction qui vérifiera les URL tout en augmentant l'ID avec 8\18\17\8 (sous-titres de retour en haut) etc '.

c'est la fonction -

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)

ce qui est fondamentalement -checkNextID (ID) obtient le premier identifiant que je connais qui contient les données moins 8 de sorte que la première itération correspondra à la première clause "if isValid" (isValid (ID + 8) renverra True).

lastResult est une variable qui enregistre le dernier identifiant d'URL connu, nous allons donc exécuter jusqu'à ce que numOfRuns soit

isValid () est une fonction qui obtient un ID + l'un des sous-titres et renvoie True si l'url contient ce dont j'ai besoin et enregistre un objet soupe du URL vers une variable globale nommée - ' curRes ', elle renvoie False si l'url ne contient pas les données dont j'ai besoin.

parseHTML est une fonction qui obtient l'objet soupe (curRes), analyse les données dont j'ai besoin, puis enregistre les données dans un fichier csv, puis renvoie True.

si isValid () renvoie True, nous appellerons parseHTML (), puis essayer de vérifier l'ID suivant + les subtrahends (en appelant checkNextID (ID + subtrahends), si aucun d'eux ne retournera ce que je cherche, je vais l'augmenter avec 1 et vérifier à nouveau jusqu'à ce que je trouve la prochaine URL valide.

vous pouvez voir le reste du code ici

après avoir exécuté le code, j'ai obtenu environ 950 ~ bons résultats et soudain, une exception s'est levée -

"quelque chose s'est mal passé: la profondeur de récursivité maximale a été dépassée lors de l'appel d'un objet Python"

Je pouvais voir sur WireShark que le scipt est resté sur l'id - 510009541 (j'ai commencé mon script avec 510000003), le script a essayé d'obtenir l'url avec cet ID plusieurs fois avant de remarquer l'erreur et de l'arrêter.

J'étais vraiment excitant de voir que j'obtenais les mêmes résultats mais 25x à 40x plus vite que mon ancien script, avec moins de requêtes HTTP, c'est très précis, je n'ai raté qu'un seul résultat pour 1000 bons résultats, qui est trouvé par moi, c'est impossible de ruminer 5 millions de fois, mon ancien script a fonctionné pendant 30 heures et j'ai obtenu 14 à 15 000 résultats lorsque mon nouveau script m'a donné 960 ~ résultats en 5 à 10 minutes.

J'ai lu des informations sur les limitations de la pile, mais il doit y avoir une solution pour l'algorithme que j'essaie d'implémenter dans Python (je ne peux pas revenir à mon ancien " algorithme ", il ne finira jamais).

Merci!

31
YSY

cela transforme la récursivité en une boucle:

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 n'a pas un grand support pour la récursivité en raison de son manque de TRE ( Tail Recursion Elimination ).

Cela signifie que chaque appel à votre fonction récursive créera une pile d'appels de fonction et parce qu'il y a une limite de profondeur de pile (par défaut 1000) que vous pouvez vérifier par sys.getrecursionlimit (bien sûr, vous pouvez le changer en utilisant sys.setrecursionlimit mais ce n'est pas recommandé) votre programme finira par planter quand il atteindra cette limite.

Comme une autre réponse vous a déjà donné un moyen beaucoup plus agréable de résoudre ce problème dans votre cas (qui consiste à remplacer la récursivité par une boucle simple), il existe une autre solution si vous souhaitez toujours utiliser la récursivité qui consiste à utiliser l'une des nombreuses recettes de implémenter TRE dans python comme ceci n .

NB: Ma réponse est destinée à vous donner plus d'informations sur la raison pour laquelle vous obtenez l'erreur, et je ne vous conseille pas d'utiliser le TRE comme je l'ai déjà expliqué parce que dans votre cas, une boucle sera beaucoup mieux et facile à lire.

34
mouad

Vous pouvez augmenter la capacité de la pile comme suit:

import sys
sys.setrecursionlimit(10000)
18
coderjack

Au lieu de faire une récursivité, les parties du code avec checkNextID(ID + 18) et similaires pourraient être remplacées par ID+=18, puis si vous supprimez toutes les instances de return 0, alors il devrait faire la même chose mais comme une simple boucle. Vous devez ensuite mettre un return 0 à la fin et rendez vos variables non globales.

2
murgatroid99