
건국대학교 종강시를 크롤링하며 겪었던 문제들을 적어보고자 한다.
먼저, 처음에는 대학과 학과 선택을 기준으로 나오는 강의 리스트들의 정보만 크롤링하려 했고, 이는 준수한 속도를 내었기에 멀티쓰레딩에 대한 고려는 하지 않았다. 하지만, 강의계획서 안에 있는 수강 신청 유의 사항도 조사하고자 했고, 팝업창 기능까지 추가하는 순간 속도가 너무 느려졌다.
마침, AI 해커톤에서 데이터를 처리할 때 멀티쓰레딩을 했던 경험이 있었고, 속도가 매우 빨라졌었다. 물론 selenium이 thread-safe는 아니어서 의미가 없을 것은 대강 알고 있었지만, 궁금해서 한번 해보기로 했다.
먼저, 멀티쓰레딩을 하기 위해 기존 코드에서 멀티쓰레딩에 맞는 구조로 바꿔주고, 파이썬의 threading 라이브러리를 사용했다.
일단 해결해야 하는 문제는 다음과 같았다.
1. 크롤링한 결과를 저장하는 배열에 다른 thread들이 동시에 접근하면 안 됨.
2. selenium의 webdriver 또한 thread-safe가 아니기 때문에 동시에 접근하면 안 됨.
3. 위의 조건을 준수하며, 시간을 줄여야 함.
먼저 1번과 2번의 문제는 lock을 통해 해결할 수 있었다.
with lock:
...
그러나 thread 안에서 n~n+k의 행을 순회하며 정보를 수집해야 하는데, n번째만 수집하고 그 다음부터는 주소도 정확하고 페이지도 그대로인데, 요소에 접근할 수 없다는 오류를 계속해서 반환했다.
StaleElementReferenceException
발생한 오류
분명 webdriver를 제어하는 팝업창 제어의 경우는 with lock 안에서 실행하여 다른 thread의 영향을 받지 않게 했는데 이런 오류가 나는 것이 의아했다. 찾아본 결과로는 다른 thread에서도 팝업창을 열었다 닫으며 webdriver가 계속 변경되어서 이런 오류를 반환하는 것으로 추정된다.
따라서 webdriver가 영향을 받지 않도록 해야 했고, 아예 함수 내부에서 webdriver를 생성한뒤에 작업을 하고 끝내버리는 방법을 생각해 냈다. 다만 건국대학교 종강시의 강의계획서는 링크로 접속할 때는 보안 문제로 막히고, 팝업창으로 열어야만 열리는 문제를 발견하였기에, 아무 사이트(여기서는 건국대학교 수강 신청 사이트)에 접속하여 팝업창을 여는 JS 스크립트 명령어를 입력하여 팝업창을 열고, 팝업창을 다루기로 했다.
#팝업창 제어
tmp = webdriver.Chrome(service=service, options=options)
tmp.get("https://sugang.konkuk.ac.kr/")
tmp.execute_script(f'window.open("팝업창 링크")')
tmp.switch_to.window(tmp.window_handles[1])
# 강의계획서 수강신청 유의사항 저장 코드
...
tmp.quit()
다만 이 경우 가끔 성능 문제로 드라이버를 여는 데 실패하므로 다음과 같이 코드를 수정하고, 기존의 thread 개수를 조금 더 줄일 필요가 생겼다.
#팝업창 제어
while True:
try:
tmp = webdriver.Chrome(service=service, options=options)
tmp.get("https://sugang.konkuk.ac.kr/")
break
except:
print("에러 : 팝업창 임시 웹드라이버 생성 실패 [자동으로 다시시도]")
time.sleep(1)
tmp.execute_script(f'window.open("팝업창 링크")')
tmp.switch_to.window(tmp.window_handles[1])
# 강의계획서 수강신청 유의사항 저장 코드
...
tmp.quit()
그리고 실행한 결과 오류 없이 모든 행을 순회하긴 하였다. 즉, 1번과 2번 조건을 충족하며 잘 실행되었다. 솔직히 여기까지 3일 정도가 걸렸기에 너무 기뻤다. 하지만 제일 시간이 오래 걸리는 부분을 non-multithread로 구현을 해버리고, 오히려 드라이버를 생성하고 없애는 작업까지 추가해 버려서 시간이 더 오래 걸리는 것을 확인하였다.... 따라서, with lock을 쓰지 않으며, 드라이버를 새로 생성하지 않고, 팝업창을 제어해야 했다. 따라서, 다음과 같은 방법을 생각했다.
기존의 테이블을 불러오는 것은 멀티쓰레딩을 활용하되, 테이블의 내용을 불러올 때 강의 번호를 queue에 저장하고, 테이블 내용을 불러오는 것을 끝내면 다시 queue에 있는 lecnum을 순회하며 jsscript로 팝업창을 열며, 팝업창만 순회하는 방식을 해보기로 했다.
이걸 적용한 결과
multithreading을 사용했을 때 8개의 항목을 불러오는 데에 14초가 걸렸다면, multithreading을 사용하지 않을 때는 21초가 걸렸다. 건국대 종강시 24년 1학기 전선을 기준으로 '전체대학 전체대학'에서 '전체대학 철학과' 까지는 multithreading을 사용했을때 2분 27초가 걸린 반면, 사용하지 않았을때는 4분 45초가 걸릴 정도로 유의미한 시간 차이를 발생시켰다.
또한 몇 개의 multithreading을 사용할 건지 설정하는 부분도 만들어 놨는데
소수점 차이기는 하지만 10 미만의 숫자에 대해서는 숫자가 커질수록 시간이 빨라졌고, 11을 넘어갔을 때 알고리즘 특성상 시간이 더 오래 걸릴 수 있다는 것도 알아냈다.
즉, 1, 2, 3번 조건을 충족하며, 내가 의도한대로 코드가 작동하였다!!
추가적으로
느낀점
코딩을 할때에는 공식문서를 읽고, 특성에 맞게 설계하는 것도 중요하지만, 꼼수를 생각해 내야 하는 경우가 많다는 것을 알게 되었다.
'웹 > web' 카테고리의 다른 글
| [CORS] json-server, gitpod CORS 오류 (0) | 2024.07.04 |
|---|