[Python] Flask API와 Selenium을 활용한 동적 웹 크롤링 구현 후기

Seong-Am Kim
6 min readSep 3, 2020

먼저 ‘크롤링’이라는 단어보단 스크래핑이라는 표현이 더 정확하지만 이를 구분하지 않고 부르는 경우가 많기에 이해를 돕기 위해 이 글에서는 크롤링이라 표현함을 알린다.

일반적으로 파이썬을 이용하여 크롤링을 하게 되면 Selenium을 가지고 Scheduler 패키지등을 이용해 크롤링을 주기적으로 돌리거나 스크립트 실행을 통한 크롤링을 수행하는 경우가 많을것이다.

Photo by Chris Ried on Unsplash

오늘 소개 하려고 하는 것은 이와 같이 수동 또는 특정 시간에 크롤링을 실행하는것이 아니라 유저가 필요한 데이터를 요청시 동적으로 크롤링을 하려는 것을 이야기 하고자 한다.

강의가 아니라 그냥 이렇게도 할 수 있다라는 것과 느낀점을 공유하기 위함이 크다. 궁금한 점이 있으면 개인적인 연락을 부탁한다.

회사의 프로젝트라 모든 코드를 공개할 수 없는 점 또한 양해 부탁드린다.

프로젝트 소개

해당 프로젝트의 큰 흐름을 소개 하자면 다음 그림과 같다.

프로젝트 흐름도
  1. Web Server에서 API 요청 (유저가 버튼 클릭)
  2. Flask API에서 이를 수신 하여 Selenium 크롤링을 시작하도록 전달
  3. Selenium을 이용한 크롤링 수행 및 데이터 저장, 반환
  4. Flask API에서 크롤링된 데이터를 Web Server로 반환

참고사항

코드를 보는데 이해를 돕기 위한 것으로 해당 프로젝트는 아래 사항을 포함하고 있다.

  1. 실제 프로젝트를 진행 했을때는 업무상 다른 사람과 협업을 하여야 했고 문서화가 필요했기에 Swagger라는 API Document 자동화 툴을 이용하였다.
    Swagger를 이용하면 소규모 프로젝트에서 커뮤니케이션과, 유지 보수의 편성을 도모할 수 있다. 자세한 설명은 공식 홈페이지에서 확인해보길 바란다.
    해당 프로젝트에선 Swagger의 파이썬 패키지 flask_restplus를 이용했다.
  2. 외부 프로그램의 데이터 크롤링이 필요했기 때문에 윈도우 프로그램 제어 툴인 Pywinauto를 이용했다.
  3. 화면 제어로 인해 여러개의 Request들에 대하여 동시에 처리하게 되면 외부 프로그램 제어 또는 셀레니움에서 창에 대한 인식과 키보드 입력등의 이벤트 제어에 대한 컨트롤에 대한 에로사항이 있다.
    이를 위해 하나씩 Request를 처리하여야 했으며 Thread를 하나 열어 Queue 방식으로 Request를 하나씩 꺼내어 처리했음을 알린다.

구현 코드

위의 코드는 Swagger를 이용하는 flask_restplus 코드로 일반적인 Flask 라우터와 크게 다르지 않다.

(21행)before_first_request 함수의 경우 Request들에 대하여 Queue로 처리하기 위함이며 before_first_request 함수는 처음 request가 들어올때 호출된다.

(23행) threading.Thread(target=req.async_requests).start()를 통해 새로운 쓰레드를 하나 생성해서 시작하여 14번 행에서 이벤트 루프를 돌면서 새로운 request가 들어온지 계속 적으로 검사하고 들와왔을경우 해당 request에 대하여 동기적으로 순차적 처리를 하게 된다.

위의 참고사항 3번에 보면 왜 이렇게 처리했는지 알 수 있다. 만약 입력에 대한 이벤트 처리나 외부 프로그램의 제어가 없다면 동시적으로 Request들에 처리하는게 효율면에서 낫다고 본다.

(35행) get 함수가 request를 받는 함수로 42행에 q.put 메소드로 해당 요청에 대해 Queue 대기열에 추가한다.

(14행) async_request 함수를 통해 request가 있을경우 이를 self.crawling_api.request_handling(q.get()) 크롤링을 시작하는 함수로 전달한다.

이후 크롤링이 실행되며 이에 대한 반환값으로 다른 Router를 API를 요청했을 때 결과 값이 나오게 된다.

해당 프로젝트는 크롤링 실행에 대한 API만을 요청하고 정상적으로 실행됐는지에 대한 응답값만 밖에 되며 종료시 웹 API를 호출 함으로 이를 종료됐음을 알린다.

이는 시간이 비교적 걸리기 때문에 사용자 경험을 향상 시키기 위한 것으로 크롤링 실행에 대한 요청과 동시에 이에 대한 데이터에 대한 반환값을 해주어도 무방하다.

(이의 경우 Client가 크롤링을 끝나게 될때 까지 기다려야 하는 단점이 있다. 해당 프로젝트는 이를 제거하기 위해 크롤링 실행에 대한 API와 데이터를 요청하는 API를 별도로 구성하여 설계했다.)

결과

Swagger 사용 예시

Swagger를 이용하게 되면 위와 같이 별다른 코드를 작성하지 않고 손쉽게 API 문서에 대한 자동화가 가능하다.

보안 문제로 모든 사진을 첨부하지 않았지만 모델에 대한 설명, 필수 파라미터 및 선택 파라미터등 다양하게 API와 관련된 설명들을 추가하여 자동화 할 수 있으니 소규모 팀에서 적극 활용하시는걸 추천 드린다.

실제 구동 화면 (인증 토큰은 테스트 값을 사용했다.)

후기

기존의 크롤링은 많이 해보았지만 외부 프로그램과 API요청시 크롤링을 시작하는 것을 구현한다는 점에서 재미있게 작업에 임했던것 같다.

다만 이 방법은 사용자의 경험을 결코 좋게 할 수 없으므로 가능하다면 미리 크롤링을 통해 데이터를 받아놓고 요청시 바로 데이터를 응답하도록 하는 것이 우선 순위에 놓인다고 본다.

(해당 프로젝트는 크롤링을 실행할때 마다 비용이 발생되므로 모든 데이터를 가지고 온다면 엄청난 비용이 발생하기에 어쩔 수 없이 선택한 방법이다.)

아쉬운 점이라면 Request 들에 대하여 화면 제어로 인해 Queue를 이용해 하나씩 동기적으로 수행하도록 만든 부분인데 이렇게 구현 하면 프로그램의 안정성은 높아질 수 있지만 대기가 많아 지게 되면 속도에 대한 이슈가 있다. (크롤링의 대기 순위가 밀리면 자연스럽게 느려질 수 밖에 없다.)

화면에 대한 제어를 보이는 윈도우에 대한 화면에 관한 속성이 아니라 고유한 키를 가지고 핸들링 할 수 있다면 이를 통해 해결 할 수 있다고 본다.

또 다른 방법은 기존의 방법을 유지한체 여러개의 가상 윈도우를 사용해서 Request값이 들어올 경우 이를 각 가상 컴퓨터 마다 분배하는 것인데 리소스 활용 측면에서 효율적인가에 대한 의문은 사실 있다.

크롤링이 본업이 아닌지라 이런 저런 아이디어로 적용해봤는데 최선에 대한 아쉬움은 항상 있는듯 하다.

기회가 되면 이것 말고 다른 크롤링 했던 프로젝트들에 대한 아이디어를 글을 남기도록 하겠다.

--

--