Python AsyncIO를 이용한 웹사이트 멀티 스크랩핑 예시

2022-07-09

.

Data_Engineering_TIL(20220709)

[참고자료]

인프런 “우리를 위한 프로그래밍 : 파이썬 중급 (Inflearn Original)” 강의내용

URL : https://www.inflearn.com/course/프로그래밍-파이썬-중급-인프런-오리지널/dashboard

[학습내용]

먼저 터미널을 열고 테스트할 파이썬 가상환경을 아래와 같은 명령어를 실행해서 구성해준다.

$ mkdir asyncio_test

$ cd asyncio_test

$ python3 -m venv asyncio_test

$ cd asyncio_test 

$ ll
total 8
drwxr-xr-x  12 minman  staff   384B  7  9 13:24 bin
drwxr-xr-x   2 minman  staff    64B  7  9 13:24 include
drwxr-xr-x   3 minman  staff    96B  7  9 13:24 lib
-rw-r--r--   1 minman  staff    94B  7  9 13:24 pyvenv.cfg

$ cd bin

$ ll
total 72
-rw-r--r--  1 minman  staff   8.6K  7  9 13:24 Activate.ps1
-rw-r--r--  1 minman  staff   1.9K  7  9 13:24 activate
-rw-r--r--  1 minman  staff   884B  7  9 13:24 activate.csh
-rw-r--r--  1 minman  staff   2.0K  7  9 13:24 activate.fish
-rwxr-xr-x  1 minman  staff   271B  7  9 13:24 pip
-rwxr-xr-x  1 minman  staff   271B  7  9 13:24 pip3
-rwxr-xr-x  1 minman  staff   271B  7  9 13:24 pip3.9
lrwxr-xr-x  1 minman  staff     9B  7  9 13:24 python -> python3.9
lrwxr-xr-x  1 minman  staff     9B  7  9 13:24 python3 -> python3.9
lrwxr-xr-x  1 minman  staff    42B  7  9 13:24 python3.9 -> /opt/homebrew/opt/python@3.9/bin/python3.9

$ source activate
# 비활성화 명령어 : deactivate

$ pip list
Package    Version
---------- -------
pip        22.0.4
setuptools 60.10.0
WARNING: You are using pip version 22.0.4; however, version 22.1.2 is available.
You should consider upgrading via the '/Users/minman/minman_study/asyncio_test/asyncio_test/bin/python3.9 -m pip install --upgrade pip' command.

$ pip install asyncio
Collecting asyncio
  Downloading asyncio-3.4.3-py3-none-any.whl (101 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.8/101.8 KB 1.1 MB/s eta 0:00:00
Installing collected packages: asyncio
Successfully installed asyncio-3.4.3
WARNING: You are using pip version 22.0.4; however, version 22.1.2 is available.
You should consider upgrading via the '/Users/minman/minman_study/asyncio_test/asyncio_test/bin/python3.9 -m pip install --upgrade pip' command.

$ pip install beautifulsoup4
Collecting beautifulsoup4
  Downloading beautifulsoup4-4.11.1-py3-none-any.whl (128 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 128.2/128.2 KB 1.4 MB/s eta 0:00:00
Collecting soupsieve>1.2
  Downloading soupsieve-2.3.2.post1-py3-none-any.whl (37 kB)
Installing collected packages: soupsieve, beautifulsoup4
Successfully installed beautifulsoup4-4.11.1 soupsieve-2.3.2.post1
WARNING: You are using pip version 22.0.4; however, version 22.1.2 is available.
You should consider upgrading via the '/Users/minman/minman_study/asyncio_test/asyncio_test/bin/python3.9 -m pip install --upgrade pip' command.

$ pip list
Package        Version
-------------- -----------
asyncio        3.4.3
beautifulsoup4 4.11.1
pip            22.0.4
setuptools     60.10.0
soupsieve      2.3.2.post1
WARNING: You are using pip version 22.0.4; however, version 22.1.2 is available.
You should consider upgrading via the '/Users/minman/minman_study/asyncio_test/asyncio_test/bin/python3.9 -m pip install --upgrade pip' command.

AsyncIO를 이용해서 여러 웹사이트를 동시에 스크래핑하는 프로그램을 구현할 것이다.

AsyncIO는 비동기 input/output 작업을 코루틴을 쉽게할수록 지원해주는 패키지임

None 블러킹 비동기 처리를 지원해준다.

Blocking I/O : 호출된 함수가 자신의 작업이 완료될때까지 제어권을 가지고 있는것을 말한다. 그래서 타 함수는 대기해야함.

None Blocking I/O : 호출된 함수(서브 루틴)가 리턴후 호출한 함수 (메인 루틴)에 제어권 전달하는 것을 말한다. 그래서 타 함수는 일을 계속해서 할 수 있다.

참고로 우리가 실습때 사용할 request 라이브러리는 블러킹 I/O이다. (구글링 하면 나옴)

블러킹 함수는 AsyncIO를 사용하는 것보다 멀티쓰레딩을 사용하는게 더 낫다.

즉 내가 사용할 함수도 제네테레이터 같이 비동기 함수로 구현되어 있어야지 AsyncIO를 사용하는게 효과적이다.

멀티쓰레드를 사용할 경우 단점 : 디버깅에 어려움, 레이스 컨디션(경쟁상태), 데드락을 고려하고 코딩해야 한다.

코루틴의 장점 : 하나의 단일쓰레드에서 실행되는 루틴만 실행해도 락관리가 필요가 없다.

코루틴의 단점 :코루틴을 사용하려면 사용할 함수가 비동기로 구현이 되어 있어야 하거나 또는 직접 비동기로 구현해야 한다.

아래와 같이 test.py 를 작성해보자.

import asyncio
import timeit
from urllib.request import urlopen
from concurrent.futures import ThreadPoolExecutor
import threading
from bs4 import BeautifulSoup
# urlopen 함수는 블럭 함수임 따라서 asyncio로 urlopen 함수를 여러개 동시에 실행해도 블러킹 되버린다. 즉 asyncio가 비효과적이다.
# 결론적으로 그래서 https://pymotw.com/3/asyncio/executors.html 와 같은 패턴을 사용하면 블러킹 함수를 사용해도 asyncio를 효과적으로 사용할 수 있다.

# 실행시작 시간
start = timeit.default_timer()

urls = ['https://naver.com', 'https://google.com','https://tstory.com','https://minman2115.github.io','https://pymotw.com/3/asyncio/executors.html']

async def fetch(url,executor):
  # 쓰레드 시작상태 출력
  print('Thread Name : ', threading.current_thread().getName(),'Start', url)

  # 실행
  response = await loop.run_in_executor(executor, urlopen, url)
  soup = BeautifulSoup(response.read(), 'html.parser')

  # 전체 페이지 소스 확인
  #print(soup.prettify())

  # 웹사이트의 html 타이틀만 가져오기
  result_data = soup.title

  # 쓰레드 종료상태 출력
  print('Thread Name : ', threading.current_thread().getName(),'Done', url)

  # 결과 반환
  return result_data

# 함수선언 할때 async를 붙여주는 거는 이 함수가 비동기 함수라는 것을 선언하는 것임.
async def main():
  # 쓰레드풀 생성
  executor = ThreadPoolExecutor(max_workers=10)

  # future 객체 모아서 gather해서 실행
  futures = [asyncio.ensure_future(fetch(url,executor)) for url in urls]

  # 결과 취합
  # futures 객체가 리스트 타입이기 때문 *futures 로 언패킹을 한것임
  result = await asyncio.gather(*futures)

  print("Result : ", result)


if __name__ == '__main__':
  # 루프 초기화
  loop = asyncio.get_event_loop()
  # 메인함수가 끝날때까지 루프를 계속 돌린다.
  loop.run_until_complete(main())
  # 수행시간 계산
  duration = timeit.default_timer() - start
  # 프로그램 러닝타임 출력
  print('Running time : ', duration)
  • test.py를 실행하면 실행결과는 아래와 같음.
$ python3 test.py
Thread Name :  MainThread Start https://naver.com
Thread Name :  MainThread Start https://google.com
Thread Name :  MainThread Start https://tstory.com
Thread Name :  MainThread Start https://minman2115.github.io
Thread Name :  MainThread Start https://pymotw.com/3/asyncio/executors.html
Thread Name :  MainThread Done https://minman2115.github.io
Thread Name :  MainThread Done https://pymotw.com/3/asyncio/executors.html
Thread Name :  MainThread Done https://naver.com
Thread Name :  MainThread Done https://google.com
Thread Name :  MainThread Done https://tstory.com
Result :  [<title>NAVER</title>, <title>Google</title>, None, <title>Whiplash – Data Science Study Notes</title>, <title>Combining Coroutines with Threads and Processes — PyMOTW 3</title>]
Running time :  4.363094542