프로그래밍 언어/Python

[pyqt] 웹크롤링하기

잉읭응 2025. 2. 21. 14:33
반응형

 

1. BeautifulSoup

 

BeautifulSoup 라이브러리에서는 웹사이트에 데이터를 읽어와서 파싱할 수 있는 기능을 제공하고 있다.

import requests
from bs4 import BeautifulSoup

def get_fear_and_greed():
    url = "https://edition.cnn.com/markets/fear-and-greed"
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # HTML 구조에 맞는 특정 요소를 찾아 Fear and Greed Index 값을 추출
    index = soup.find("div", class_="fear-and-greed-index").text.strip()
    return index

if __name__ == "__main__":
    index_value = get_fear_and_greed()
    print(f"CNN Fear and Greed Index: {index_value}")

 

 

하지만 아래 코드를 수행해보면 정상적으로 데이터를 파싱하지 못하는 것 처럼 보일 것이다.

(특히 직접 원하는 영역의 div의 class이름을 검색해보면 결과가 포함이 되지 않을 것이다.)

 

2. Selenium

그 이유는 해당 값이 JavaScript로 동적으로 로딩되기 때문이다. requests와 BeautifulSoup은 HTML 소스를 바로 가져오기 때문에 JavaScript로 생성된 콘텐츠는 포함되지 않는다.

 

이 문제를 해결하려면, Selenium과 같은 브라우저 자동화 도구를 사용하여 동적으로 로딩되는 콘텐츠를 포함한 페이지를 가져올 수 있다. Selenium을 사용하면 JavaScript가 로드된 후 페이지에서 데이터를 추출할 수 있다.

 

from selenium import webdriver
from selenium.webdriver.common.by import By

url = "https://edition.cnn.com/markets/fear-and-greed"

# 브라우저를 실행합니다.
driver = webdriver.Chrome()

driver.get(url)

# 원하는 div를 찾습니다.
index_value = driver.find_element(By.CLASS_NAME, "market-fng-gauge__dial-number").text
print(f"CNN Fear and Greed Index: {index_value}")

# 브라우저 종료
driver.quit()

 

 

이때 매 실행마다 url에 해당하는 사이트가 열리고 켜지면서 실행시간이 아주 오래 걸리는 것을 확인할 수 있다.

 

3. 시간 단축

실제로 데이터를 동적로딩해야하기 때문에 이는 어쩔 수 없는 방법이다. 하지만 시간 단축을 위해서 몇가지 추가할 수 있는 옵션이 있다.

 

Seleniumheadless 모드를 사용해 브라우저 창을 열지 않고 백그라운드에서 동작하도록 할 수 있다. 이렇게 하면 화면에 표시되는 부분을 렌더링하지 않아서 속도가 빨라진다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options

url = "https://edition.cnn.com/markets/fear-and-greed"

# Chrome 옵션 설정 (headless 모드)
chrome_options = Options()
chrome_options.add_argument("--headless")  # 화면을 표시하지 않음

# 브라우저를 headless 모드로 실행
driver = webdriver.Chrome(options=chrome_options)

driver.get(url)

# 원하는 div를 찾습니다.
index_value = driver.find_element(By.CLASS_NAME, "market-fng-gauge__dial-number").text
print(f"CNN Fear and Greed Index: {index_value}")

# 브라우저 종료
driver.quit()

 

 

4. api를 활용하자

그렇지만 실제로 동적으로 데이터를 받아와야 하는 입장이기 때문에 속도저하는 불가피하다.

이를 개선하기 위해서 다양한 api를 활용해야한다.

 

만약에 미국 주식 지수와 관련한 데이터라면 yfinance 라이브러리를 통해서 데이터를 제공받을 수 있다.

pip install yfinance

 

기본 예시 코드

import yfinance as yf

# VIX 지수 티커
vix = yf.Ticker("^VIX")

# 최근 종가 데이터 가져오기
vix_data = vix.history(period="1d")
print("VIX 지수:", vix_data["Close"].iloc[-1])

 

 

웹크롤링과 라이브러리를 함께 사용한 예시 코드

import sys
import requests
import yfinance as yf
import matplotlib.pyplot as plt
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel
from PyQt6.QtCore import Qt
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas

class DataFetcher:
    """데이터를 가져오는 클래스 (웹 크롤링 또는 API)"""

    @staticmethod
    def fetch_fear_greed_index():
        """CNN Fear & Greed Index 크롤링"""
        chrome_options = Options()
        chrome_options.add_argument("--headless")
        chrome_options.add_argument("--ignore-certificate-errors")

        driver = webdriver.Chrome(options=chrome_options)
        driver.get("https://edition.cnn.com/markets/fear-and-greed")

        index_value = driver.find_element(By.CLASS_NAME, "market-fng-gauge__dial-number").text
        driver.quit()

        return f"CNN Fear & Greed Index: {index_value}"

    @staticmethod
    def fetch_vix_data():
        """VIX 지수 데이터 가져오기 (지난 1개월)"""
        vix = yf.Ticker("^VIX")
        vix_data = vix.history(period="1mo")
        return vix_data

class MarketIndexApp(QWidget):
    """GUI를 관리하는 클래스"""

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Market Index Dashboard")
        self.resize(500, 400)

        self.layout = QVBoxLayout()
        
        self.fear_greed_label = QLabel("Loading Fear & Greed Index...", self)
        self.fear_greed_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.layout.addWidget(self.fear_greed_label)

        self.vix_label = QLabel("Loading VIX Index...", self)
        self.vix_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.layout.addWidget(self.vix_label)

        # 그래프 추가
        self.figure, self.ax = plt.subplots(figsize=(5, 3))
        self.canvas = FigureCanvas(self.figure)
        self.layout.addWidget(self.canvas)

        self.setLayout(self.layout)

        self.update_data()

    def update_data(self):
        """데이터를 가져와 UI에 표시"""
        self.fear_greed_label.setText(DataFetcher.fetch_fear_greed_index())

        vix_data = DataFetcher.fetch_vix_data()
        self.vix_label.setText(f"VIX 지수 (최신값): {vix_data['Close'].iloc[-1]:.2f}")

        # 그래프 업데이트
        self.ax.clear()
        self.ax.plot(vix_data.index, vix_data["Close"], marker='o', linestyle='-', color='b', label="VIX Close")
        self.ax.set_title("VIX Index Trend (Last 1 Month)")
        self.ax.set_xlabel("Date")
        self.ax.set_ylabel("VIX Value")
        self.ax.legend()
        self.ax.grid(True)

        self.canvas.draw()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MarketIndexApp()
    window.show()
    sys.exit(app.exec())
반응형