확인할 것
코드 상 driver 가 새 TAB을 여는 부분 확인
열었던 TAB을 바로 닫는지 확인
driver.quit 를 명시적으로 호출
가비지 컬렉터 활용
selenium 크롤링 코드를 개발 중이다.
크롤링을 진행하고 나서 ChromeDriver 프로세스가 CPU를 계속 점유하고 있는 문제가 지속적으로 발생했다.

우선 초기 코드는 다음에서 부터 시작했다.
아래 코드에서는 실행이 끝나면 관련 프로세스도 같이 정리되는 것을 확인할 수 있었다.
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("https://www.google.com/")
time.sleep(5)
참고로, 실제 구글 크롤링은 아니다
아,, 어차피 프로세스가 끝나면 자연스럽게 프로세스도 같이 정리되겠구나 라는 안일한 생각을 가지고 계속 개발을 해왔다.
그러나 문제가 발생했다. 어느 순간부터 프로세스가 종료되고 나서 ChromeDriver가 메모리를 계속 점유하고 있던 것이다. 처음부터 확인하면서 다시 개발할 수 없으니 어디서 메모리 누수가 발생하는지 하나하나 확인하기로 했다.
처음 위 코드를 통해 크롬 드라이버가 자체적으로 누수가 원래 발생하는지 확인했지만 역시 아니였다.
-- ㅎ_ㅎ 잘못 만들일이 없지
잘못된건 역시 내 코드였어
두 번째로 class 를 확인하였다.
굳이 왜 class 로 만들었냐 함은,, xpath, id, class 등 element를 수집하기 위한 긴 함수들이 너무 귀찮았다.
혹시 class를 인스턴스로 만들고, 인스턴스 자체에서 driver를 호출하면 메모리 누수가 발생하나? 라는 생각을 가지고 또 간단하게 코드를 작성해 보았다.
proxy 때문에 ,, selenium 대신 seleniumwire 를 사용했다..
'''test.py'''
from seleniumwire import webdriver
import time
class Driver:
def __init__(self):
self.driver = webdriver.Chrome()
'''test2.py'''
from test1 import Driver
if __name__ == '__main__':
test_driver = Driver()
test_driver.driver.get("https://www.google.com/")
time.sleep(5)
역시 문제가 없었다.. 문제가 뭐지,, 그냥 내가 문제인가?
그래서 작성해 놓은 클래스의 이름은 WebDriverUtility로 실제로 사용하는 클래스를 사용해서 테스트 해보기로 하였다.
from new_site.selenium_base import WebDriverUtility
import time
if __name__ == '__main__':
test_driver = WebDriverUtility()
test_driver.driver.get("https://www.google.com/")
time.sleep(5)

두둥~
일단 어디서 문제인지는 찾았다.
흠,, 예외처리를 어느정도 하긴했지만 완벽하게 하지 않았던 나 자신이 문제였던 것이다.
이제 뭐가 문제인지 봐야한다..
지금까지
별도로 quit_driver 함수를 구현하여 코드 마지막 부분에 추가하였다.
def quit_driver(self) -> None:
"""
dirver 종료
:return:
"""
self.driver.quit()
'''test.py'''
test_driver.quit_driver()
여전하다.
어디선가 명시적으로 참조를 제거하면 된다고 해서 아래 코드를 추가했다
def quit_driver(self) -> None:
"""
dirver 종료
:return:
"""
self.driver.quit()
self.driver = None
이 부분이 문제였다.
생성한 객체의 driver 자체가 계속 참조되고있던 것이 문제였다.
아까 객체로 테스트 할때는 괜찮았는데, 내 코드에서는 왜이러는 거지?
어찌됐든 해결됐다.
그렇지만 어떠한 문제가 또 발생할 수 있으니까 여러가지 방안을 작성해야했다.
def __del__(self):
self.driver.quit()
self.driver = None
print("__del__")
정상적으로 호출되는지 확인하기 위한 print와 같이 실행했다.

소멸자가 호출은 되지만 여전히 해결되지는 않았다.
당연히 안되는 걸지도
그런데 뭔가 이상한게 왜 소멸자에서는 안되는거지?
quitdriver 도 실행하고 _del 도 같이 작성하였는데

이런 에러가 발생했고, 이미 quit 호출 했으니까 NoneType 이라 quit가 불가능하다~~
quit도 호출이 되는데~ 소멸자만 작성하면 안되는 것,, 안되니까 del 과감하게 버리고 직접 수집 코드에 예외처리를 하고 예외가 발생하면 무조건 quit_driver 를 호출하는 것이 방법인 것 같다.
아무리 예외처리를 잘한다고 해도 에러가 발생할 수 있으므로 여러 대책을 세운다
import gc
gc.collect()
이 코드를 소멸자에 작성하면 될까?
바로 테스트 해봤다. (quit_driver 주석)
def __del__(self):
gc.collect()
오,, 된다,, 어쨋든 된다.
따라서 최종적으로
def quit_driver(self) -> None:
"""
dirver 종료
:return:
"""
self.driver.quit()
self.driver = None
def __del__(self):
gc.collect()
요렇게 작성했다.
테스트에는 완벽한 줄 알았지만,, 실전 코드로 옮기니 바로 똑같은 문제가 터졌다... ㅋㅋㅋ
그래도 이제 해결할 수 있다는 가능성을 봤으니 구현 코드를 봤다.
문제의 부분이었다. 동적 크롤링을 진행하다 보니 빠르게 열고 닫기 위한 코드의 예외처리 부분이 문제였던 것이다.
for page in pages:
driver.execute_script("window.open(arguments[0]);", data.get_attribute('href'))
driver.switch_to.window(driver.window_handles[1])
try:
crawling()
except:
break
self.WebDriverUtility.driver.close()
self.WebDriverUtility.driver.switch_to.window(driver.window_handles[0])
이렇게 페이지를 열고, 닫는 코드가 있었는데 저 부분이 문제였다..
바보같은 실수를 했던 것,,,
새 tab 을 open 했으면 무조건 close를 하고 드라이버를 종료해야 된다는 사실을 알았다. 당연히 driver.quit 하면 다 지워질 줄 알았는디,,
파일도 열었으면 닫아야 되는 개념이랑 같은건가보다