- 요약을 JSON으로 구조화: oneliner, main_points, conclusion 분리 - Claude에게 JSON 형식으로만 응답하도록 프롬프트 변경 - n8n Discord 임베드: 섹션별 필드 분리, 이모지, 타임스탬프 추가 - JSON.stringify Expression으로 특수문자 이스케이프 처리 - 전체 문서 API 응답 형식 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.6 KiB
n8n 워크플로우 구성 가이드
워크플로우 개요
이 워크플로우는 2개의 YouTube 채널(머니코믹스, 슈카월드)의 새 영상을 YouTube Data API v3로 감지하고, FastAPI /api/news/summarize 엔드포인트를 호출하여 요약한 뒤, 응답 결과에 따라 Discord로 요약 또는 에러 알림을 전송하는 자동화 파이프라인입니다.
전체 흐름:
Schedule Trigger (매 정시)
├→ HTTP Request (머니코믹스 playlistItems) → Edit Fields (channel_name: 머니코믹스)
└→ HTTP Request (슈카월드 playlistItems) → Edit Fields (channel_name: 슈카월드)
↓
Merge (Append)
↓
Code (새 영상만 필터링)
↓
HTTP Request (POST /api/news/summarize)
↓
Switch (status 분기)
├→ ok → Discord 요약 전송
├→ skipped → No Operation
└→ error → Discord 에러 알림
사전 준비: YouTube Data API 키 발급
- Google Cloud Console → 프로젝트 생성/선택
- "YouTube Data API v3" 검색 → 사용 설정
- 사용자 인증 정보 → API 키 생성
- (권장) 키 제한 설정:
- API 제한: YouTube Data API v3만 허용
- 애플리케이션 제한: IP 주소 → n8n 서버 IP
- 일일 쿼터: 10,000 units (
playlistItems.list는 1 unit/요청 → 2채널 × 24회 = 48 units/일)
노드 구성 상세
1. Schedule Trigger
| 설정 항목 | 값 |
|---|---|
| Type | Schedule Trigger |
| Rule | Every Hour |
| Minute | 0 |
2. YouTube API — playlistItems (채널별 각 1개)
| 설정 항목 | 값 |
|---|---|
| Type | HTTP Request |
| Method | GET |
| URL | https://www.googleapis.com/youtube/v3/playlistItems |
| Send Query Parameters | ON |
| Specify Query Parameters | Using Individual Fields |
Query Parameters (Add Parameter로 추가):
| 파라미터 | 값 |
|---|---|
part |
snippet |
playlistId |
채널 업로드 목록 ID (아래 참고) |
maxResults |
5 |
key |
YouTube Data API v3 키 |
채널별 playlistId (채널 ID의 UC → UU로 변환):
| 채널 | 채널 ID | playlistId (업로드 목록) |
|---|---|---|
| 머니코믹스 | UCJo6G1u0e_-wS-JQn3T-zEw |
UUJo6G1u0e_-wS-JQn3T-zEw |
| 슈카월드 | UCsJ6RuBiTVWRX156FVbeaGg |
UUsJ6RuBiTVWRX156FVbeaGg |
응답 예시:
{
"items": [
{
"snippet": {
"publishedAt": "2026-03-25T06:00:00Z",
"title": "영상 제목",
"resourceId": { "videoId": "abc123" }
}
}
]
}
3. Edit Fields (채널별 각 1개)
| 설정 항목 | 값 |
|---|---|
| Type | Edit Fields (Set) |
| Mode | Manual Mapping |
| Fields to Set | channel_name (String) = 머니코믹스 또는 슈카월드 |
| Include Other Input Fields | All (기존 데이터 유지) |
4. Merge
| 설정 항목 | 값 |
|---|---|
| Type | Merge |
| Mode | Append |
5. Code (새 영상 필터링)
| 설정 항목 | 값 |
|---|---|
| Type | Code |
| Language | JavaScript |
| Mode | Run Once for All Items |
최근 1시간 이내 발행된 영상만 필터링합니다:
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
return $input.all().filter(item => {
const items = item.json.items || [];
return items.some(i => new Date(i.snippet.publishedAt) > oneHourAgo);
}).flatMap(item => {
const channelName = item.json.channel_name || '';
return (item.json.items || [])
.filter(i => new Date(i.snippet.publishedAt) > oneHourAgo)
.map(i => ({
json: {
video_url: `https://www.youtube.com/watch?v=${i.snippet.resourceId.videoId}`,
title: i.snippet.title,
channel_name: channelName,
}
}));
});
참고: Schedule Trigger가 매 정시 실행되므로, 1시간 이내 영상을 필터링하면 새 영상만 처리됩니다. 영상이 없으면 빈 배열이 반환되어 이후 노드가 실행되지 않습니다.
6. HTTP Request (요약 API 호출)
| 설정 항목 | 값 |
|---|---|
| Type | HTTP Request |
| Method | POST |
| URL | https://<서버주소>/api/news/summarize |
| Send Body | ON |
| Body Content Type | JSON |
| Specify Body | Using JSON |
Body (Expression 모드):
{
"video_url": "{{ $json.video_url }}",
"title": "{{ $json.title }}",
"channel_name": "{{ $json.channel_name }}"
}
Headers (API_SECRET 사용 시):
| 헤더 | 값 |
|---|---|
X-Api-Secret |
설정한 시크릿 값 |
Options:
- Always Output Data: ON (에러 시에도 다음 노드로 전달)
주의: 영상 제목에 큰따옴표(
")가 포함된 경우 JSON이 깨질 수 있습니다. 반드시 Expression 모드를 사용하세요.
7. Switch (응답 분기)
| 설정 항목 | 값 |
|---|---|
| Type | Switch |
| Mode | Rules |
| Data Type | String |
| Value | {{ $json.status }} |
Rules:
| Rule | Operation | Value | Output |
|---|---|---|---|
| Rule 1 | Equal | ok |
→ Discord 요약 전송 |
| Rule 2 | Equal | skipped |
→ No Operation (무시) |
| Rule 3 | Equal | error |
→ Discord 에러 알림 |
8. Discord 요약 전송 (status=ok)
| 설정 항목 | 값 |
|---|---|
| Type | HTTP Request |
| Method | POST |
| URL | Discord 웹훅 URL |
| Send Body | ON |
| Body Content Type | JSON |
| Specify Body | Using JSON |
Body:
Specify Body를 Using JSON으로 선택 후, JSON 입력란 우측의 Expression 토글을 켜고 아래 코드를 입력합니다. JSON.stringify를 사용해야 요약 텍스트의 특수문자(따옴표, 줄바꿈 등)가 자동 이스케이프됩니다.
={{ JSON.stringify({
embeds: [{
title: "📰 [" + $json.channel_name + "] " + $json.title,
url: $json.video_url,
description: "### 💡 " + $json.oneliner,
color: 2829105,
fields: [
{
name: "📋 주요 내용",
value: $json.main_points,
inline: false
},
{
name: "🎯 결론 / 시사점",
value: $json.conclusion,
inline: false
},
{
name: "🔗 원본 영상",
value: $json.video_url,
inline: false
}
],
thumbnail: {
url: "https://img.youtube.com/vi/" + $json.video_url.split("v=")[1] + "/hqdefault.jpg"
},
footer: {
text: "YouTube 뉴스 요약 봇 • " + $json.channel_name
},
timestamp: new Date().toISOString()
}]
}) }}
9. Discord 에러 알림 (status=error)
| 설정 항목 | 값 |
|---|---|
| Type | HTTP Request |
| Method | POST |
| URL | Discord 웹훅 URL |
| Send Body | ON |
| Body Content Type | JSON |
| Specify Body | Using JSON (Expression 토글 ON) |
Body:
={{ JSON.stringify({
embeds: [{
title: "❌ [" + $json.channel_name + "] 뉴스 요약 실패",
color: 15548997,
fields: [
{ name: "영상 제목", value: $json.title || "(제목 없음)", inline: false },
{ name: "영상 URL", value: $json.video_url, inline: false },
{ name: "에러 타입", value: "`" + $json.error_type + "`", inline: true },
{ name: "에러 내용", value: "```\\n" + $json.error_message + "\\n```", inline: false }
],
footer: {
text: "봇 요약 처리 에러 — FastAPI 응답"
},
timestamp: new Date().toISOString()
}]
}) }}
에러 처리
에러 알림은 두 종류로 구분됩니다:
| 구분 | 발생 위치 | 원인 예시 | 알림 제목 |
|---|---|---|---|
| 워크플로우 에러 | n8n 자체 | API 연결 불가, 노드 설정 오류, YouTube API 실패 | ⚠️ n8n 워크플로우 에러 |
| 요약 처리 에러 | FastAPI 봇 | 자막 없음, 쿠키 만료, Claude API 오류 | ❌ [채널명] 뉴스 요약 실패 |
Error Trigger 워크플로우 (n8n 워크플로우 에러)
메인 워크플로우 자체가 실패하는 경우를 대비해 별도 에러 워크플로우를 만들 수 있습니다.
- 새 워크플로우 생성 → Error Trigger 노드 추가
- HTTP Request 노드로 Discord Webhook 호출:
| 설정 항목 | 값 |
|---|---|
| Type | HTTP Request |
| Method | POST |
| URL | Discord 웹훅 URL |
| Send Body | ON |
| Body Content Type | JSON |
| Specify Body | Using JSON |
Body (Expression 모드):
{
"embeds": [{
"title": "⚠️ n8n 워크플로우 에러",
"color": 16753920,
"description": "n8n 워크플로우 실행 중 에러가 발생했습니다. 봇 요약 처리가 아닌 **워크플로우 자체 문제**입니다.",
"fields": [
{ "name": "워크플로우", "value": "{{ $json.workflow.name }}", "inline": true },
{ "name": "실패 노드", "value": "{{ $json.execution.lastNodeExecuted }}", "inline": true },
{ "name": "에러 내용", "value": "```\n{{ $json.execution.error.message || '상세 내용 없음' }}\n```", "inline": false },
{ "name": "실행 ID", "value": "`{{ $json.execution.id }}`", "inline": true }
],
"footer": {
"text": "n8n Error Trigger — 워크플로우 에러"
}
}]
}
- 메인 워크플로우의 Settings → Error Workflow에서 이 에러 워크플로우를 지정
참고:
$json.execution.error.message가 빈 값일 수 있으므로|| '상세 내용 없음'으로 fallback 처리합니다.