pip install 할 게 하나도 없다.
또 하나의 핵심은 "내 LLM을 가져와서 쓴다(BYO-LLM)" 구조다. 본인의 Claude 구독($0)이나 아무 OpenAI 호환 API 키를 llm.config.json 한 줄로 갈아끼운다. 데이터는 전부 내 컴퓨터에만 남는다.
"뉴스는 넘치는데 정작 산업이 어디로 가는지는 안 보인다" 를 해결하는 도구
Investment News는 이름 그대로 "투자 뉴스" 도구지만, 흔한 뉴스 앱과는 목적이 다르다. 보통의 뉴스 앱은 피드(feed)를 준다 — 기사를 끝없이 쌓아 보여주고, 읽는 건 사람 몫이다. 이 도구의 핵심은 반대다. "읽고 요약하는 일을 AI에게 시킨다."
구체적으로는 이렇게 동작한다. ① 반도체·AI·로봇·전기차·에너지 등 12개 산업 섹터 각각에 대해, 미리 골라둔 108개의 권위 있는 뉴스 소스(RSS)에서 최근 기사를 긁어온다. ② 섹터별로 사용자 본인의 LLM을 불러 "오늘의 요점 3~5개"를 뽑고, 영어 제목을 중국어로 번역한다. ③ 결과를 브라우저 대시보드 한 장(http://localhost:8793)에 올린다. 왼쪽에서 섹터를 고르면, 맨 위에 AI 요점 카드가 뜨고 그 아래 뉴스가 한 줄씩 정렬된다.
당신이 12개 산업(반도체·AI·로봇…)에 투자한다고 하자. 각 산업마다 세계 곳곳의 매체 수십 개가 매일 기사를 쏟아낸다. 혼자 다 읽는 건 불가능하다.
이 도구는 산업마다 "비서"를 한 명씩 붙여준다. 비서(=LLM)가 그 산업의 매체 10~20곳을 대신 읽고, 중복을 걸러, "오늘 이 산업의 핵심은 이 3~5가지"라고 요약해 준다. 원문이 궁금하면 요점 옆 화살표(↗)를 눌러 바로 가면 된다. 비서 월급은? 본인 Claude 구독을 쓰면 $0.
기술 트렌드 상으로 딱 지금 먹히는 3가지 코드가 겹쳐 있다
이 저장소가 트렌딩에 오른 이유는 단순히 "투자 뉴스 모아줌" 이 아니다. 2026년 개발 씬에서 동시에 인기 있는 세 가지 흐름이 한 프로젝트에 깔끔하게 겹쳐 있기 때문이다.
요즘 트렌딩 상위권은 Claude Code / Codex 구독을 API처럼 재활용하는 도구가 점령하고 있다. 이 프로젝트도 같은 코드다. 이미 월정액을 내고 있는 Claude 구독을, 뉴스 요약이라는 "배치 작업"에 재사용한다. 별도 API 키도, 토큰 과금도 없다(claude-cli 모드, $0). API를 쓰고 싶으면 DeepSeek·OpenAI 등 아무 OpenAI 호환 엔드포인트로 한 줄만 바꾸면 된다.
투자 관심사는 민감한 정보다. "내가 반도체·방산에 관심 있다"는 걸 외부 서버에 남기고 싶지 않다. 이 도구는 데이터베이스도, 호스팅도, 계정도 없다. 웹서버는 127.0.0.1(내 컴퓨터)에만 바인딩되고, 수집·렌더링이 전부 로컬에서 끝난다. 클라우드에 남는 게 없다.
파이썬 프로젝트를 받아 pip install 하다 버전 충돌로 좌절해 본 적 있다면 이 설계가 왜 인기인지 안다. 이 저장소는 third-party 패키지가 하나도 없다. RSS 파싱은 xml.etree, HTTP 요청은 urllib, 웹서버는 http.server — 전부 파이썬에 기본 내장된 표준 라이브러리다. git clone 후 python3 server.py 만 치면 끝. 이게 진입장벽을 극단적으로 낮춘다.
일반 뉴스 앱(Feedly 등): 기사를 쌓아만 주고, 요약은 없거나 클라우드 유료. 내 관심사가 그들 서버에 남는다.
RSSHub + n8n 같은 자동화 스택: 강력하지만 설치·운영이 무겁고 서버가 필요하다.
Investment News: 파일 몇 개 + 파이썬 한 방. AI 요약이 기본 내장, 데이터는 내 노트북에만. "가볍고 · 로컬이고 · 요약해주는" 교집합을 노렸다.
스타 수가 폭발한 대형 프로젝트가 아니라, 개인 개발자(Simon Lin)가 만든 단정한 소품이다. 소스는 RSS만 지원하고(웹 크롤링·API 소스는 없음), 요약 품질은 전적으로 당신이 붙인 LLM에 달려 있다. "투자 조언"이 아니라 "산업 동향 브리핑"이라고 저자가 반복해 못박는다. 배울 게 많은 깨끗한 레퍼런스 코드로 보는 게 정확하다.
외부 패키지가 없다는 건, 파이썬 표준 라이브러리를 제대로 쓴다는 뜻이다
"third-party 0개"는 마케팅 문구가 아니라 실제로 requirements.txt가 없다. 그 대신 파이썬에 기본 내장된 표준 모듈들을 조합한다. 초보자가 이 저장소를 좋은 교재로 삼을 수 있는 이유가 여기 있다 — 배우는 게 전부 "순정 파이썬"이라 어디서든 재사용된다.
| 역할 | 표준 모듈 | 어디서 쓰나 |
|---|---|---|
| HTTP 요청 | urllib.request | RSS 소스 다운로드, OpenAI 호환 API 호출 |
| XML 파싱 | xml.etree.ElementTree | RSS/Atom 피드에서 title·link·pubDate 추출 |
| 병렬 처리 | concurrent.futures | 108개 소스를 스레드 40개로 동시 수집 |
| 웹 서버 | http.server | 대시보드 정적 서빙 + /api/refresh |
| 외부 프로세스 | subprocess | 본체가 claude CLI를 자식 프로세스로 실행 |
| 날짜/시간 | datetime, email.utils | 제각각인 RSS 시간을 베이징 시간으로 통일 |
| 데이터 직렬화 | json | 수집 결과를 data.js에 저장 |
<item>(RSS) 또는 <entry>(Atom) 안에 제목·링크·발행일이 들어 있다. 이 도구의 fetch.py는 두 형식을 동시에 처리한다 — 태그 이름만 다르고 구조가 비슷하기 때문.
index.html 한 파일 안에 HTML·CSS·JavaScript가 전부 들어 있다. React도, 번들러도, npm도 없다. 브라우저가 data.js(수집 결과)를 <script> 태그로 불러오고, 바닐라 JS가 사이드바 12개 섹터와 뉴스 행을 그린다. 테마는 CSS 변수(--accent 등)로 다크 모드 고정. 폰트는 Inter + JetBrains Mono를 Google Fonts에서 로드한다.
데이터베이스 없음, 도커 없음, 클라우드 없음. "인프라"라 부를 게 server.py가 띄우는 로컬 HTTP 서버 하나뿐이다. 배포 대상이 "내 노트북"이라서 인프라가 통째로 사라졌다. 이게 초보자에게는 오히려 축복이다 — 볼 코드가 명확하다.
보통의 웹 프로젝트가 "부품(패키지) 50개를 조립한 완성차"라면, 이 프로젝트는 "엔진·바퀴·핸들만 있는 카트"다. 부품이 적어서 각 부품이 무슨 일을 하는지 눈으로 다 따라갈 수 있다. 배우기엔 카트가 낫다.
fetch(수집) → digest(요약) → render(표시), 그리고 그 사이를 잇는 data.js
전체 구조는 놀랄 만큼 단순한 3단 파이프라인이다. 각 단계가 독립 스크립트라서, 하나만 따로 돌려볼 수도 있다. 단계 사이의 "데이터 배턴"은 data.js 파일 하나다.
같은 ThreadPoolExecutor를 쓰면서 두 단계의 스레드 수가 정반대다. 이게 의도된 튜닝이다.
max_workers=40: 네트워크 대기(I/O bound)가 병목이라, 스레드를 많이 띄워 108개 소스를 "동시에 기다린다". 파이썬 GIL이 있어도 I/O 대기 중에는 다른 스레드가 도니까 효과가 크다.WORKERS=3: LLM 호출은 무겁고, 특히 claude CLI를 자식 프로세스로 중첩 실행하면 너무 많이 띄울 때 불안정해진다. 그래서 일부러 3개로 줄였다.
digest.py는 "요약해줘"만 알고, 그게 Claude 구독인지 DeepSeek API인지는 모른다. 그 차이를 llm.py의 call(system, user, cfg) 함수 하나가 흡수한다. provider 값에 따라:
# llm.py 의 핵심 분기
def call(system, user, cfg=None, timeout=240):
if cfg.get("provider") == "api":
return _call_api(...) # urllib 로 /chat/completions POST
return _call_cli(...) # subprocess 로 claude -p 실행
이게 바로 어댑터 패턴(Adapter Pattern)이다. 새 LLM 제공자가 생겨도 llm.py에 함수 하나만 추가하면, digest.py는 손댈 필요가 없다. "구독이냐 API냐"를 설정 파일 한 줄로 가르는 유연함이 여기서 나온다.
구독 모드에서 claude를 부를 때, 이 코드는 --disallowedTools로 Read·Write·Bash·WebFetch 등 모든 도구를 꺼버린다. 즉 claude를 "파일도 못 읽고 인터넷도 못 쓰는, 오직 넘겨준 텍스트만 처리하는 순수 요약기"로 가둔다. 자동화 배치에서 AI가 예상 밖 행동(파일 수정·웹 접근)을 하지 못하게 막는 최소 권한 원칙의 실전 예다.
대시보드의 새로고침 버튼은 /api/refresh에 POST로만 요청한다. server.py는 do_GET에서 이 경로를 일부러 처리하지 않는다. 이유가 주석에 적혀 있다:
만약 GET으로 /api/refresh가 동작하면, 악성 웹페이지가 <img src="http://localhost:8793/api/refresh"> 한 줄로 당신 컴퓨터의 fetch.py+claude 자식 프로세스를 몰래 실행시킬 수 있다. 이걸 CSRF(사이트 간 요청 위조)라 한다. POST로만 받으면 이런 단순 태그 공격이 막힌다. 게다가 서버는 127.0.0.1에만 바인딩돼 외부 네트워크에서 아예 닿을 수 없다.
뉴스 제목·링크는 외부 RSS에서 온 데이터라 그대로 믿으면 안 된다. index.html은 두 겹으로 막는다: esc()로 < > & "를 전부 이스케이프하고, 링크는 safeUrl()로 http(s)://로 시작하는 것만 통과시킨다. javascript: 같은 가짜 프로토콜은 걸러진다.
// 외부 RSS 링크는 http(s) 만 허용 → javascript: 등 차단
function safeUrl(u){
u=(''+(u||'')).trim();
return /^https?:\/\//i.test(u) ? esc(u) : '';
}
"외부에서 들어온 데이터는 전부 오염된 것으로 취급한다"는 웹 보안의 기본기를, 40줄짜리 렌더 함수 안에서 정직하게 실천한다.
수집 결과를 왜 data.json이 아니라 data.js로 저장할까? 파일 첫 줄이 window.DATA = {...}라서, 브라우저가 <script src="data.js">로 불러오는 순간 전역 변수에 데이터가 꽂힌다. 별도의 fetch() 요청도, CORS 설정도 필요 없다. 순수 정적 파일 서버(심지어 python3 -m http.server)만 있어도 대시보드가 뜨는 이유다.
파일이 15개도 안 된다 — 전부 한 번씩 열어볼 만하다
읽는 순서를 추천하자면: ① README.en.md로 목적 파악 → ② fetch.py로 "데이터가 어떻게 들어오나" → ③ llm.py로 "LLM을 어떻게 추상화했나" → ④ digest.py로 "요약 프롬프트와 재시도" → ⑤ server.py로 "서버·보안" → ⑥ index.html의 JS 부분으로 "어떻게 그리나". 이 순서가 파이프라인 흐름과 정확히 일치한다.
가장 긴 스크립트(177줄)인데도 부가 기능이다. sources.json에 소스를 추가·수정할 때, 그 RSS URL이 실제로 응답하는지(liveness test) 확인하고 재빌드한다. "데이터 소스 관리를 코드로 자동화"하는 실용 예시라 따로 볼 가치가 있다.
data.js는 fetch.py와 digest.py가 매번 덮어쓰는 생성 파일이다. 직접 편집해도 다음 새로고침에 날아간다. 데이터를 바꾸고 싶으면 소스(sources.json)를 고치거나 새로고침(⟳)을 누르는 게 맞다.기술별로 "이 저장소에서 배울 수 있는 것 + 바로 응용할 아이디어"
fetch.py는 "requests 없이도 웹 수집이 된다"는 걸 보여준다. urllib.request.Request에 User-Agent 헤더를 붙이고, xml.etree로 RSS/Atom을 동시에 파싱하며, email.utils.parsedate_to_datetime으로 제각각인 날짜 문자열을 표준 datetime으로 바꾼다.
응용 아이디어: 이 fetch.py만 떼어내 "내가 보는 블로그 20개의 최신 글 모으기" 같은 개인 도구로 재활용할 수 있다.
llm.py는 좋은 인터페이스 설계의 교과서다. 호출부(digest.py)는 call(system, user) 하나만 알고, 뒤에 구독이 있든 API가 있든 신경 쓰지 않는다.
응용 아이디어: 이 llm.py를 그대로 복사해 다른 프로젝트에 붙이면, 어떤 앱이든 "구독/API 둘 다 지원"이 공짜로 생긴다.
digest.py의 시스템 프롬프트는 "오직 JSON만 출력, 설명·코드블록 금지"라고 명령하고, 원하는 스키마({"points":[...],"items":[...]})를 예시로 못박는다. 그리고 extract_json()이 응답에서 첫 {와 마지막 } 사이만 잘라 파싱한다. LLM이 가끔 군더더기를 붙여도 견디는 방어적 파싱이다.
응용 아이디어: "요점 + refs(출처 번호)"처럼, AI 요약에 근거 링크를 붙이는 패턴은 RAG·리서치 도구 어디에나 쓰인다.
v1.0.1의 변경 이력이 통째로 교훈이다. 원래는 실패를 조용히 삼켰는데(except: return []), 자동 스케줄로 돌릴 때 "어느 섹터가 왜 비었는지" 아무도 몰랐다. 그래서 ① 예외 원인을 출력하고 ② 최대 3번 재시도하고 ③ 끝에 실패 섹터를 요약하고 ④ "재실행하면 실패한 섹터만 다시 채운다"로 바꿨다.
fetch.py는 소스가 에러면 None, 성공했지만 최근 기사가 없으면 []를 반환해 둘을 구분한다. 자동화 시스템에서 이 구분이 없으면 "조용한 실패"에 계속 당한다. 작지만 실무에서 뼈아프게 배우는 감각이다.
sources.json에 redline_keywords 배열이 있다 — 도박·복권·예측시장(polymarket/kalshi)·암호화폐·성인 콘텐츠 키워드. fetch.py가 제목+요약을 소문자로 합쳐 이 단어가 있으면 그 기사를 버린다. "정치·재경은 통과, 특정 카테고리는 차단"이라는 수집 단계 필터링의 단순하고 명확한 예다.
노트북 한 대면 충분하다 — 무거운 건 "네가 붙인 LLM" 뿐
| 항목 | 요구사항 | 비고 |
|---|---|---|
| OS | macOS / Windows / Linux | 파이썬만 돌면 됨 |
| Python | 3.7 이상 | 표준 라이브러리만 사용, pip install 불필요 |
| 메모리/CPU | 일반 사무용 노트북 | 수집·렌더는 매우 가벼움 |
| 네트워크 | 인터넷 필요 | 해외 소스 일부는 프록시가 필요할 수 있음 |
| LLM (택1) | Claude Code 구독 또는 OpenAI 호환 API 키 | 구독 모드는 claude login 1회 필요, $0API 모드는 사용량 과금 |
실제 "부하"는 전적으로 LLM 요약 단계에 몰린다. 구독 모드는 로컬 claude를 자식 프로세스로 12섹터 × 재시도만큼 부르므로, 첫 새로고침이 수십 초~몇 분 걸릴 수 있다(digest.py 타임아웃은 섹터당 최대 20분으로 넉넉). 반대로 API 모드는 병렬 HTTP라 대체로 빠르지만 토큰 비용이 든다.
claude-cli 모드는 본체가 로컬에 로그인된 claude CLI를 직접 실행하는 방식이라, 그 컴퓨터에서만 동작한다. 다른 기기·서버에서 돌리려면 API 모드로 바꿔야 한다. 이 트레이드오프(공짜지만 로컬 전용 vs 유료지만 어디서나)를 이해하는 게 이 도구를 제대로 쓰는 핵심이다.클론해서 하나씩 손대보면 파이프라인이 통째로 이해된다
sources.json의 sources 배열에 한 줄({"name","hint","type":"rss","url"})을 더해, 국내 매체 RSS를 붙여본다. 코드 변경 0줄로 새 소스가 뜨는 걸 확인.fetch.recent_days를 7 → 3으로 줄여 "최근 3일"만 보기. 필터가 어떻게 반영되는지 관찰.redline_keywords에 넣고, 해당 기사가 사라지는지 확인.llm.config.json을 provider:"api"로 바꾸고 DeepSeek 키를 넣어, 구독 없이 요약이 도는지 검증. llm.py의 어댑터가 어떻게 분기하는지 코드로 따라가기.digest.py의 프롬프트를 고쳐 "요점 3~5개"를 "요점 최대 8개 + 각 요점에 한 줄 근거"로 확장해 본다.python3 scripts/fetch.py만 돌려 data.js를 만들고, 콘솔에 찍히는 "섹터별 소스/기사 수" 통계를 읽어본다.fetch.py에 type:"html" 분기를 추가해, RSS가 없는 사이트를 xml.etree 대신 간단한 파싱으로 긁어오게 확장.data.js를 매번 덮어쓴다. 날짜별로 data-YYYYMMDD.js를 남겨 "지난주 요점"을 비교하는 기능을 붙여본다.이 저장소를 발판 삼아 "AI 데이터 파이프라인 개발자"로 가는 4주 코스
| 주차 | 주제 | 학습 내용 · 이 저장소와의 연결 |
|---|---|---|
| 1주차 | 파이썬 표준 라이브러리 웹 수집 | urllib·xml.etree·datetime 익히기. 이 저장소 fetch.py를 한 줄씩 재현. RSS/Atom 스펙 훑기. |
| 2주차 | 동시성 (스레드 vs 프로세스 vs async) | concurrent.futures 심화. I/O bound에 왜 스레드가 먹히는지, GIL이 뭔지 이해. asyncio로 같은 수집을 다시 짜보기(비교 학습). |
| 3주차 | LLM 오케스트레이션 · 프롬프트 엔지니어링 | 구조화 출력(JSON mode), 재시도·타임아웃, 어댑터 설계. digest.py+llm.py를 교재로. "요점+근거(refs)" 패턴을 RAG로 확장. |
| 4주차 | 로컬 서버 · 웹 보안 기초 | http.server로 미니 API 만들기. CSRF·XSS·SSRF·최소권한 개념을 server.py/index.html의 실제 방어 코드로 체득. |
이 저장소는 작지만 "수집 → AI 처리 → 표시 + 보안"이라는 현대 AI 앱의 축소판을 전부 담고 있다. 4주를 마치면 "뉴스"를 "고객 리뷰·논문·깃허브 이슈" 등 무엇으로 바꿔도 같은 골격으로 도구를 찍어낼 수 있다.
이 문서에 나온 개념들을 한 줄로 정리
| 키워드 | 한 줄 정의 |
|---|---|
| BYO-LLM | "Bring Your Own LLM" — 도구가 LLM을 내장하지 않고, 사용자가 자기 구독/API를 붙여 쓰는 방식. |
| RSS / Atom | 사이트가 새 글 목록을 기계용 XML로 공개하는 표준. 이 도구 수집의 원천. |
| 표준 라이브러리 | 파이썬 설치 시 기본 포함된 모듈(urllib·json·http.server 등). 이 저장소는 이것만 씀. |
| I/O bound | 계산보다 대기(네트워크 등)가 많은 작업. 스레드를 늘리면 빨라진다. |
| ThreadPoolExecutor | 스레드 풀로 여러 작업을 동시에 굴리는 표준 도구. 수집 40, 요약 3. |
| 어댑터 패턴 | 서로 다른 대상(구독·API)을 하나의 인터페이스로 감싸 교체 가능하게 만드는 설계. |
| 구조화 출력 | LLM에게 "JSON만 뱉어라"고 강제해 프로그램이 파싱하기 쉽게 만드는 기법. |
| 방어적 파싱 | LLM 응답에 군더더기가 섞여도 견디게, 첫 {~마지막 }만 잘라 파싱. |
| CSRF | 사이트 간 요청 위조. GET 새로고침을 막고 POST로만 받아 방어. |
| XSS | 외부 데이터로 스크립트를 주입하는 공격. esc()+safeUrl()로 방어. |
| 최소 권한 원칙 | 구독 claude를 부를 때 모든 도구를 끄고 "텍스트 요약기"로만 격리. |
| 레드라인 필터 | 수집 단계에서 특정 키워드(도박·코인·성인)를 걸러내는 콘텐츠 거버넌스. |
| Local-first | 데이터·처리를 전부 내 기기에 두는 설계. 127.0.0.1 바인딩이 상징. |
직접 클론해 읽어보는 게 가장 빠른 학습이다