Python: максимальная глубина рекурсии превышена при вызове объекта Python - PullRequest
30 голосов
/ 25 июля 2011

Я создал сканер, который должен был работать примерно на 5 млн. Страниц (путем увеличения идентификатора URL), а затем анализирует страницы, содержащие информацию, которая мне нужна.

после использования алгоритма, который работает наURL-адреса (200 КБ) и сохраненные хорошие и плохие результаты, которые я обнаружил, что я трачу много времени.Я мог видеть, что есть несколько возвращаемых вычитаемых, которые я могу использовать, чтобы проверить следующий действительный URL.

вы можете увидеть вычитаемые довольно быстро (немного ex 'из нескольких первых "хороших идентификаторов") -

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

после сканирования около 200 тыс. URL, которые дали мне только 14 тыс. Хороших результатов, я знал, что теряю время и нуждаюсь в оптимизации, поэтому я запустил некоторую статистику и создал функцию, которая будет проверять URL при увеличенииидентификатор с 8 \ 18 \ 17 \ 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, поэтому первая итерация будет соответствовать первому предложению «if isValid» (isValid (ID + 8) вернет True).

lastResult являетсяпеременная, которая сохраняет последний известный идентификатор URL, поэтому мы будем работать до тех пор, пока numOfRuns не будет

isValid () - это функция, которая получает идентификатор + одно из вычитаемых значений и возвращает True, если URL-адрессодержит то, что мне нужно и сСохраняет объект супруга URL-адреса в глобальную переменную с именем - ' curRes ', он возвращает False, если URL-адрес не содержит данных, которые мне нужны.

parseHTML - это функция, которая получает объект супа (curRes), анализирует нужные мне данные и затем сохраняет данные в CSV, а затем возвращает True.

если isValid () возвращает True, мы вызовем parseHTML (), а затем попробуйте проверить следующий идентификатор + вычитаемые (вызывая checkNextID (идентификатор + вычитаемые), если ни один из них не вернет то, что я ищу, я увеличу его на 1 и проверю снова, пока не найдуследующий действительный URL.

вы можете увидеть остальную часть кода здесь

после выполнения кода я получил около 950 ~ хороших результатов, и внезапно возникло исключение -

"что-то пошло не так: максимальная глубина рекурсии превышена при вызове объекта Python"

Я видел на WireShark, что сценарий застрял на id - 510009541 (я запустил свой скриптс 510000003), скрипт пытался получить тон URL с этим идентификатором несколько раз, прежде чем я заметил ошибку и остановил ее.

Мне было очень приятно видеть, что я получаю те же результаты, но в 25–40 раз быстрее, чем мой старый скрипт, с меньшим количеством HTTP-запросов., это очень точно, я пропустил только 1 результат на 1000 хороших результатов, который я нашел, 5 миллионов раз невозможно найти, мой старый скрипт работал 30 часов и получил 14-15K результатов, когда мой новый скрипт дал мне960 ~ результаты за 5-10 минут.

Я читал об ограничениях стека, но должно быть решение для алгоритма, который я пытаюсь реализовать в Python (я не могу вернуться к своему старому "алгоритм" , он никогда не закончится).

Спасибо!

Ответы [ 4 ]

34 голосов
/ 25 июля 2011

Python не имеет большой поддержки рекурсии из-за отсутствия TRE ( Исключение хвостовой рекурсии ).

Это означает, что каждый вызов вашей рекурсивной функции создаст функциюстек вызовов и поскольку существует предел глубины стека (по умолчанию 1000), который вы можете проверить с помощью sys.getrecursionlimit (конечно, вы можете изменить его, используя sys.setrecursionlimit , ноэто не рекомендуется) ваша программа завершится сбоем, когда достигнет этого предела.

Поскольку другой ответ уже дал вам гораздо более хороший способ решить эту проблему в вашем случае (то есть заменить рекурсию простымloop) есть другое решение, если вы все еще хотите использовать рекурсию, которая заключается в использовании одного из множества рецептов реализации TRE в python, например: one .

NB: Мой ответ призван дать вам более полное представление о том, почему вы получаете ошибку, и я не советую вам использовать TRE, как я уже объяснил, потому что в вашем случае цикл будет оченьлегко читается.

17 голосов
/ 06 марта 2013

Вы можете увеличить емкость стека следующим образом:

import sys
sys.setrecursionlimit(10000)
13 голосов
/ 25 июля 2011

это превращает рекурсию в цикл:

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)
2 голосов
/ 25 июля 2011

Вместо выполнения рекурсии части кода с checkNextID(ID + 18) и аналогичными могут быть заменены на ID+=18, а затем, если вы удалите все экземпляры return 0, тогда он должен сделать то же самое, но простопетля.Затем вы должны поставить return 0 в конце и сделать ваши переменные неглобальными.

...