Repository navigation
Adding a News Source
โMinjung edited this page Sep 26, 2026
·
1 revision
์๋ก์ด ๋ด์ค ์์ค๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ ์๋ดํฉ๋๋ค.
HackDigest์ ๋ด์ค ์์ค๋ ํ ํฝ ๊ธฐ๋ฐ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์ ํ ํฝ์ ์ถ๊ฐํ๊ฑฐ๋, ๊ธฐ์กด ํ ํฝ์ ์์ค๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
src/models.py์ ์ ํ ํฝ์ ์ถ๊ฐํฉ๋๋ค:
class Topic(str, Enum):
TECH = "tech"
STOCKS = "stocks"
REALESTATE = "realestate"
CRYPTO = "crypto" # ์ ํ ํฝsrc/fetchers/ ๋๋ ํ ๋ฆฌ์ ์ fetcher ํ์ผ์ ์์ฑํฉ๋๋ค:
# src/fetchers/crypto.py
"""Fetch cryptocurrency news from RSS sources."""
import logging
import re
import feedparser
from src.models import Article, Language, Topic
logger = logging.getLogger(__name__)
# ์์ด ์์ค
EN_FEEDS = [
("CoinDesk", "https://www.coindesk.com/arc/outboundfeeds/rss/"),
]
# ํ๊ตญ์ด ์์ค
KR_FEEDS = [
("Google ์ฝ์ธ", "https://news.google.com/rss/search?q=๋นํธ์ฝ์ธ+์ฝ์ธ+๊ฐ์ํํ&hl=ko&gl=KR&ceid=KR:ko"),
]
# ํค์๋ ํํฐ
KEYWORDS = re.compile(
r"\b(bitcoin|crypto|ethereum|blockchain|defi|nft|token|altcoin)\b",
re.IGNORECASE,
)
def fetch(language: Language = Language.EN) -> list[Article]:
"""Fetch crypto news articles."""
feeds = KR_FEEDS if language == Language.KO else EN_FEEDS
articles = []
for source_name, url in feeds:
feed = feedparser.parse(url)
for entry in feed.entries[:20]:
title = entry.get("title", "")
link = entry.get("link", "")
if not title or not link:
continue
# ํค์๋ ํํฐ๋ง (์์ด ์์ค๋ง)
if language == Language.EN and not KEYWORDS.search(title):
continue
articles.append(Article(
title=title,
url=link,
hn_url="",
score=0,
comment_count=0,
topic=Topic.CRYPTO,
))
# ์ค๋ณต ์ ๊ฑฐ (URL ๊ธฐ์ค)
seen = set()
unique = []
for a in articles:
if a.url not in seen:
seen.add(a.url)
unique.append(a)
return unique[:5] # ์์ 5๊ฐsrc/fetchers/__init__.py์ ์ fetcher๋ฅผ ๋ฑ๋กํฉ๋๋ค:
from src.fetchers.crypto import fetch as fetch_crypto
FETCHER_MAP = {
Topic.TECH: fetch_tech,
Topic.STOCKS: fetch_stocks,
Topic.REALESTATE: fetch_realestate,
Topic.CRYPTO: fetch_crypto, # ์ ์ถ๊ฐ
}
# ์ธ์ด ์์กด์ ํ ํฝ ๋ชฉ๋ก์ ์ถ๊ฐ (์ธ์ด๋ณ ๋ค๋ฅธ ์์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ)
LANGUAGE_DEPENDENT_TOPICS = {Topic.STOCKS, Topic.REALESTATE, Topic.CRYPTO}docs/index.html๊ณผ Cloudflare Worker์ ALLOWED_TOPICS์ ์ ํ ํฝ์ ์ถ๊ฐํฉ๋๋ค:
// worker/src/index.js
const ALLOWED_TOPICS = ['tech', 'stocks', 'realestate', 'crypto'];subscribers.yml์ ํ ํฝ ์ฒดํฌ๋ฐ์ค์ ์ ํญ๋ชฉ์ ์ถ๊ฐํฉ๋๋ค.
๊ธฐ์กด fetcher์ RSS ํผ๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๊ฐ๋จํฉ๋๋ค.
์๋ฅผ ๋ค์ด, ํ๊ตญ์ด ์ฃผ์ ๋ด์ค์ ๋ค์ด๋ฒ ๊ธ์ต RSS๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด:
# src/fetchers/stocks.py
KR_STOCK_FEEDS = [
("Google ์ฃผ์+์ฝ์คํผ", "https://news.google.com/rss/search?q=..."),
("Google ์ฆ์+์ฃผ๊ฐ", "https://news.google.com/rss/search?q=..."),
("๋ค์ด๋ฒ ๊ธ์ต", "https://rss.news.naver.com/..."), # ์ ์ถ๊ฐ
]def fetch(language: Language = Language.EN) -> list[Article]:
"""
Args:
language: ๊ตฌ๋
์ ์ธ์ด (Language.KO ๋๋ Language.EN)
Returns:
list[Article]: ์ต๋ 5๊ฐ์ ๊ธฐ์ฌ ๋ชฉ๋ก
"""@dataclass
class Article:
title: str # ๊ธฐ์ฌ ์ ๋ชฉ
url: str # ์๋ณธ ๊ธฐ์ฌ URL
hn_url: str # Hacker News URL (HN์ด ์๋ ๊ฒฝ์ฐ ๋น ๋ฌธ์์ด)
score: int # ์ ์/์ธ๊ธฐ๋ (์์ผ๋ฉด 0)
comment_count: int # ๋๊ธ ์ (์์ผ๋ฉด 0)
topic: Topic # ํ ํฝ
summary: str = "" # ์์ฝ (summarizer๊ฐ ์ฑ์)- Rate Limiting โ API ํธ์ถ์ ์ ์ ํ ๋๋ ์ด/์ฌ์๋ ๋ก์ง ์ถ๊ฐ
- ํค์๋ ํํฐ๋ง โ ๊ด๋ จ ์๋ ๊ธฐ์ฌ ํํฐ๋ง์ผ๋ก ํ์ง ์ ์ง
- ์๋ฌ ํธ๋ค๋ง โ ์์ค ๋ถ๊ฐ ์ ๋น ๋ฆฌ์คํธ ๋ฐํ (์ ์ฒด ํ์ดํ๋ผ์ธ ์ค๋จ ๋ฐฉ์ง)
- ์ค๋ณต ์ ๊ฑฐ โ URL ๊ธฐ์ค์ผ๋ก ์ค๋ณต ๊ธฐ์ฌ ์ ๊ฑฐ
- ๊ฒฐ๊ณผ ์ ํ โ ์ต๋ 5~10๊ฐ๋ก ์ ํ (์ด๋ฉ์ผ ๊ธธ์ด ๊ด๋ฆฌ)
-
๋ก๊น
โ
logger.info/warning/error๋ก ์ ์ ํ ๋ก๊น
# ํน์ fetcher๋ง ํ
์คํธ
python -c "
from src.fetchers.crypto import fetch
from src.models import Language
articles = fetch(Language.KO)
for a in articles:
print(f'{a.title} โ {a.url}')
"