크롤링 라이브러리: 셀레니움(Selenium)
무한 스크롤에 대응하기가 쉬워 빠르게 크롤러를 만들 수 있을 것이라 판단.
특이사항
셀레니움의 경우는 렌더링의 시간을 기다려야 하며 드라이버 자체를이용해서 렌더링을 해야하기 때문에 대부분의 자바스크립트 코드에 대처할 수 있지만 그만큼 속도가 느리가.
→ 멀티 쓰레딩, 멀티 프로세싱을 통해서 속도를 높이는 방향으로 선택하였다.
멀티쓰레딩과 컨테이너를 활용한 크롤링 구조
클롤링의 해당 링크를 렌더링 하기 위해서는 드라이버를 통해서 연결을 하고 렌더링을 통해서 원하는 태크극 기다려서 원하는 데이터를 가져오게 된다. 이때 하나의 원격 컨테이너 드라이버에 연결될 수 있는 쓰레드 혹은 프로세스는 하나이므로 분산 처리를 위해서 여러개의 컨테이너를 사용하였다.
파이썬은 GIL(Global Interpreter Lock)을 이용해서 쓰레드를 관리한다. GIL은 전역적으로 메모리에 접근하는 쓰레드에게 락을 부여한다는 것인데, 쉽게 말해서 한번에 하나의 쓰레드만 메모리에 접근할 수 있게 하여 강제적으로 thread-safe 상태를 유지한다는 것이다.
한번에 하나의 쓰레드만 CPU의 통제권을 가지게 된다.
즉, 다시말하면 안전성을 위해서 효율성을 포기한 것으로 다른 뜻으로 본다면 파이썬에서는 멀티쓰레딩이 필요없는 것이 아닌가? 하는 의문이 생긴다.
→ 실제로도 파이썬을 통해서 되도록이면 멀티 쓰레딩을 권장하지 않는다고 한다.
하지만, 의무적으로 딜레이가 필요한 프로그램의 경우는 얘기가 달라진다. 쓰레드 간의 context change가 일어나면서 딜레이(time.sleep
)를 효율적을 처리할 수 있기 때문이다.
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=9) as executor:
executor.map(Callable, Iterable)
큐(queue)를 이용한 멀티 쓰레딩 구조
클래스 내부에 가용한 포트에 대한 타입 변수(static)로 만든 후에 priority queue를 통해서 쓰레드가 pop을 통해 연결할 포트를 얻게 되고 크롤링이 끝나게 되면 다시 반납을 하게 된다.
priority queue를 사용한 이유는 pop, push에 대한 직관성을 높이기 위함이다. append
와 insert
를 사용하여도 상관은 없지만 코드의 직관성을 높이기 위해서 사용하였다.
try:
port = heapq.heappop(TopViewCrawler.valid_ports)
except IndexError:
time.sleep(3)
# Done
heapq.heappush(TopViewCrawler.valid_ports, port)
아래의 GIF를 보면 브라우저에서 네트워크를 Slow 3G로 변경한 상태에서 동적으로 HTML이 로딩되는 모습이다. 마지막 부분에 태그가 바뀌는 부분을 확인할 수 있다.
자바스크립트 코드가 실행되기 까지 시간이 필요하다.
구체적으로 보자면 다음과 같은 태그의 변화가 있다. 로딩이 완료되기 전에는 main
이지만 로딩이 완료된 상태에서는 complete
클래스가 뒤따라 나오게 된다.
로딩 전 | 로딩 후 |
---|---|
main | main.complete |
범용적으로 적용되는 태그의 변화가 아니다. 사이트마다 코드가 다르기 때문에 이를 다른 사이트에서 적용할 수는 없다. 즉, 우리는 complete
클래스를 찾을 수 있는 상황까지 기다려야 한다.
WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located(
(By.CSS_SELECTOR, MAIN_BOARD_SELECTOR)
)
)