본문 바로가기
파이썬/주식

Python, multiprocessing으로 좀 더 빠른 Naver Finance 크롤러 만들기 / multiprocessing crawling

by 행복론자 2019. 11. 27.

지난번 포스팅에서는 Naver Finance에 있는 한 기업의 재무제표를 크롤링하는 과정을 담았습니다.

하지만 코스피/코스닥에 상장된 모든 기업의 정보를 받아와 파싱한다고 하면 어떨까요?

 

 

2천여개의 기업 정보를 파싱하는 것은 그렇다치지만 정보를 받아오는 과정, requests를 하나씩 보내고 받는 일련의 과정에 시간이 많이 듭니다. 

 

이 여러 번의 작업을 나눠서 병렬처리를 통해 빠르게 해주는 것이 Python multiprocessing 패키지입니다. 

multiprocessing은 기본 내장 패키지로 Python이 설치되어 있다면 따로 설치할 필요가 없습니다.

 

multiprocessing is a package that supports spawning processes using an API similar to the threading module. The multiprocessing package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock by using sub-processes instead of threads.
Due to this, the multiprocessing module allows the programmer to fully leverage multiple processors on a given machine. It runs on both Unix and Windows.
- Docs

 

우선 multiprocessing의 장점을 알아보기 위해 이를 사용하지 않고 모든 상장 기업 정보를 얻어와 보겠습니다.

 

import requests
import time
import multiprocessing
import pandas as pd

BASE_URL = 'https://finance.naver.com'
all_urls = []

def generate_urls():
    df = pd.read_html('http://kind.krx.co.kr/corpgeneral/corpList.do?method=download&searchType=13', header=0)[0]
    df.종목코드 = df.종목코드.map('{:06d}'.format)
    for idx, code in enumerate(df['종목코드']):
        all_urls.append(BASE_URL + '/item/main.nhn?code=' + code)

def crawl(url):
    res = requests.get(url)
    print(res.status_code, res.url)


if __name__ == '__main__':
    generate_urls()
    start_time = time.time()
    
    for url in all_urls:
        crawl(url)
        
    print('({:.2f}sec)'.format(time.time() - start_time))

 

crawl 함수에서 print를 안하면 조금 더 빠르기는 하나 324초나 걸립니다. (제 환경이 느린가요?)

 

 

아무튼 하나씩 다 받아올 때는 저 정도 걸립니다. 다른 빠른 작업들에 대해서는 multiprocessing을 사용하나 안하나 장점을 느끼실 수 없습니다. 원래 빠르기 때문입니다.

 

그럼 multiprocessing을 적용해보겠습니다.

import requests
import time
import multiprocessing
import pandas as pd

BASE_URL = 'https://finance.naver.com'
all_urls = []

def generate_urls():
    df = pd.read_html('http://kind.krx.co.kr/corpgeneral/corpList.do?method=download&searchType=13', header=0)[0]
    df.종목코드 = df.종목코드.map('{:06d}'.format)
    for idx, code in enumerate(df['종목코드']):
        all_urls.append(BASE_URL + '/item/main.nhn?code=' + code)

def crawl(url):
    res = requests.get(url)
    print(res.status_code, res.url)


if __name__ == '__main__':
    generate_urls()
    start_time = time.time()

    pool = multiprocessing.Pool(multiprocessing.cpu_count())
    results = [pool.apply_async(crawl, (url,)) for url in all_urls]
    pool.close()
    pool.join()
    posts_list = [res.get() for res in results]

    print('({:.2f}sec)'.format(time.time() - start_time))

 

 

pool을 만들어 multiprocessing을 적용했습니다. cpu_count() > 4개로 처리하니 약 4배 가량 빨라졌습니다. 

 

Comment

1. pool의 apply_async는 작업을 나눠서 진행하는 것도 모자라 비동기식(async)으로 결과가 돌아오기를 기다리지 않는 방식입니다.

이렇게 작업이 완료되면 AsyncResult라는 Object가 반환되고 이를 get()을 이용하여 하나씩 꺼내야합니다.

물론 비동기식를 사용한다고 하여 결과가 return됨을 보장하지 않기에 이에 대비한 time out error처리가 있으면 좋겠지만 이 경우에는 생략했습니다.

 

2.  빨리 돌아온 결과부터 저장하기 때문에 보낸 순서대로 기업 크롤링 결과가 저장되어 있음을 보장하지 않습니다.

 

3. cpu_count 함수를 이용해 4개의 프로세스로 했지만 이 수를 늘이면 더 빨라지기는 합니다만 어느 순간부터는 오히려 속도 지연이 발생할 수 있습니다. (10개로 돌리면 51초 나옵니다.)

 

4. 사실 주식과 큰 상관 없는 카테고리지만 주식 카테고리에 넣었습니다. ㅎㅎ

 

 

 

제 블로그에 방문해주셔서 감사합니다.
좋아요, 댓글은 제가 글을 쓰는데 큰 힘이 됩니다.
위 링크를 클릭하면 쿠팡 파트너스 활동을 통해 일정 수익이 발생할 수 있음을 알려 드립니다.

 

반응형
이 포스팅은 쿠팡파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

댓글