본문 바로가기
Web Application

웹 스크래핑을 통한 비동기 프로그래밍 필요성의 이해

by quantWhale 2024. 4. 5.
반응형

안녕하세요. 인천고래입니다.

이번 글에서는 비동기와 동기식 함수를 만들고 호출하는 방법에 대해 알아보는 시간을 가져보도록 하겠습니다.

 

프로그래밍을 하다 보면 간단한 데이터를 가져오거나 연산을 하는 경우도 발생이 되고 대용량 데이터를 DB에서 가져오기도 합니다. 그리고 웹 상에서 필요한 데이터를 추출하기도 하죠.

컴퓨터가 고사양이 되다보니 데이터를 연산하는 속도가 빨라지다 보니 결과도 빠르게 나오게 되는데 외부에서 필요한 데이터를 추출하는 과정을 거치는 작업은 네트워크를 거치다 보니 당연히 결과를 얻는데 시간 지연이 발생되게 됩니다.

 

위의 과정을 아래와 같이 기능별로 항목을 구분하여 함수화 하고 동기식 처리 형태로 코드를 구현해 보도록 하겠습니다.

 

기능 항목

  • 연산작업
  • 웹 스크래핑

 

동기식 프로그래밍

파이썬 코드를 기준으로 동기식 프로그래밍에 대해 알아보도록 하겠습니다.

동기식 프로그래밍이란 한 행씩 명령을 수행하고 해당 명령이 수행 완료되기 전까지는 그 다음행으로 명령이 안 넘어가는 것을 의미합니다.

 

아래의 코드를 보면 웹 사이트에 접속해서 타이틀 제목을 가져오는 get_title_sync() 함수와

데이터를 연산하는 함수로 perform_calculation_sync() 함수가 존재합니다.

 

위에서 정의한 기능별 항목 2가지를 함수화한 것입니다.

import requests
from bs4 import BeautifulSoup
import time

def get_title_sync(url):
    start_time = time.time()
    print(f"{url} 스크래핑 시작...")
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    title = soup.title.string
    end_time = time.time()
    print(f"{url} 스크래핑 끝... {end_time - start_time}")

    return title


def perform_calculation_sync():
    start_time = time.time()
    print("for문으로 100만까지 더하기 시작 ...")
    result = sum([i for i in range(1000000)])
    end_time = time.time()
    print(f"더하기 끝: {result}, {end_time - start_time}")


def main_sync():
    start_time = time.time()
    urls = ['https://naver.com', 'https://daum.net', 'https://bing.com']

    # 웹 스크래핑 작업 실행
    titles = [get_title_sync(url) for url in urls]
    print(f"Scraped Titles: {titles}")

    # 계산 작업 실행
    perform_calculation_sync()
    end_time = time.time()
    print(f"프로그램 종료: {end_time - start_time}")


main_sync()

 

 

그리고 main_sync()함수에서 아래와 같이  get_title_sync() 함수를 호출하는데 urls의 길이만큼 즉, 3번을 호출하게 하였습니다.

    urls = ['https://naver.com', 'https://daum.net', 'https://bing.com']

    # 웹 스크래핑 작업 실행
    titles = [get_title_sync(url) for url in urls]
    print(f"Scraped Titles: {titles}")

 

 

웹 스크래핑 호출이 끝나면 아래와 같이 계산 작업을 실행하도록 호출을 해 줍니다.

    # 계산 작업 실행
    perform_calculation_sync()

 

위의 코드들 중에 time 관련 코드는 해당 함수의 기능을 시작할 때의 시간과 끝날 때의 시간을 기록하기 위해 로그용으로 추가해 놨습니다.

 

위의 코드를 복사하시는 대신에 아래의 첨부 파일을 다운로드하시면 확인할 수 있습니다.

동기방식_동기2.py
0.00MB

 

 

해당 코드를 실행하면 아래와 같은 결과가 나옵니다.

동기식 결과

 

 

  1. 웹 스크래핑 함수 호출
  2. naver.com 스크래핑을 시작하고 끝이 나야 urls 리스트에 있는 항목 중 다음 순서의 url(daum.net)이 실행
  3. 웹 스크래핑 함수 종료, 리턴 결과 출력
  4. 연산 함수 수행, 프로그램 종료.

이런 식으로 코드를 하나씩 순차적으로 실행하게 되는 것을 동기식 프로그래밍이라고 합니다.

파이썬에서는 별도의 요구사항 없이 호출하게 되면 무조건 동기식 프로그래밍 형태의 코드가 되는 것입니다.

 

여기에서 의문이 생깁니다.

get_title_sync() 함수에서 오랜 시간이 걸리는 작업이 존재한다면???

예를 들어 해당 함수에 주식 데이터를 가져오기 위해 종목별로 웹 스크래핑을 하는 코드가 있다고 가정을 할 경우

함수의 기능을 완료함에 있어서 짧게는 1시간 길게는 3시간 이상 소요될 수 있습니다.

 

이런 경우라면 perform_calculation_sync() 함수는 get_title_sync()가 끝나는 시간(길게는 3시간 이상)이 지나야 실행이 됩니다.

 

웹 스크래핑이 아니라 데이터베이스에서 쿼리를 이용하여 데이터를 가져오는 경우도 마찬가지가 될 것이고

키움 OpenAPI에서 주가 데이터를 받아오거나 할 때에도 데이터를 안 받았으니 다른 작업을 못하게 됩니다.

 

멀티태스킹이 안 되는 것이죠.

그래서 비동기화 작업이 필요하게 됩니다.

 

그러면 데이터를 로드하고 전달하는 과정에서 발생하는 지연 시간 동안 다른 작업을 수행할 수 없는 문제를 해결하기 위해 아래와 같은 비동기 방법들이 존재합니다.

  • 콜백 함수 사용: 비동기 처리가 완료될 때 호출될 콜백 함수를 정의하여, 데이터 로딩이 끝나는 즉시 특정 작업을 시작할 수 있습니다. 이 방법은 비동기 작업이 완료된 후에 필요한 다음 단계를 즉시 실행할 수 있도록 합니다.

  • 프로미스(Promise)와 async/await 사용: 자바스크립트의 프로미스(Promise)와 async/await 문법을 사용하여 비동기 작업을 더욱 쉽게 관리할 수 있습니다. 프로미스를 사용하면 비동기 작업이 성공적으로 완료되었을 때와 오류가 발생했을 때의 처리를 간결하게 작성할 수 있으며, async/await를 사용하면 비동기 코드를 동기 코드처럼 읽고 작성할 수 있습니다.

  • 멀티 스레딩 또는 멀티 프로세싱: 파이썬에서는 threading 또는 multiprocessing 모듈을 사용하여 병렬 처리를 구현할 수 있습니다. 이를 통해 주요 작업을 수행하는 동안 다른 스레드나 프로세스에서 데이터 로딩과 같은 비동기 작업을 수행할 수 있어, 애플리케이션의 전반적인 반응성과 성능을 향상시킬 수 있습니다.

  • 이벤트 루프 활용: 특히 Node.js 같은 환경에서는 이벤트 루프를 사용하여 비동기 작업을 관리합니다. 이벤트 루프를 통해 비동기 작업의 완료를 기다리면서도, 다른 작업을 계속해서 처리할 수 있습니다.

  • 웹워커(Web Workers) 사용: 브라우저 환경에서는 웹워커를 사용하여 병렬 처리를 할 수 있습니다. 웹워커는 메인 스레드와는 별개의 백그라운드 스레드에서 코드를 실행할 수 있게 해 주어, 메인 스레드의 UI 업데이트나 다른 작업을 방해받지 않고 비동기 작업을 수행할 수 있습니다.

 

위의 방법들은 비동기 작업 중에 발생할 수 있는 대기 시간을 최소화하고, 애플리케이션의 전반적인 효율성과 반응성을 향상하는 데 도움이 될 수 있을 것이라 생각되고 각 방법의 구현 방식과 장단점을 고려하여 가장 적합한 방법을 선택하는 것이 중요할 것으로 보입니다.

 

비동기식 프로그래밍

그럼 동기식 코드를 비동기식 코드로 전환을 해 보도록 하겠습니다.

아래의 코드를 참고해 주세요.

import asyncio
import aiohttp
from bs4 import BeautifulSoup
import time


async def get_title_async(url):
    start_time = time.time()
    print(f"{url} 스크래핑 시작...")
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            soup = BeautifulSoup(await response.text(), 'html.parser')
            end_time = time.time()
            print(f"{url} 스크래핑 끝... {end_time - start_time}")
            return soup.title.string


async def perform_calculation():
    start_time = time.time()
    # 여기에서는 간단한 예로 숫자 계산을 수행합니다.
    print("for문으로 100만까지 더하기 시작 ...")
    result = sum([i for i in range(1000000)])
    end_time = time.time()
    print(f"더하기 끝: {result}, {end_time - start_time}")


async def main():
    start_time = time.time()
    urls = ['https://naver.com', 'https://daum.net', 'https://bing.com']

    # 웹 스크래핑과 계산 작업을 동시에 실행
    # gather()를 사용하여 모든 작업을 동시에 실행하고 결과를 저장합니다.
    results = await asyncio.gather(
        *(get_title_async(url) for url in urls),
        perform_calculation()
    )

    # 스크래핑한 제목들을 출력합니다. 계산 결과는 마지막에 포함되어 있으므로 제외합니다.
    titles = results[:-1]  # 계산 작업의 결과를 제외한 나머지가 제목 리스트입니다.
    print(f"Scraped Titles: {titles}")
    end_time = time.time()
    print(f"프로그램 종료: {end_time - start_time}")

asyncio.run(main())

 

해당 코드는 아래의 첨부파일로 올려놓겠습니다.

동기방식_비동기2.py
0.00MB

 

 

각각의 코드를 확인하기 전에 바로 실행결과부터 확인을 해 보도록 하겠습니다.

비동기식 결과

 

어떤가요? 호출은 정상적으로 웹스크래핑 함수를 호출하였고 그 뒤에 연산 함수까지 호출이 되었으나 웹 스크래핑 결과를 기다리지 않고 호출만 된 상태이며 각각의 웹 스크래핑 결과는 리턴되는 시점에 맞춰서 각자 알아서 끝나게 됩니다.

 

한 사람 한 사람에게 가서 일 시켜서 결과를 확인한 후 다른 사람에게 일을 시키러 가는 게 아니라

한 사람 한 사람에게 가서 일은 시키지만 결과는 각자 보고하게 해 두는 결과라고 볼 수 있겠네요.

 

아래의 비교 결과를 보면 좀 더 이해하기 쉬우실 거예요.

더군다나 프로그램 종료하는 시간도 좀 더 줄었네요.

파이썬 함수의 동기 비동기 결과

 

오늘은 동기화 코드가 동작되는 방식을 통해 비동기화 코드가 왜 필요한지에 대해 알아보았으며

그럼 각각의 비동기화를 만드는 코드에 대해서는 다음 글에서 알아보도록 하겠습니다.

 

 

오늘도 제 사이트를 찾아와 주신 모든 분들께 감사의 말씀을 드립니다.

반응형
-

댓글