본문 바로가기
Automation Tool/n8n Project

Sense Stock, D+13

by 그때 그때 끄적 2025. 6. 30.

📅 Sense Stock 개발 일지 (2025-06-30)

n8n 기반 주식 자동화 시스템 Sense Stock 구축 중 진행한 작업들을 정리합니다.

오늘은 Docker Rebuilding, Schedule Workflow 생성, Playwright - Python, DB 저장 작업을 진행했습니다.

 

❓ 지난번 고민 내용

  • Docker Image 재구성이 최우선  => Docker Rebuilding
  • 최근 자료 첨부 구현 => Schedule Workflow

 

🐳 Docker 재빌드 및 환경 정리

리빌딩 목적

 

  • 기존 n8n 컨테이너에서 Playwright 기반 Python 자동화를 실행하려 했으나,
    • Playwright, Python, Chromium 등 환경이 기본 이미지에 없음
    • headless 모드의 한계로 DOM 요소 미표시 등 이슈 발생
  • 따라서 Playwright 환경을 포함한 커스텀 n8n Docker 이미지로 리빌드 필요

 

커스텀 Dockerfile
->
Docker Compose 구성, docker-compose.yml
->
docker-compose down --volumes     # 캐시된 볼륨 제거
docker-compose up -d --build      # 재빌드 및 실행

Step 1. n8n 데이터 보존 구조 정리

  • 로컬 경로: C:\...\n8n
  • 컨테이너 내 마운트 경로(volumes): /home/node/.n8n
  • 핵심 파일: database.sqlite – 워크플로우, 크레덴셜, 설정 등 전부 포함
  • 볼륨 설정 방식(docker-compose.yml)
volumes:
  - "C:/.../n8n:/home/node/.n8n"

주의사항: 이 마운트된 .n8n 폴더는 반드시 node:node 소유자로 설정해야 함.
안 그러면 UI(= n8n Workflow 탭)에 데이터가 안 뜨는 현상 발생

더보기

node 권한 문제 해결 과정

리빌드하면서 node 사용자가 없거나, 파일이 root:root로 되어 있어서 문제가 됐다.

-rw-r--r-- 1 root root   462848 Jun 28 03:10 database.sqlite
  • n8n 컨테이너 내부의 Node.js 프로세스가 해당 파일을 읽거나 쓸 수 없는 상태
  • 커스텀 Dockerfile에서 아래처럼 직접 user를 추가함

<Dockerfile에 추가 작성>

# ▶ 기본 실행 명령, node 계정으로
USER node
## 이렇게 되면 UI에 정상적으로 이전에 작업했던 Workflow가 뜬다
-rw-r--r-- 1 node node   462848 Jun 28 03:10 database.sqlite

 

👉 핵심 포인트는 하나.
database.sqlite만 제대로 마운트되면 데이터는 살아 있다.
근데도 안 보이면 거의 90% 확률로 권한 문제거나, DB 파일 구조가 깨진 경우다.

나같은 경우엔 저장되지 않은 database.sqlite파일 가지고 한참을 헤맸다. 

sqlite 파일을 미리보기 할 수 있는 사이트가 있으니 활용하자!!

https://sqliteviewer.app/#/database.sqlite/table/sqlite_sequence/

 

SQLite Viewer Web App

SQLite Viewer Web App SQLite Viewer Web is a free, web-based SQLite Explorer, inspired by DB Browser for SQLite and Airtable. Use this web-based SQLite Tool to quickly and easily inspect .sqlite files. Your data stays private: Everything is done client-sid

sqliteviewer.app

 

Step 2. Playwright 실행에서 Click 실패

<증상>

playwright._impl._errors.TimeoutError: Locator.click: Timeout 30000ms exceeded.
waiting for get_by_text("Share map")

 

  • 실행 환경: Docker + Linux + headless
  • 원인: headless 환경에서는 finviz.com에서 해당 버튼("Share map")이 아예 DOM에 렌더링되지 않음
    • 해당 버튼을 클릭해야 이미지 URL 및 다운로드가 가능
  • 로컬 브라우저로 실행하면 정상 → headless일 때만 문제 발생

🔹 해결 시도 및 우회

방법 결과
headless=False로 실행 ❌ Docker에 XServer 없음 → 실행 실패
wait_for_selector() 사용 ❌ 여전히 존재하지 않음 → Timeout
대안 1: 클릭 제거, 이미지 직접 추출 시도 ❌ <canvas> 기반이라 <img> 없음
대안 2: canvas.toDataURL() 사용 ❌ headless에선 <canvas> 자체가 아예 존재하지 않음

최종 대안

Playwright에서 viewport만 크게 설정한 뒤, heatmap(<canvas> 위치 정보 계산) 이미지만 캡처하는 방식

더보기

Python Code

import asyncio
import base64
from playwright.async_api import async_playwright

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)

        ## 브라우저 컨텍스트 설정(우회) 
        context = await browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
            viewport={"width": 1400, "height": 1000},
            java_script_enabled=True,
            locale="en-US"
)

        page = await context.new_page()
        await page.goto("https://finviz.com/map.ashx", wait_until="domcontentloaded", timeout=60000)
        
        ## 캔버스의 위치 정보 계산
        box = await page.evaluate("""() => {
            const el = document.querySelector('canvas.chart');
            if (!el) return null;
            const rect = el.getBoundingClientRect();
            return {
                x: rect.x,
                y: rect.y,
                width: rect.width,
                height: rect.height
            };
        }""")

        if not box:
            print("❌ 캔버스 요소를 찾을 수 없습니다.")
            await browser.close()
            return

        ## 특정 위치만 캡처
        image_bytes = await page.screenshot(clip=box)
        base64_img = base64.b64encode(image_bytes).decode("utf-8")
        print(base64_img)

        await browser.close()

asyncio.run(main())

 

 

🧱 Schedule Workflow 

미국 주식시장 개장일을 기준으로 매일 S&P500 Heatmap 이미지를 저장하고,
이후 AI Agent가 이를 참조하여 투자 정보 분석에 활용하는 자동화 흐름을 구축

 

📌 미국 증시 휴장일 수집 및 저장 Workflow

Sense Stock US Holiday Workflow

  • 대상 사이트: 한화투자증권 모바일 페이지 (#_TBL_M)
  • 툴: Python + Playwright 스크립트 (us_market_holidays.py)
  • 저장 방식: 연도별 JSON 파일 → GitHub (holidays/2025.json 등)
  • 실행 시점: 매년 1월 1일, 단 업데이트 시점은 유동적이므로 향후 검토 필요

 

📌 Market Data 저장 Workflow (이미지 추출)

Sense Stock Market Data Workflow

 

  • Trigger: 매일 오전 9시 (KST)Cron 표현식: 0 0 * * * ( America/New_York 기준)
  • 미국 휴장일 Data 참조(HTTP Holidays)
  • 조건 분기
    • ✅ 공휴일이 아님 + 화~토 요일이면 실행
    • ❌ 일요일, 월요일, 미국 공휴일일 경우 → 실행 제외
  • 저장 경로: heatmap/YYYY-MM/finviz_map_YYYY-MM-DD.png
  • 저장 방식: base64 이미지 → GitHub API 업로드

중요!

n8n의 Schedule 노드가 기본적으로 서버 시간대(= Docker 컨테이너의 시스템 시간대) 기준으로
동작하기 때문에, Docker 환경에서 시간대(Timezone)를 "Asia/Seoul"로 설정하면,
Cron 표현식을 한국 시간 기준으로 직접 작성할 수 있습니다.

하지만 America/New_York 시간도 필요할것같아 그냥 쓰기로한다.

=> 한국 시간 기준으로 실행돼야하기 때문에 변경

=> 다시 그냥 사용하기로 한다. 

- 내 PC를 한시간 더 일찍 켜놓아야 되겠지만, 개인 서버 or 서버에 올린 상태로 n8n을 실행한다면 무의미

https://blog.naver.com/arimlog/223781101012?trackingCode=rss

더보기

한국시간 오전 9시 vs 미국 동부시간 (뉴욕 기준)

 

구분 미국 동부 시간대 시차 현지 시간 (뉴욕)
✅ 서머타임 적용 시 EDT (UTC−4) -13시간 전날 오후 8시 (20:00)
❌ 서머타임 미적용 시 EST (UTC−5) -14시간 전날 오후 7시 (19:00)
방법 Cron Timezone 결과
권장 방법 0 9 * * * Asia/Seoul 매일 한국 오전 9시 실행
서버 시간 유지 0 20 * * * America/New_York 서머타임 시 한국 9시
서버 시간 유지 0 19 * * * America/New_York 비서머타임 시 한국 9시

<docker-compose.yml에 설정 추가 방법>

services:
  n8n:
    image: n8nio/n8n
    environment:
      - TZ=Asia/Seoul
      
      
## 저장 후, 컨테이너 재시작
docker-compose down
docker-compose up -d
* down 없이 up -d --force-recreate로만 해도 적용되긴 합니다.

## 컨테이너에서 확인 방법
docker exec -it [컨테이너 이름] date

## 출력확인, KST
Tue Jul  2 09:00:01 KST 2025

 

📌 AI Agent 참조 Node

기존 Workflow에 추가될 Node

 

  • 참조 방식: OCR 사용 ❌ → GPT가 heatmap 이미지를 직접 이해함
    • 이미지를 적절하게 이해하고 있음. 관련 프롬프트만 추가
  • GitHub에서 가장 최근 이미지를 참조하는 구조
    • Step 1: 해당 월 디렉토리(heatmap/YYYY-MM)의 파일 목록 조회
    • Step 2: 최신 날짜 기준 파일 1개 선택
  • 예외 처리:
    • 📅 7월 1일이 공휴일이면 → heatmap/2025-07 디렉토리에 아무 파일도 없을 수 있음
    • ✅ 이 경우 → 지난달(2025-06) 디렉토리로 fallback → 가장 마지막 날짜의 이미지 사용

 

스케줄링 고민과 설계 포인트

단순한 "매일 실행"만으론 신뢰할 수 없다는 걸 깨닫고, 다양한 예외를 고려함

📌 고민 포인트

항목 처리 방식
✅ 요일 필터링 일요일, 월요일은 시장 미개장 → 기존 토요일 이미지 참조
✅ 공휴일 대응 GitHub holidays/2025.json 참조 → 해당 날짜는 이미지 저장 제외
✅ 시간대 문제 America/New_York 시간 → 한국시간 기준으로 변환(Summer Time이 적용되면 시간이 달라짐)
✅ 파일 존재 확인 저장 워크플로우는 아예 업로드하지 않음 → 참조 워크플로우에서 직접 GitHub 파일 목록을 조회하여 판단
✅ 이미지 참조 fallback 해당 월에 데이터가 없을 경우 → 지난달로 fallback하여 안정성 확보

 

❓ 다음 단계에서 고민 중인 것들

  • 데이터 적재 확인 및 GitHub 한계 이슈
    현재 이미지 기반 데이터가 잘 적재되고 있지만, GitHub는 5GB 제한이 있어 향후 대용량 이미지/데이터셋을 처리하려면 외부 저장소(Supabase, S3, 혹은 Vector DB)로 이전해야 할 가능성이 높음.
    (추후)언제 이전할지를 기준으로 워크플로우 구조를 다시 짜야 할지도
  • Workflow 모듈화 고민
    현재는 이미지 추출과 분석이 기존 워크플로우 안에 통합돼 있는데, 이걸 별도 워크플로우로 분리하고, 메인 워크플로우에서 호출(Execute Workflow)하는 구조로 바꾸는 게 더 유연할 수 있음.
    → 재사용성과 유지보수 측면에서 장점이 크지만, 데이터 공유 방식(예: Binary 데이터 연결 등) 정리가 필요
  • 이미지 분석 지침 프롬프트 재작성 필요
    S&P500 Heatmap이라는 제한된 케이스에 맞춰서 설계된 지금의 지침은 범용성이 떨어짐
    → 시각 자료를 받아서 어떤 정보(섹터/티커별 등)를 추출해야 할지 명확히 지시하는 프롬프트로 재구성해야 함
  • 참조 데이터 확장
    현재 이미지는 분석이 되긴 하지만, 실제 의사결정에 참조할 만큼 충분한 정보는 아님
    → 이미지 + 실시간 뉴스 + 정책/금리/환율 등으로 다층적인 참조 구조를 만들 필요가 있음
  • 질문자의 의도 파악 → 조건부 참조 데이터 연결
    예를 들어, 질문자가 “반도체 업종 앞으로 괜찮을까?”라고 물으면,
    1. 업종 파악
    2. 해당 업종 관련 뉴스 요약
    3. 최근 정책/금융지표 참조
      이런 식으로 조건부 참조 로직이 필요함
  • 사용자 입력 기반 분석 기능 (URL / 텍스트 / PDF 업로드)
    • 질문 기반 분석 외에, 사용자가 정보를 제공하는 흐름도 실험 중이다.고민 중인 입력 타입
      • 📎 URL: Firecrawl이나 Puppeteer로 콘텐츠 요약 후 분석
      • 📝 텍스트 입력: 사용자가 복사한 뉴스 전문 등
      • 📄 PDF 업로드: PDF → 텍스트 변환 → RAG 적용 가능성 검토 중
      이 흐름이 정착되면 단순한 응답형 챗봇이 아니라,
      "사용자 맞춤형 투자 분석 비서"로 역할 확장이 가능해진다.
    • 단순히 "이 종목 어때요?"가 아니라, 사용자 스스로 리포트나 기사 링크를 붙이는 방식.

 

'Automation Tool > n8n Project' 카테고리의 다른 글

Sense Stock 개발 회고, 두 번째  (1) 2025.07.04
Sense Stock, D+14  (0) 2025.07.01
Sense Stock, D+12  (0) 2025.06.27
Sense Stock, D+11  (1) 2025.06.26
Sense Stock, D+10  (0) 2025.06.24

댓글