Docker

Docker 환경에서 크롤링하기(동적 웹)

Developer trainee_J ^~^ 2023. 6. 30. 05:45

프로야구 데이터 시각화 프로젝트를 진행하면서,  statiz.co.kr 웹 페이지의 박스스코어 데이터를 크롤링했었다. 그런데 데이터 무결성 검사를 해보니 대략 10%의 데이터가 비어있는것을 확인했다. 원인 분석을 위해 일단 크롤링 DAG부터 확인했는데 정상적으로 작동하고 있었고, 매일매일 데이터가 적재되고있었다. 비어있는 데이터를 추적해보니 경기 진행일 전체의 데이터가 아니라 경기하나의 데이터만 빠져있는 것을 확인했고, 실제 웹 페이지에서도 확인해보니 박스 스코어 데이터가 비어있었다.. 

사실 저번달에도 이런 경우를 확인했는데 월 마감이 될때마다 데이터가 들어오는걸 확인하고 월 재적재 DAG를 만들어 놨었는데 아예 개막전 경기부터 비어있었다니 정말 충격적이었다.. 

 

어쩔 수 없이 데이터 크롤링하는 웹을 바꾸기로 결정하고 가장 잘 알려진 네이버 스포츠의 데이터를 크롤링하기로 했다. 

 

필자는 매일 작동하는 DAG를 만들어서 DAG 작동일 '전날'의 데이터를 크롤링하는게 목표였다. 

우선 야구 -> 일정/결과 페이지를 확인해보니 '경기결과'버튼이 존재했고, 태그에 url 주소가 있는걸 확인했다.

target_url = f'https://sports.news.naver.com/kbaseball/schedule/index?date={y_date}&month={y_month}&year={y_year}&teamCode='
html = urllib.request.urlopen(target_url).read()
bsObject = BeautifulSoup(html, 'html.parser')
init_url = str(bsObject.find_all('span', {'class' : 'td_btn'})).split('a href=')[1:]

 

일단 해당 화면의 html을 크롤링한 뒤 링크부분만 잘라내어 리스트형식으로 가져와 봤다. 

링크는 잘 가져와졌고  'record'와 'video'로 나눠져있었다. video는 '경기결과'버튼 옆에 있는 '경기영상'버튼의 주소였다. 

아무튼 init_url을 for문에 돌리면서 'record'가 존재하는 링크만 선택하여 접속 url을 만들어줬다. 그리고 해당 월 전체 경기의 주소가 있으므로 어제 날짜의 경기만 선택하게 조건을 걸어줬다. 

 

여기까지는 statiz에서 하던 것과 거의 비슷해서 아주 간단했다. 이제 이 화면의 html데이터를 크롤링해봤는데 이상하게도 데이터가 하나도 들어있지않았다. 화면 소스를 확인해보니 동작에 따라 html의 데이터가 변하는 '동적 페이지'였다. 이렇게 되면 BeautifulSoup모듈로 크롤링하는 것은 한계가 있었고 다른 방법을 찾아야했다. 

 

"selenium 모듈 사용하기"

selenium 모듈은 처음에 statiz크롤링을 할 때 시도해본 모듈이었는데, 로컬환경에서는 큰 문제없이 크롤링되었지만 Docker환경에서는 동작하지 않아서 중간에 포기한 방법이었다. 하지만 지금은 다른 방법이 없어서 결국 Docker환경에서 크롤링이 가능한 환경을 세팅해야했다.

 

 

Docker 환경에서 크롤링하기

야구 데이터를 지속적으로 얻기 위해서 http://www.statiz.co.kr/main.php 사이트에서 경기 정보를 크롤링하여 데이터를 적재하는 DAG를 작성하는과정중 예상치 못한 문제가 발생했다. 크롤링을 제대로

developer-trainee-j.tistory.com

<당시 실패상황을 다룬 포스팅링크>

지난 실패를 확인해보면서 selenium모듈을 사용하기 위한 환경 구성 과정을 생각해봤다. 

 

1. selenium은 webdriver를 반드시 사용해야한다. 

2. 크롬에서 webdriver를 사용하기 위해서는 크롬드라이버와 크롬을 설치해야한다. 

3. 구글링을 해보니 크롬과 크롬드라이버의 버전이 '일치'해야한다. 

 

1번 과정은 이미 예전에 selenium모듈을 requirements.txt 파일안에 넣어두었다. 컨테이너에도 정상적으로 설치되어있는걸 확인했다. 

2번 과정은 지난 트라이때도 인지하고있었고 Dockerfile에 설치 명령어를 넣어뒀다. 

3번 과정, 지난번에 간과한 부분이다. 단순히 설치만하면 작동할거라 생각했는데.. 이번에는 버전까지 명시하고 드라이버 설치경로를 지정하지 않은 상태로 Dockerfile을 작성했다. 

FROM apache/airflow:2.5.2

USER root 
WORKDIR /usr/src
RUN apt-get update
RUN apt install wget
RUN apt install unzip  
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
RUN apt -y install ./google-chrome-stable_current_amd64.deb
RUN wget  https://chromedriver.storage.googleapis.com/114.0.5735.90/chromedriver_linux64.zip


RUN unzip chromedriver_linux64.zip
USER airflow


WORKDIR /opt/airflow

RUN pip install --upgrade pip 

COPY requirements.txt ./

RUN pip install -r requirements.txt

이대로 이미지를 만든 후 airflow를 실행시켰다.

 

airflow 서버는 정상적으로 올라왔고 driver가 정상적으로 작동하는지 확인하기 위해 샘플 DAG를 만들어봤다. 

DAG를 작동시켜보니 

 

"(Message: unknown error: Chrome failed to start: crashed. (unknown error: DevToolsActivePort file doesn't exist)

(The process started from chrome location /usr/bin/google-chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.)"

에러가 발생했다. 저번에 발생했던 에러와는 달라서 구글링을 해보니 driver 옵션을 추가하라는 내용이 대다수였다. 

options = webdriver.ChromeOptions()

## 보안을 위한 sandbox를 사용하지 않겠다는 옵션
options.add_argument('--no-sandbox')

##  크롬창을 띄우지 않게 하는 옵션이다.
options.add_argument('--headless')

## /dev/shm 파티션 용량문제로 문제가 발생할 수 있기때문에
## 사용하지 않겠다는 옵션
options.add_argument('--disable-dev-shm-usage')

## 단일 프로세스로 실행하는 옵션
## 더이상 공식적으로 지원되지 않아서 작동은하지만 문제가 발생할수있다
options.add_argument("--single-process")

 

우선 이렇게 옵션을 추가해줬다. 이제 되겠지하면서 DAG를 실행시켰는데도 똑같이 에러가 발생했다. 진짜 이거 해결하려고 6시간동안 구글링하거나 image를 만지작거렸는데 실제 원인은 정말 단순했다. 

 

driver = webdriver.Chrome(service=service, options=options)

이렇게 driver를 연결해놓고 아래쪽에 또 연결을 해버렸다.

driver = webdriver.Chrome()

그렇다 보니 자꾸 driver가 충돌나서 에러가 발생했던 것이었다. 역시 에러는 오타나 코드 안에 있는 헛짓거리부터 찾아봐야하는것 같다. 그래도 이제 Docker안에서도 동적 웹 크롤링을 정상적으로 할 수 있다는 것에 만족한다. 

 

 

다음 포스팅은 naver스포츠에서 크롤링한 데이터를 전처리 했던 과정을 적어보려한다.