본문 바로가기
직장인 Claude 활용법-Claude 기초 교실

AI 주식 분석 앱 만들기 5편 - 실시간 주가·뉴스 연동 + API 키 보안 완성

by 크리처 2026. 5. 17.
반응형

Cloudflare Worker로 Yahoo Finance 실시간 주가, Naver 뉴스, DART 공시를 연동하고 API 

키를 안전하게 숨기는 방법. 실제 검증된 Worker 코드 전체 수록.

이번 편을 완료하면 진짜 실시간 데이터 기반 AI 분석이 완성됩니다! 

 

  5편에서 할 일
  □  Cloudflare Worker 새로 만들기 (stock-ai-proxy)
  □  Worker 코드 입력 및 배포
  □  환경변수에 API 키 2개 등록 (Anthropic + DART)
  □  Worker GET 엔드포인트로 종목 자동검색 확인
  □  index.html Worker URL 수정 후 재배포
  □  실시간 주가 기반 AI 분석 최종 확인

 

  Worker가 하는 일 — 전체 구조

 

  Cloudflare Worker는 우리 앱의 서버 역할을 합니다. 브라우저 대신 모든 데이터를 수집하고 AI 분석을 처리합니다.

 

  Worker가 처리하는 것들
  ① 종목명 검색 (GET 요청) → 네이버 자동완성 API로 전체 2,500개+ 종목 검색
  ② 실시간 주가 수집 → Yahoo Finance API (현재가, 등락률, 52주고저, 시가총액)
  ③ 최신 뉴스 수집 → Naver 검색으로 관련 뉴스 5개
  ④ Claude AI 분석 → 수집된 실제 데이터 기반으로 매수/매도/홀드 판단
  ⑤ API 키 보안 → 사용자는 키를 절대 볼 수 없음
  왜 Worker가 필요한가?
  브라우저에서 직접 API 호출 → CORS 오류로 차단됨
  Worker에서 API 호출 → CORS 문제 없음
  API 키를 Worker 환경변수에 보관 → 사용자에게 노출 안 됨
  결론: Worker = 중간 서버 역할 + 보안 처리

 

  PART 1 — Cloudflare Worker 만들기

 

    1        Workers & Pages 접속
  dash.cloudflare.com 로그인
  왼쪽 메뉴 'Workers & Pages' 클릭
  '응용 프로그램 생성' 버튼 클릭

영어 메뉴가 불편하시면 한글로 변경하시면 됩니다.

 

    2   Hello World로 시작 선택
  화면에서 'Hello World로 시작하십시오!' 클릭
  (Upload your static files가 아닌 것 주의!)
  Worker 이름 입력: stock-ai-proxy
  '배포' 버튼 클릭
3   코드 편집 화면 열기
  배포 완료 후 Worker 상세 페이지로 이동
  오른쪽 상단 '코드 편집' 버튼 클릭
  코드 편집기 화면이 열립니다
  ✅ 핵심 포인트  '코드 편집' 버튼이 안 보이면 상단 '개요' 탭을 클릭하면 오른쪽 상단에 버튼이 나타납니다!

 

  PART 2 — Worker 코드 입력하기

 

  코드 편집기가 열리면 기존 코드를 전체 삭제하고 아래 코드를 붙여넣습니다.

 

  코드 입력 방법
  ① 코드 편집기 안을 클릭
  ② Ctrl+A (전체 선택) → Delete (삭제)
  ③ 아래 코드 전체를 Ctrl+C로 복사
  ④ 편집기에 Ctrl+V로 붙여넣기
  ⑤ 오른쪽 상단 '배포' 버튼 클릭
  ⑥ 오른쪽 미리보기에서 새로고침 → [ ] = 정상! (search 파라미터 없으면 빈 배열 반환)

  Worker 전체 코드 (복사해서 사용)

export default {
  async fetch(request, env) {
 
    // CORS 허용
    if (request.method === 'OPTIONS') {
      return new Response(null, {
        headers: {
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Methods': 'POST, OPTIONS, GET',
          'Access-Control-Allow-Headers': 'Content-Type',
        }
      });
    }
 
    // GET 요청 - 종목 자동검색 (네이버 자동완성)
    if (request.method === 'GET') {
      const url = new URL(request.url);
      const query = url.searchParams.get('search') || '';
      if (!query) return new Response('[]', {
        headers: {'Content-Type':'application/json','Access-Control-Allow-Origin':'*'}
      });
      if (/^\d{6}$/.test(query)) return new Response(
        JSON.stringify([{name:query,code:query,market:'직접입력'}]),
        {headers:{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'}}
      );
      try {
        const res = await fetch(
          `https://ac.stock.naver.com/ac?q=${encodeURIComponent(query)}&q_enc=utf-8&target=stock`,
          {headers:{'User-Agent':'Mozilla/5.0','Referer':'https://finance.naver.com'}}
        );
        const data = JSON.parse(await res.text());
        const results = (data.items||[])
          .filter(i=>i.code&&/^\d{6}$/.test(i.code)&&
            (i.typeCode==='KOSPI'||i.typeCode==='KOSDAQ'))
          .slice(0,8)
          .map(i=>({name:i.name,code:i.code,market:i.typeName||i.typeCode}));
        return new Response(JSON.stringify(results),
          {headers:{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'}});
      } catch(e) {
        return new Response('[]',
          {headers:{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'}});
      }
    }
 
    if (request.method !== 'POST') {
      return new Response('Method not allowed', {status:405});
    }
 
    try {
      const body = await request.json();
      const stockName = body.stockName || '';
      const stockCode = body.stockCode || '';
      const stockMarket = body.stockMarket || '';
      const mode = body.mode || 'recommend';
      const userPrompt = body.messages?.[0]?.content || '';
      let priceText = '', newsText = '', hasRealData = false;
 
      if (stockName && mode === 'analyze') {
        // Yahoo Finance 실시간 주가
        if (stockCode) {
          try {
            const suffix = stockMarket.includes('KOSDAQ')||
              stockMarket==='코스닥' ? 'KQ' : 'KS';
            let res = await fetch(
              `https://query1.finance.yahoo.com/v8/finance/chart/${stockCode}.${suffix}?interval=1d&range=1d`,
              {headers:{'User-Agent':'Mozilla/5.0','Accept':'application/json'}}
            );
            let data = await res.json();
            let meta = data?.chart?.result?.[0]?.meta;
            if (!meta?.regularMarketPrice) {
              const alt = suffix==='KS'?'KQ':'KS';
              res = await fetch(
                `https://query1.finance.yahoo.com/v8/finance/chart/${stockCode}.${alt}?interval=1d&range=1d`,
                {headers:{'User-Agent':'Mozilla/5.0'}}
              );
              data = await res.json();
              meta = data?.chart?.result?.[0]?.meta;
            }
            if (meta?.regularMarketPrice) {
              const cur = meta.regularMarketPrice;
              const prev = meta.previousClose||meta.chartPreviousClose||cur;
              const chg = ((cur-prev)/prev*100).toFixed(2);
              const mktCap = meta.marketCap ?
                (meta.marketCap>=1e12 ?
                  `${(meta.marketCap/1e12).toFixed(1)}조원` :
                  `${(meta.marketCap/1e8).toFixed(0)}억원`) : '-';
              priceText =
                `현재가: ${Math.round(cur).toLocaleString()}원 (${chg>0?'+':''}${chg}%)\n` +
                `52주최고: ${Math.round(meta.fiftyTwoWeekHigh||0).toLocaleString()}원\n` +
                `52주최저: ${Math.round(meta.fiftyTwoWeekLow||0).toLocaleString()}원\n` +
                `시가총액: ${mktCap}`;
              hasRealData = true;
            }
          } catch(e) {}
        }
 
        // Naver 뉴스 수집
        try {
          const newsRes = await fetch(
            `https://search.naver.com/search.naver?where=news&query=${encodeURIComponent(stockName+' 주가')}&sort=1`,
            {headers:{'User-Agent':'Mozilla/5.0','Referer':'https://www.naver.com/'}}
          );
          const html = await newsRes.text();
          const titles = [...html.matchAll(
            /class="news_tit"[^>]*title="([^"]{10,80})"/g
          )].slice(0,5).map(m=>m[1]);
          if (titles.length>0) {
            newsText = titles.map((t,i)=>`${i+1}. ${t}`).join('\n');
            hasRealData = true;
          }
        } catch(e) {}
      }
 
      // Claude AI 분석
      const prompt = [
        stockName ? `분석 종목: ${stockName}` : '',
        priceText ? `\n【실시간 주가 (Yahoo Finance)】\n${priceText}` :
          '\n【주가】실시간 조회 실패 — 학습 데이터로 보완',
        newsText ? `\n【최신 뉴스 (Naver)】\n${newsText}` : '',
        `\n${userPrompt}`,
        priceText ? '\n⚠️ 위 실제 주가를 반드시 그대로 사용하고 현재가를 명시하세요.' : ''
      ].filter(Boolean).join('\n');
 
      const claudeRes = await fetch('https://api.anthropic.com/v1/messages', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': env.ANTHROPIC_API_KEY,
          'anthropic-version': '2023-06-01',
        },
        body: JSON.stringify({
          model: 'claude-haiku-4-5-20251001',
          max_tokens: 2500,
          system: `당신은 주식 분석 전문가입니다. 한국어로 답변하세요.
실시간 주가가 제공된 경우 반드시 그 수치를 그대로 사용하세요.
형식: [요약] 현재가 XX원 기준 / [판단] 매수/매도/홀드 /
[근거 1] 밸류에이션 / [근거 2] 뉴스 이슈 / [근거 3] 성장성 /
[주의사항] 리스크 / ⚠️ 투자 결정은 본인 책임입니다.`,
          messages: [{role:'user',content:prompt}]
        })
      });
      const claudeData = await claudeRes.json();
      const news = newsText ?
        newsText.split('\n').map(l=>({title:l.replace(/^\d+\.\s*/,''),desc:''})) : [];
 
      return new Response(JSON.stringify({
        content: claudeData.content,
        news, priceData: priceText, hasRealData
      }), {headers:{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'}});
 
    } catch(err) {
      return new Response(JSON.stringify({error:err.message}),
        {status:500,headers:{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'}});
    }
  }
};
  ✅ 핵심 포인트  코드를 이해할 필요 없습니다! 전체를 복사-붙여넣기만 하면 됩니다.

 

  PART 3 — 환경변수에 API 키 등록하기

 

  Worker 코드 안의 env.ANTHROPIC_API_KEY와 env.DART_API_KEY는 환경변수에서 값을 가져옵니다.

 

1   설정 탭으로 이동
  코드 편집 화면에서 왼쪽 상단 '← stock-ai-proxy' 클릭
  상단 탭에서 '설정' 클릭
  '변수 및 암호' 항목 찾기
2   Anthropic API 키 등록
  오른쪽 '+ 추가' 버튼 클릭
  변수 이름: ANTHROPIC_API_KEY (정확히 이렇게!)
  값: sk-ant-api... (2편에서 발급받은 키)
  '배포' 버튼 클릭
3   DART API 키 등록
  다시 '+ 추가' 버튼 클릭
  변수 이름: DART_API_KEY
  값: 2편에서 발급받은 40자리 DART 키
  '배포' 버튼 클릭

 

  • 배포가 완료되면 화면 과 같이 변수 및 암호에 두개가 등록된 것이 표시됩니다.
  🚨 절대 주의  API 키 입력 시 절대 스크린샷을 찍지 마세요! 키가 노출되면 즉시 삭제하고 새 키를 발급받아야 합니다.

 

  변수 이름 주의사항
  ANTHROPIC_API_KEY — 대소문자 정확히 입력 (모두 대문자)
  DART_API_KEY — 대소문자 정확히 입력 (모두 대문자)
  앞뒤 공백 없도록 주의
  코드의 env.ANTHROPIC_API_KEY와 정확히 일치해야 함

 

  PART 4 — 종목 자동검색 작동 확인

 

  Worker 배포 후 종목 검색이 정상 작동하는지 브라우저에서 직접 확인합니다.

 

1   기본 작동 확인
  브라우저 주소창에 입력:
  https://stock-ai-proxy.본인계정명.workers.dev
  [ ] = 정상! (search 파라미터 없으면 빈 배열 반환) 뜨면 Worker 정상 작동 ✅
2   종목 검색 확인
  브라우저 주소창에 입력:
  https://stock-ai-proxy.본인계정명.workers.dev/?search=삼성전자
  아래와 같은 결과가 나오면 성공:
  [{"name":"삼성전자","code":"005930","market":"코스피"},...]
  ✅ 핵심 포인트  'Method not allowed'는 오류가 아닙니다! GET/POST만 허용하도록 설계된 정상 동작입니다.

 

  PART 5 — index.html Worker URL 수정

 

  index.html 파일에서 WORKER_URL을 본인의 Worker 주소로 변경해야 합니다.

 

1   수정할 부분 찾기
  VS Code에서 index.html 열기
  Ctrl+F → 'WORKER_URL' 검색
  아래 줄 찾기:
  const WORKER_URL = 'https://stock-ai-proxy.본인계정명.workers.dev';
2   본인 주소로 변경
  계정명 부분을 본인 Cloudflare 계정명으로 변경
  예시: const WORKER_URL = 'https://stock-ai-proxy.본인의 계정명.workers.dev';
  Ctrl+S로 저장
3   재배포
  수정된 index.html을 Cloudflare에 재업로드
  4편 PART 4의 재배포 방법 참고
  배포 완료 후 URL 접속해서 확인

 

  PART 6 — 실시간 분석 최종 확인

 

  모든 설정이 완료되면 실제 종목으로 테스트합니다.

  최종 확인 순서
  ① 배포된 앱 URL 접속(https://polished-queen-4451.본인계정명.workers.dev/)
  ② 탭 2 클릭 → 종목명 입력 (예: 삼성전자)
  ③ 드롭다운에서 '삼성전자 (005930) — 코스피' 선택
  ④ 'AI 매수/매도 판단 받기' 클릭
  ⑤ 배지 확인: 📊 실시간 뉴스 + 주가 데이터 반영 (초록색) ✅
  ⑥ 분석 결과에서 현재 실제 주가가 정확히 나오는지 확인

 

  • 메인화면이 나타나면 테스트를 실시하여 정상적으로 가동되는지 확인합니다

 

  • 분석결과가 화면과 같이 출력됩니다.
  정상 작동 확인 항목
  ✅ 종목명 입력 시 드롭다운 자동완성 작동
  ✅ 분석 결과 상단에 '📊 실시간 뉴스 + 주가 데이터 반영' 배지 표시
  ✅ 현재가가 실제 주가와 일치 (네이버 증권에서 확인)
  ✅ 최신 뉴스 5개 표시
  ✅ AI 분석 결과 출력

 

  오류 발생 시 해결법

  🔴 오류: Worker 오류: 500
        원인: Worker 코드에 오류가 있거나 API 키가 잘못 설정됨
        해결: 환경변수 이름 대소문자 확인 (ANTHROPIC_API_KEY, DART_API_KEY)
  🔴 오류: 📚 AI 학습 데이터 기반 분석 배지 (회색)
        원인: 실시간 주가 수집 실패 — 종목코드나 시장 정보가 잘못됨
        해결: 드롭다운에서 종목을 반드시 선택 후 분석 (직접 타이핑 말고)
  🔴 오류: 검색 결과 없음
        원인: 종목명이 정확하지 않거나 네이버에서 다른 이름으로 등록됨
        해결: 짧게 줄여서 검색 (예: KBI메탈 → KBI, 한화에어로스페이스 → 한화에어)

 

  5편 완료 체크리스트

  stock-ai-proxy Worker 생성 완료
  Worker 코드 붙여넣기 및 배포 완료
  Worker 기본 작동 확인 (Method not allowed)
  종목 검색 확인 (?search=삼성전자 → JSON 결과)
  ANTHROPIC_API_KEY 환경변수 등록 완료
  DART_API_KEY 환경변수 등록 완료
  index.html WORKER_URL 수정 완료
  Cloudflare 재배포 완료
  실시간 주가 기반 AI 분석 작동 확인
  📊 실시간 배지 초록색으로 표시 확인

 

  6편 예고 — PWA 스마트폰 앱 아이콘
  • manifest.json 파일 만들기 (앱 이름, 아이콘 설정)
  • sw.js 서비스워커 만들기
  • 앱 아이콘 이미지 준비 (192x192, 512x512)
  • index.html에 PWA 설정 추가
  • 스마트폰 홈화면에 'AI주식' 앱 아이콘 설치!
  ✅ 핵심 포인트  🎉 5편 완료! 실시간 데이터 기반 AI 주식 분석이 완성됐습니다. 
        6편에서는 스마트폰 홈화면에 앱 아이콘을 추가합니다!

 

⚠️  본 글은 교육 목적으로 제작되었으며 투자 권유가 아닙니다.

반응형