ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 대용량 데이터를 수집하기 위한 생산성있는 웹 크롤러의 구조
    Software Development/Data Engineering 2020. 11. 5. 19:26

    1. 웹 크롤러? 웹 스크래퍼?

    [그림 1] Web Crawler Architecture[1]

    웹 크롤링과 웹 스크래핑의 정의에 의하면 둘의 의미는 엄연히 다르다고 볼 수 있지만 위키피디아 정의에 따르면 웹 스크래핑이 웹 크롤링의 부분 집합이라 볼 수 있습니다.

     

    • 웹 크롤링: 조직적, 자동화된 방법으로 월드 와이드 웹을 탐색하는 컴퓨터 프로그램이다. 웹 크롤러는 대체로 방문한 사이트의 모든 페이지의 복사본을 생성하는데 사용되며, 검색 엔진은 이렇게 생성된 페이지를 보다 빠른 검색을 위해 인덱싱한다. 또한 크롤러는 링크 체크나 HTML 코드 검증과 같은 웹 사이트의 자동 유지 관리 작업을 위해 사용되기도 하며, 자동 이메일 수집과 같은 웹 페이지의 특정 형태의 정보를 수집하는 데도 사용된다[1].
    • 웹 스크래핑: 웹 스크래핑은 페이지를 가져 와서 추출하는 것을 포함하여 웹 사이트에서 데이터 추출하는데 사용된다. 웹 스크래퍼는 일반적으로 페이지에서 무언가를 가져와 다른 곳에서 다른 용도로 사용한다. 예를 들어 이름과 전화 번호 또는 회사와 해당 URL을 찾아서 목록에 복사한다[2].

     

    이처럼 웹 크롤링의 의미가 목적에 따라 다중의 의미를 갖기 때문에 웹 크롤링은 목적 지향적 특성에 따라 세분화해 정의를 내려야 할 것 입니다. 데이터 관점에서 크롤링에 접근하기 위해 일반적으로 언급하는 크롤링이 아니라, 웹에서 콘텐츠를 가져오기 위한 크롤링으로 한정해서 웹 크롤링에 대해 알아보겠습니다.

     

    2. 서론

    일반적으로 웹 크롤러의 생산성은 크롤러 자체의 성능이라고 말할 수 있습니다. 웹 크롤러 자체의 생산성(성능)을 올리기 위해서는 Blocking되는 지점을 Non-Blocking으로 처리하면 됩니다. Python에서는 Muli-Thread, Coroutine(asyncio, generator) 등을 통해 IO에 대한 처리 시 성능을 향상 시킬 수 있습니다.

     

    이번에 다룰 주제는 웹 크롤러 자체의 개발에 대해 다룹니다. 대용량 데이터를 다루는 웹 크롤러를 알아보고 이를 어떻게 구조화하여 웹 크롤러 개발 및 유지 보수에 더 효율적으로 접근할 수 있는지 알아보겠습니다.

     

    먼저 들어가기에 앞서 웹 크롤링의 목적이 '주가 예측을 위한 데이터 수집'이라고 하겠습니다.

     

    주가 예측을 하기 위해서 필요한 것은 데이터입니다. 주가 예측을 하기 위해 데이터를 구해가는 과정을 나열해보겠습니다.

     

    1. 주가 예측을 위해 첫 번째로 주가 데이터가 필요하다. 이를 위해서 주가 데이터 가져오는 웹 크롤러를 만든다.
    2. 주가 데이터만 가지고는 주가를 예측하기 어렵다. 거래량 데이터를 가져오는 웹 크롤러를 만든다.
    3. 역시 거래량만으로도 부족하다. 추가적으로 투자자별 수급 데이터를 가져오는 웹 크롤러를 만든다.
    4. 아직은 부족하다. 더 많은 데이터가 필요하다. 그리고 수 많은 모델들이 떠오른다. 기업 공시 및 사업 보고서, 뉴스, 커뮤니티, 증권사 리포트, 각 종 경제 지표 및 정부 정책 등의 데이터를 가져와 모델을 더 고도화 시키고 싶다.
    5. 추가적으로 웹 크롤링해야하는 데이터가 많아지자 전체 웹 크롤러의 구조가 복잡해지고 있다.
    6. 웹 크롤러가 몇 개 안되는 상황에서는 수동으로 데이터를 확인하며 검증했다. 그러나 데이터가 많아지자 검증이 어려워 진다.
    7. 하나의 컴퓨터로 여러개의 웹 크롤러를 실행하니 CPU 및 메모리 점유율이 높아 성능이 느려진다.
    8. 앞으로 더 많은 웹 크롤러가 필요할 수도 있다. 이때 마다 크롤러 환경을 설정하고 관리해야 하며 수백 개의 웹 사이트에서 수만 개의 웹 페이지를 크롤링해야 할지도 모른다.
    9. 비동기 방식으로 웹 크롤링한 데이터를 DB에 넣으려고 하다 보니 동시접근이 발생하여 데이터 누락이 생긴다[3].

     

    이 외에도 발생할 수 있는 문제가 있습니다. 우선 5~9에 해당하는 부분을 차례대로 알아보면서 어떻게 바꿔야 할지 알아보도록 하겠습니다.

     

    3. 웹 크롤러 개선

    3.1 추가적으로 웹 크롤링해야하는 데이터가 많아지자 전체 웹 크롤러의 구조가 복잡해지고 있다.

     

    데이터가 적은 경우에는 간단한 프로그래밍만으로도 원하는 데이터를 가져올 수 있습니다. 그러나 가져올 데이터의 양과 URL 수가 많아지면 크롤러의 소스 코드가 복잡하고 길어지게 됩니다. 

     

    이렇게 되면 유지보수가 어려워 지고 예외 사항에 취약한 크롤러가 될 수 있습니다. 이렇게 잘못 작성된 크롤러는 다음과 같은 문제점을 지니고 있습니다[4].

     

    • 코드 라인 수가 많아짐에 따라 크롤러의 각 부분 (데이터를 받는 부분, 비즈니스 로직 처리하는 부분 등)이 강하게 결합되어 유지보수하기 어려워짐.
    • 기본적인 예외처리가 되지 않을 경우 한 번의 request error가 날 때 뒤이은 request 및 비즈니스 로직과 함께 어플리케이션이 정지되버림.
    • 하나의 요청 처리가 길어지게 되면 다른 요청이 처리가 안되기 때문에 데이터 처리 시간이 늦어지게 됨(일종의 critical path가 생겨버림).

     

    이러한 단점을 보완하기 위해 scrapy와 같은 프레임워크를 사용하여 유지보수 및 예외 사항에 대비한 코드를 작성할 수 있도록 도와줍니다.

    프레임워크 기반 언어 구글 검색결과
    Scrapy 파이썬 481,000
    Nutch Java 476,000
    Crawler4j Java 20,600

    [표 1] 크롤러 프레임워크 비교[5]

     

     

    [그림 2] Scrapy 아키텍처[4]

    Scrapy의 아키텍처 구조는 수집 주기를 설정하는 스케줄러가 존재하고, 수집할 항목을 정의하는 아이템과 수집 데이터의 저장 형식을 정의하는 파이프라인이 출력을 담당합니다. 스파이더를 통해 월드 와이드 웹 정보를 수집합니다.

     

    • 스케줄러 : 스케줄러는 수집주기, 프록시 설정, 멀티 에이전트 설정 기능을 가지고 있어 Scrapy 엔진의 수집에 관련된 정책 사항을 설정하는 역할을 담당
    • 아이템 파이프라인: 아이템 파이프라인은 수집하려는 데이터의 입출력을 담당. 수집하려는 항목을 아이템으로 정의하고 수집한 데이터의 형태를 파일 혹은 DBMS로 직접 입력이 가능하도록 설정할 수도 있다.
    • 스파이더: 수집하는 데이터를 크롤링하는 역할을 한다. 스파이더는 스케줄러로부터 프로젝트에서 크롤링하는 정책에 따라 설정 값을 요청하여 다운로더로부터 받은 크롤링 데이터를 아이템 형태로 아이템 파이프라인으로 전송.
    • 다운로더: http, ftp 프로토콜을 해석하여 웹에 있는 데이터를 다운로드 하는 역할을 담당.

     

    3.2 웹 크롤러가 몇 개 안되는 상황에서는 수동으로 데이터를 확인하며 검증했다. 그러나 데이터가 많아지자 검증이 어려워 진다.

     

    웹 크롤링할 웹 사이트 및 웹 페이지가 많아질 수록 발생할 수 있는 예외 상황과 오류도 증가하게 됩니다. 특히 코드가 너무 방어적이어서 아무것도 하지 않은 채로 조용히 지나쳐버리는 비어있는 except 블록은 가장 안좋은 예라고 볼 수 있습니다.

     

    try:
        scrap()
    except:
        pass

     

    이 코드의 문제는 결코 실패하지 않는다는 것입니다. 이러한 코드는 문제를 숨기고 유지보수를 더욱 어렵게 만듭니다.

    이를 해결 하기 위한 방법으로 다음이 있습니다.

     

     

    • 보다 구체적인 예외를 사용해야 한다.
    • except 블록에서 실제 오류 처리를 한다.

     

    가장 좋은 방법은 두 항목을 동시에 적용하는 것입니다. 보다 구체적인 예외를 사용하면 무엇을 기대하는지 알게 되기 때문에 프로그램을 더욱 유지보수하기 쉽습니다. 또한 다른 종류의 예외가 발생하면 바로 버그로 판단할 수 있으므로 쉽게 대응할 수 있습니다.

     

    예외 상황을 로깅하거나 다른 방법으로 기본 값을 반환하여 예기치 못하는 상황에 대비할 수 있어야 합니다. 여기서 말하는 기본 값은 오류를 발견하기 전이 아니라 오직 오류를 발견한 뒤에만 사용하는 값입니다.

     

    def connect_with_retry(connector, retry_n_times, retry_threshold =5 ):
        """connector의 연결을 맺는다. <retry_n_times>회 재시도.
        연결에 성공하면 connection 객체 반환
        재시도까지 모두 실패하면 ConnectionError 발생
        
        :param connector: '.connect()' 메서드를 가진 객체
        :param retry_n_times: 'connector.connect()' 를 호출 시도하는 횟수
        :param retry_threshold: 재시도 사이의 간격    
        """
        for _ in range(try_n_times):
            try:
                return connector.connect()
            except ConnectionError as e:
                logger.info(
                    "%s: 새로운 연결 시도 %is", e, retry_threshold
                )
                time.sleep(retry_threshold)
        exc = ConnectionError(f"{retry_n_times} 번째 재시도 연결 실패")
        logger.exception(exc)
        raise exc
        

     

    구체적인 예외 상황을 통해 로그를 남길 경우 해당 로그를 모아서 크롤러의 안정성을 위한 밑거름이 될 수 있습니다. 예를 들어 retry에 해당하는 로그를 집계하여 특정 웹 사이트가 어떤 시간 대에 retry가 자주 발생하는지 알 수 있습니다. 나아가 html xpath가 바꼈을 경우 과거에 스크래핑한 데이터를 가지고 변경된 xpath를 찾아 수정할 수도 있을 것 같습니다.

     

    3.3 하나의 컴퓨터로 여러개의 웹 크롤러를 실행하니 CPU 및 메모리 점유율이 높아 성능이 느려진다.

     

    단일 머신에 데이터베이스와 크롤러가 있을 경우 웹 크롤러가 자원을 많이 사용할 경우 데이터베이스의 성능이 느려질 수 있습니다. 이러한 경우에 분산 크롤러를 구축하여 해결할 수 있습니다[6]

     

    Scrapyd를 사용하면 여러 Spider를 동시에 실행할 수 있습니다 (Scrapy를 사용하면 한 번에 하나의 Spider만 실행할 수 있습니다). 각각의 작은 Spider는 큰 Spider가 크롤링 한 부분을 크롤링합니다. 이러한 작은 Spider는 크롤링하는 콘텐츠에서 겹치지 않아야합니다. 하나의 스파이더를 10 개의 작은 스파이더로 분할하면 스크래핑 프로세스가 약 10 배 빨라집니다[7].

     

    Scrapyd로 부족할 경우 scrapy-cluster[8]와 같은 구조로 설계하여 보다 대용량 데이터를 크롤링할 수 있습니다. 

     

    Kafka Monitor: Kafka Monitor는 크롤러 아키텍처의 진입 점 역할을합니다. API 요청의 유효성을 검사하여 나중에 데이터가 올바른 형식인지 확인할 수 있습니다.

     

    Crawler: Scrapy 클러스터는 요청된 크롤링 작업에 대해 크롤링 작업을 조정하기 위해 서로 다른 머신에 있는 여러 개의 스파이더를 만들 수 있습니다. 크롤링 queue는 Redis에서 관리합니다.

     

    Redis Monitor: Redis 모니터는 redis 기반 크롤링 대기열을 조정하는 역할을합니다. 이전 크롤링을 만료하고 기존 크롤링을 중지하고 클러스터에 대한 정보를 수집하는 데 사용됩니다.

     

    3.4 앞으로 더 많은 웹 크롤러가 필요할 수도 있다. 이때 마다 클로러 환경을 설정하고 관리해야 하며 수백 개의 웹 사이트에서 수만 개의 웹 페이지를 크롤링해야 할지도 모른다.

     

    Docker를 사용하여 가상 크롤러의 환경 설정을 빠르게 해결합니다. 이를 통해 클라우드에 배포하여 효율적으로 크롤러의 개수를 늘릴 수 있습니다.

     

    3.5 비동기 방식으로 웹 크롤링한 데이터를 DB에 넣으려고 하다 보니 동시접근이 발생하여 데이터 누락이 생긴다.

     

    이를 해결하기 위해 크롤링한 데이터를 곧 바로 DB에 적재하기 보다는 큐에 저장하여 해결할 수 있습니다. 단순하게 큐잉 시스템으로 Kafka를 사용할 수 있습니다. Kafka는 확장성, 가용성, 데이터 영속성을 지원하기 때문에 데이터 누락에 대한 고민을 해결 할 수 있습니다.

     

     

     

    References:

    [1] ko.wikipedia.org/wiki/%EC%9B%B9_%ED%81%AC%EB%A1%A4%EB%9F%AC

     

    [2] en.wikipedia.org/wiki/Web_scraping

     

    [3] mudchobo.tistory.com/280

     

    [4] engkimbs.tistory.com/893

     

    [5] www.dbguide.net/db.db?cmd=view&boardUid=186818&boardConfigUid=9&categoryUid=216&boardIdx=153&boardStep=1

     

    [6] www.koreascience.or.kr/article/JAKO201925863869112.pdf

     

    [7] ichi.pro/ko/post/266888345874995

     

    [8] github.com/istresearch/scrapy-cluster

     

    [9] scrapy-cluster.readthedocs.io/en/latest/topics/kafka-monitor/index.html

     

     

    댓글

Designed by Tistory.