Files
web-page-backend/stock-lab/app/scraper.py

139 lines
5.2 KiB
Python

import requests
from bs4 import BeautifulSoup
from typing import List, Dict, Any
import time
# 네이버 파이낸스 주요 뉴스
NAVER_FINANCE_NEWS_URL = "https://finance.naver.com/news/mainnews.naver"
def fetch_market_news() -> List[Dict[str, str]]:
"""
네이버 금융 '주요 뉴스' 크롤링
반환: [{"title": "...", "link": "...", "summary": "...", "date": "..."}, ...]
"""
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"
}
resp = requests.get(NAVER_FINANCE_NEWS_URL, headers=headers, timeout=10)
resp.raise_for_status()
soup = BeautifulSoup(resp.content, "html.parser", from_encoding="cp949")
# 주요 뉴스 리스트 추출
# 구조: div.mainNewsList > ul > li
articles = []
news_list = soup.select(".mainNewsList ul li")
for li in news_list:
# 썸네일 있을 수도 있고 없을 수도 있음
dl = li.select_one("dl")
if not dl:
continue
# 제목 (dd.articleSubject > a)
subject_tag = dl.select_one(".articleSubject a")
if not subject_tag:
continue
title = subject_tag.get_text(strip=True)
link = "https://finance.naver.com" + subject_tag["href"]
# 요약 (dd.articleSummary)
summary_tag = dl.select_one(".articleSummary")
summary = ""
press = ""
date = ""
if summary_tag:
# 불필요한 태그 제거
for child in summary_tag.select(".press, .wdate"):
if "press" in child.get("class", []):
press = child.get_text(strip=True)
if "wdate" in child.get("class", []):
date = child.get_text(strip=True)
child.extract()
summary = summary_tag.get_text(strip=True)
articles.append({
"title": title,
"link": link,
"summary": summary,
"press": press,
"date": date,
"crawled_at": time.strftime("%Y-%m-%d %H:%M:%S")
})
return articles
except Exception as e:
print(f"[StockLab] Scraping failed: {e}")
return []
def fetch_major_indices() -> Dict[str, Any]:
"""
KOSPI, KOSDAQ, KOSPI200 등 주요 지표 (네이버 금융 홈)
"""
url = "https://finance.naver.com/"
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"
}
resp = requests.get(url, headers=headers, timeout=5)
resp.raise_for_status()
soup = BeautifulSoup(resp.content, "html.parser", from_encoding="cp949")
indices = []
# 네이버 금융 홈 상단 'section_stock_market' 내부
# top_kospi, top_kosdaq, top_kpsi200
targets = [
{"key": "KOSPI", "selector": ".kospi_area"},
{"key": "KOSDAQ", "selector": ".kosdaq_area"},
{"key": "KOSPI200", "selector": ".kospi200_area"},
]
for t in targets:
area = soup.select_one(t["selector"])
if not area:
continue
# 현재가
num_tag = area.select_one(".num")
value = num_tag.get_text(strip=True) if num_tag else ""
# 등락 (num2) -> 화살표, 부호 확인 필요
# num2 (상승), num3 (하락) 클래스가 유동적일 수 있음
# .num2 (수치), .num3 (퍼센트)
# 보통 .nk (수치), .per (퍼센트) 로 나뉨
change_val_tag = area.select_one(".num2")
change_pct_tag = area.select_one(".num3")
change_val = change_val_tag.get_text(strip=True) if change_val_tag else ""
change_pct = change_pct_tag.get_text(strip=True) if change_pct_tag else ""
# 상승/하락 부호 처리 (화살표 텍스트나 클래스 보고 판단해야 함)
# 단순 텍스트로는 '상승 10.5' 처럼 들어있을 수 있음
# 여기서는 단순 텍스트값 그대로 리턴
# 방향(상승/하락) 클래스 확인
direction = ""
if area.select_one(".bu_p"): direction = "red" # 상승
elif area.select_one(".bu_m"): direction = "blue" # 하락
indices.append({
"name": t["key"],
"value": value,
"change_value": change_val,
"change_percent": change_pct,
"direction": direction
})
return {"indices": indices, "crawled_at": time.strftime("%Y-%m-%d %H:%M:%S")}
except Exception as e:
print(f"[StockLab] Indices scraping failed: {e}")
return {"indices": [], "error": str(e)}