도커 환경에서 셀레니움을 이용한 웹 스크래핑

ywoosang·2021년 11월 30일
5

들어가며

디렉토리 구조

프로젝트 디렉토리 구조다. 만약 다르다면 본인의 프로젝트 구조에 맞게 도커파일을 수정하면 된다.

├─ app/
│   ├─ config/
│   ├─ models/
│   ├─ routers/
│   ├─ services/   
│   ├─ schemas/    
│   ├─ utils/       
│   └─ main.py  
├─ venv/
└─ requirements.txt 

위처럼 디렉토리 구조를 짰을 때 app/ 폴더만 COPY 로 컨테이너 내부로 가져오면 된다.

Docker 파일 작성

Dockerfile 은 아래와 같이 구성했다.

FROM python:3
WORKDIR /usr/src
RUN apt-get -y 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 -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/` curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip
RUN mkdir chrome
RUN unzip /tmp/chromedriver.zip chromedriver -d /usr/src/chrome
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
CMD [ "python", "app/main.py" ]

FROM python:3 : 도커허브에서 베이스 이미지로 python3 를 이용한다.
https://hub.docker.com/_/python

WORKDIR /usr/src 컨테이너 내부에 /usr/src 폴더 내부에서 명령어가 실행된다.

RUN apt-get -y update : 운영체제에서 사용 가능한 패키지들과 그 버전에 대한 정보를 업데이트한다. -y 옵션은 설치시 사용자의 승인을 요구할때 자동적으로 Y 를 입력하도록 해 터미널 상호작용 없이 바로 설치하도록 추가했다.

RUN apt install wget : Wget은 HTTP 통신 또는 FTP 통신을 사용해 서버에서 파일 또는 콘텐츠를 다운로드할 때 사용하는 소프트웨어다. 여러 파일을 한 번에 다운로드하거나 웹 페이지의 링크를 순회하며 여러 콘텐츠를 자동으로 다운로드할 수 있다.

RUN apt install unzip : 이후 다운로드 받을 zip 파일의 압축을 풀기 위해 설치했다.

RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb RUN apt -y install ./google-chrome-stable_current_amd64.deb : wget 을 이용해 구글 크롬을 컨테이너 내부에서 다운로드 받는다.

RUN apt -y install ./google-chrome-stable_current_amd64.deb : 다운로드 받은 .deb 파일을 이용해 구글 크롬을 컨테이너 내부에서 설치한다.

RUN wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/ curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE /chromedriver_linux64.zip : wget 을 이용해 셀레니움 실행을 위한 크롬 드라이버를 다운받는다.

RUN mkdir chrome: 크롬 드라이버를 설치할 경로로 chrome 디렉토리를 생성한다. workdir 이 /usr/src 이므로 /usr/src/chrome 경로로 생성된다.

RUN unzip /tmp/chromedriver.zip chromedriver -d /usr/src/chrome: unzip 명령어로 크롬 드라이버의 압축을 푼다. -d 옵션은 압축을 풀었을 때 생성할 디렉토리를 지정하는 것으로 /usr/src/chrome 로 지정했다.

COPY requirements.txt ./: 패키지 의존성을 설치하기 위해 가상환경에서 pip freeze > requirements.txt 로 생성한 의존성 파일을 컨테이너 내부로 COPY 한다.

RUN pip install --no-cache-dir -r requirements.txt : 패키지 의존성을 설치한다.

COPY app ./app: 프로젝스 소스 폴더를 컨테이너 내부로 COPY 한다.

CMD [ "python", "app/main.py" ]: 웹 애플리케이션 실행 명령어로 app 디렉토리에 있는 main.py 를 실행한다.

크롬 드라이버 경로 설정

가장 중요한 부분이 드라이버 경로를 잡는 것이다. 로컬에서 사용하는 경로 그대로 Docker 에 집어넣게 되면 컨테이너와 Host OS 에서 크롬 드라이버의 경로가 다르기 때문에 드라이버를 인식할 수 없게 된다.

따라서 위에서 컨테이너 내부에 설치한 크롬드라이버의 경로로 설정해준다.

 chromedriver = "/usr/src/chrome/chromedriver"

Headless Chrome

브라우저(크롬 등)을 이용해 인터넷을 브라우징 할 때 기본적으로 창이 뜨고 HTML파일을 불러오고, CSS파일을 불러와 어떤 내용을 화면에 그러야 할지 계산을 한다. 이러한 작업들은 브라우저가 해주게 된다.

생각해야 할 점은 현재 셀레니움의 실행 환경은 Docker 환경이다. Docker 와 같은 리눅스 기반 OS 에는 ‘화면’ 자체가 존재하지 않기 때문에 일반적인 방식으로는 크롬을 사용할 수 없다. 따라서 Headless 모드로 브라우저 창을 실제로 운영체제의 ‘창’으로 띄우지 않고 대신 화면을 그려주는 작업(렌더링)을 가상으로 진행하는 방법을 이용한다. Headless 모드를 사용하면 실제 브라우저와 동일하게 동작하지만 창은 뜨지 않는다.

Headless 옵션을 구성한다.

options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")
options.add_argument(f'user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36')

webdriver.ChromeOptions()

ChromeOptions()를 만들어 add_argument를 통해 Headless모드, 크롬 창 크기, gpu(그래픽카드 가속)를 사용하지 않는 옵션을 넣어준다.

크롬 창의 크기를 지정해 준 이유는 일반적으로 노트북이나 데스크탑에서 사용하는 모니터의 해상도가 1920x1080이기 때문이다. 즉, 보는 것 그대로 크롬이 동작할 것이라고 기대할 수 있다.

options.add_argument('headless') 로 크롬이 Headless모드로 동작하도록 만든다.

헤드레스 브라우저를 판독하여 거부를 하는 사이트는 대부분 user-agent 값으로 판단하는 경우가 많아 user-agent 만 수정해도 차단이 완화되는 사이트들이 많다.

driver = webdriver.Chrome('<chromedriver-path>', chrome_options=options)

셀레니움으로 스크래핑하는 방법은 아래 블로그에 설명되어 있다.
https://fenderist.tistory.com/168

예외처리

셀레니움에서 요소가 존재하지 않을 경우 코드가 그 다음으로 흘러가지 못하고 중단되는것을 볼 수 있다.
이때 try/except 를 사용한 예외처리를 해 요소가 존재할 경우 해당 과정을 수행하고 아닐 경우에는 다른 행동을 취할 수 있게 할 수 있다.

try:
    WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "rgFsT"))
    )
    ... 
except Exception as error:
    print(error)
     ... 
profile
백엔드와 인프라에 관심이 많은 개발자 입니다

0개의 댓글