📅 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
- 대상 사이트: 한화투자증권 모바일 페이지 (#_TBL_M)
- 툴: Python + Playwright 스크립트 (us_market_holidays.py)
- 저장 방식: 연도별 JSON 파일 → GitHub (holidays/2025.json 등)
- 실행 시점: 매년 1월 1일, 단 업데이트 시점은 유동적이므로 향후 검토 필요
📌 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을 실행한다면 무의미
한국시간 오전 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
- 참조 방식: 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이라는 제한된 케이스에 맞춰서 설계된 지금의 지침은 범용성이 떨어짐
→ 시각 자료를 받아서 어떤 정보(섹터/티커별 등)를 추출해야 할지 명확히 지시하는 프롬프트로 재구성해야 함 - 참조 데이터 확장
현재 이미지는 분석이 되긴 하지만, 실제 의사결정에 참조할 만큼 충분한 정보는 아님
→ 이미지 + 실시간 뉴스 + 정책/금리/환율 등으로 다층적인 참조 구조를 만들 필요가 있음 - 질문자의 의도 파악 → 조건부 참조 데이터 연결
예를 들어, 질문자가 “반도체 업종 앞으로 괜찮을까?”라고 물으면,- 업종 파악
- 해당 업종 관련 뉴스 요약
- 최근 정책/금융지표 참조
이런 식으로 조건부 참조 로직이 필요함
- 사용자 입력 기반 분석 기능 (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 |
댓글