복붙노트

[PYTHON] 파이썬 : 파이썬 객체를 호출하는 동안 최대 재귀 깊이를 초과했습니다.

PYTHON

파이썬 : 파이썬 객체를 호출하는 동안 최대 재귀 깊이를 초과했습니다.

나는 약 5M 페이지 (url ID를 증가시킴으로써)에서 실행되어야하는 크롤러를 구축하고 정보가 담긴 페이지를 분석한다.

URL (200K)에서 실행되는 알고리즘을 사용하고 좋은 결과와 나쁜 결과를 저장 한 후에 나는 많은 시간을 낭비하고 있음을 발견했습니다. 다음 유효한 URL을 확인하는 데 사용할 수있는 몇 가지 감수성이 있음을 알 수있었습니다.

당신은 아주 빨리 감수를 볼 수 있습니다 (몇 안되는 좋은 "ID"중 약간 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

나에게 단지 14K 좋은 결과를 준 약 200K의 URL을 크롤링 한 후 나는 내 시간을 낭비하고 그것을 최적화 할 필요가 있다는 것을 알았 기 때문에 일부 통계를 실행하고 8 \ 18 \ 17 \로 ID를 늘리는 동안 URL을 검사하는 함수를 만들었습니다. 8 (최고 반환 감수) etc.

이것은 함수입니다 -

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 + 하나를 얻고 URL에 필요한 내용이 들어있는 경우 True를 반환하고 URL의 스프레드 객체를 'curRes'라는 전역 varibale에 저장하는 함수입니다. url이 아닌 경우 False를 반환합니다. 필요한 데이터가 없습니다.

parseHTML은 수프 객체 (curRes)를 가져 와서 필요한 데이터를 파싱 한 다음 데이터를 CSV에 저장 한 다음 True를 반환하는 함수입니다.

isValid ()가 True를 반환하면, 우리는 parseHTML ()을 호출하고 다음 ID + 감정 검사를 시도합니다 (checkNextID (ID + 감수) 호출). 그것을 1로 올리고 다음 유효한 URL을 찾을 때까지 다시 확인하십시오.

나머지 코드는 여기에서 볼 수 있습니다.

코드를 실행 한 후 약 950 개를 얻었습니다 ~ 좋은 결과와 갑자기 예외가 발생했습니다 -

나는 WireShark에서 scipt가 id에 걸린 것을 볼 수있었습니다 - 510009541 (510000003으로 스크립트 시작) 스크립트는 오류를 발견하고 중지하기 전에 해당 ID가있는 URL을 몇 번 읽으려고했습니다.

나는 똑같은 결과를 얻었지만, 적은 수의 HTTP 요청으로 25x-40x 배 더 빠르다는 것을 알기가 정말 기뻤다. 그것은 매우 정확하다. 나는 좋은 결과 1000 건에 대해서 단 1 개의 결과만을 놓쳤다. 5M 번은 불가능했으며, 새 스크립트가 5 ~ 10 분 안에 960 ~ 결과를 주었을 때 나는 30 시간 동안 실행 된 내 오래된 스크립트를 가지고 14-15K 결과를 얻었습니다.

스택 제한에 대해 읽었지만 파이썬에서 구현하려고하는 알고리즘에 대한 솔루션이 있어야합니다 (필자는 이전의 "알고리즘"으로 되돌릴 수는 없지만 결코 끝나지 않을 것입니다).

감사!

해결법

  1. ==============================

    1.이것은 재귀를 루프로 바꾼다.

    이것은 재귀를 루프로 바꾼다.

    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. ==============================

    2.Python은 TREE (Tail Recursion Elimination)이 없기 때문에 재귀를 크게 지원하지 않습니다.

    Python은 TREE (Tail Recursion Elimination)이 없기 때문에 재귀를 크게 지원하지 않습니다.

    즉, 재귀 함수를 호출 할 때마다 함수 호출 스택이 생성되고 sys.getrecursionlimit에서 체크 아웃 할 수있는 스택 깊이 한계 (기본적으로 1000)가 있기 때문에 (sys.setrecursionlimit를 사용하여 변경할 수 있음) 그러나 권장하지 않음) 프로그램이이 제한에 도달하면 충돌로 종료됩니다.

    다른 답변은 이미 간단한 루프로 재귀를 대체하는 경우에 당신의 경우에 이것을 해결하는 방법에 대한 훨씬 더 좋은 방법을 제공하므로 많은 조리법 중 하나를 사용하는 재귀를 여전히 사용하려는 경우 다른 해결책이 있습니다. 이 같은 파이썬에서 TRE를 구현합니다.

    N.B : 제 대답은 왜 오류가 발생했는지에 대한 더 많은 통찰력을 제공하기위한 것이며, 이미 설명한대로 루프를 훨씬 잘 읽고 쉽게 읽을 수 있기 때문에 TRE를 사용하도록 조언하지 않습니다.

  3. ==============================

    3.다음과 같이 스택 용량을 늘릴 수 있습니다.

    다음과 같이 스택 용량을 늘릴 수 있습니다.

    import sys
    sys.setrecursionlimit(10000)
    
  4. ==============================

    4.재귀를 수행하는 대신 checkNextID (ID + 18) 및 유사한 코드 부분을 ID + = 18로 바꿀 수 있으며 반환 값 0의 모든 인스턴스를 제거하면 동일한 작업을 수행해야하지만 단순 루프로 수행해야합니다 . 그런 다음 마지막에 return 0을 넣고 변수를 전역 변수가 아닌 전역 변수로 설정해야합니다.

    재귀를 수행하는 대신 checkNextID (ID + 18) 및 유사한 코드 부분을 ID + = 18로 바꿀 수 있으며 반환 값 0의 모든 인스턴스를 제거하면 동일한 작업을 수행해야하지만 단순 루프로 수행해야합니다 . 그런 다음 마지막에 return 0을 넣고 변수를 전역 변수가 아닌 전역 변수로 설정해야합니다.

  5. from https://stackoverflow.com/questions/6809402/python-maximum-recursion-depth-exceeded-while-calling-a-python-object by cc-by-sa and MIT license