- 새로운 웹 크롤링 도구 Playwright2024년 09월 27일
- 31514
- 작성자
- 2024.09.27.:40
최근 셀레니움을 사용해서 웹 페이지를 크롤링하던 중, 아래와 같은 문제에 부딪혔다.
- StaleElementReferenceException 오류로 인한 신뢰성 감소
- 크롬 드라이버가 멈춰 무한루프에 빠지는 문제
문제를 해결하기 위해 다음과 같은 과정을 수행했다.
- 특정 요소가 로딩되는 시간을 명시적으로 지정하기
- time.sleep()을 사용하여 충분한 대기 시간 보장하기
- StaleElementReferenceException 오류가 발생하면 잠깐의 대기 시간을 갖고 재시도하기
그럼에도 불구하고 코드의 실행 시간만 증가할 뿐 문제는 해결되지 않았다.
계속해서 고민하던 중 "내가 너무 하나의 도구에만 의존하고 있는 건 아닐까?"라는 생각이 들었다.
그 결과 도입한 도구는 Playwright이다.
Playwright이란?
Playwright은 셀레니움과 같이 웹 크롤링에 사용되는 웹 자동화 라이브러리다.
차이점은 Playwright의 경우 비동기적으로 웹 브라우저와 상호작용하는데 특화되어 있다는 점이다.
따라서 효율적인 자원 사용이 가능하고, 태스크를 병행하여 실행할 수 있어 처리 속도가 빠르다.
Playwright의 작동 방식
<비동기 I/O>
Playwright은 브라우저와 상호작용할 때 네트워크 통신, 파일 처리, 타이머 등 I/O 작업이 많이 발생한다.
이러한 작업을 비동기적으로 처리함으로써, I/O 작업을 기다리는 동안 다른 작업을 병행할 수 있다.
<코루틴과 async/awiat>
Playwright는 파이썬에서 비동기 함수로 실행되며, 이를 위해 async def와 await 키워드를 사용한다.
모든 Playwright 동작은 비동기 함수로 정의되어 있기 때문에, 함수 호출시 작업이 바로 완료되지 않고, 코루틴(Coroutine)으로 반환된다.
코루틴은 파이썬에서 비동기 작업을 지원하는 중요한 개념 중 하나로, 함수나 작업을 중단하고 다시 재개할 수 있는 특수한 형태의 함수다.
<예시 코드>
import asyncio import time async def find_user_async(n): for i in range(1, n + 1): print(f'{n}명 중 {i}번 째 사용자 조회 중 ...') await asyncio.sleep(1) print(f'> 총 {n}명 사용자 비동기 조회 완료!') async def main(): start = time.time() # gather를 사용하여 모든 코루틴 작업을 동시에 병렬로 실행하고 모든 작업이 끝날 때까지 대기 await asyncio.gather( find_user_async(3), find_user_async(2), find_user_async(1), ) end = time.time() print(f'>>> 비동기 처리 총 소요 시간: {end - start}') asyncio.run(main()) # 출력 결과 3명 중 1번 째 사용자 조회 중 ... 2명 중 1번 째 사용자 조회 중 ... 1명 중 1번 째 사용자 조회 중 ... 3명 중 2번 째 사용자 조회 중 ... 2명 중 2번 째 사용자 조회 중 ... > 총 1명 사용자 비동기 조회 완료! 3명 중 3번 째 사용자 조회 중 ... > 총 2명 사용자 비동기 조회 완료! > 총 3명 사용자 비동기 조회 완료! 총 소요 시간: 3.00초
Playwright은 순서를 보장할 수 없을까?
위에서 설명했듯이 Playwright은 비동기적으로 동작하기 때문에 순서를 보장받지 못할거라고 생각하기 쉽다.
import asyncio import time async def find_user_async(n): for i in range(1, n + 1): print(f'{n}명 중 {i}번 째 사용자 조회 중 ...') await asyncio.sleep(1) print(f'> 총 {n}명 사용자 비동기 조회 완료!') async def main(): start = time.time() await asyncio.gather( await find_user_async(3), await find_user_async(2), await find_user_async(1), ) end = time.time() print(f'총 소요 시간: {end - start:.2f}초') asyncio.run(main()) # 출력 결과 3명 중 1번 째 사용자 조회 중 ... 3명 중 2번 째 사용자 조회 중 ... 3명 중 3번 째 사용자 조회 중 ... > 총 3명 사용자 비동기 조회 완료! 2명 중 1번 째 사용자 조회 중 ... 2명 중 2번 째 사용자 조회 중 ... > 총 2명 사용자 비동기 조회 완료! 1명 중 1번 째 사용자 조회 중 ... > 총 1명 사용자 비동기 조회 완료! 총 소요 시간: 6.00초
하지만 순서를 보장하고 싶은 코드에 await을 붙이면 위와 같은 결과를 볼 수 있다.
그러나 총 소요 시간이 3초에서 6초로 증가하여 비동기적 처리의 장점 중 하나인 빠른 처리 속도가 상쇄된다.
따라서 트레이드오프를 고려하면서 사용해야 할 거 같다.
결과
셀레니움 기반으로 실행되는 코드를 Playwright으로 바꾸면서 신뢰성을 보장하고, time.sleep()과 명시적 대기 시간으로 인해 900초가 넘게 걸리는 코드를 평균 310초로 줄일 수 있었다.
'개발' 카테고리의 다른 글
[MySQL] DELETE & UPDATE (0) 2024.10.04 멀티 프로세싱 & 멀티 스레딩 & 비동기 처리 (0) 2024.10.02 하둡 기초 개념 (0) 2024.09.30 쿼리가 무한 루프에 빠지는 문제 (0) 2024.09.19 SQL 사전 (0) 2024.09.10 다음글이전글이전 글이 없습니다.댓글