한국GPT협회 표준교재

651. n8n 리서치 자동화

같은 뼈대 위에 사례 난이도를 높이며 n8n 노드와 AI 활용 패턴을 체득 — 블룸버그 RSS·월드스틸·다중소스 뉴스레터 3건

n8n을 활용한 리서치 자동화

Learning Objectives
  • n8n의 핵심 노드 사용법(RSS Read, HTTP Request, Code, Set, Filter, Aggregate, Google Sheets, Gmail, AI)을 실제 상황에서 체득
  • 동일한 “수집 → 가공 → 필터 → 저장 → 발송” 뼈대를 세 가지 사례에 응용하며 패턴 학습
  • 막힐 때마다 AI(ChatGPT/Claude)에게 던질 수 있는 실전 프롬프트 패턴 습득

3가지 사례 한눈에 보기

Case 1 — Bloomberg RSS

가장 단순한 뼈대. RSS 한 곳 → 시간 필터 → Google Sheets 적재 → 목록 이메일 발송. 핵심 노드 10개를 차분히 익히는 단계.

Case 2 — World Steel

Case 1의 뼈대를 다른 RSS 소스에 그대로 응용. description 필드 정리 등 소스 특성에 맞춘 미세 조정 포인트 학습.

Case 3 — 다중 소스 + AI

HTTP Request + RSS + Code 노드 조합으로 3개 소스 통합, Merge로 하나로 묶고, OpenAI 노드가 뉴스레터 본문을 생성. 가장 실전에 가까운 형태.

AI 활용 교육의 포인트: 이 교육은 “n8n 사용법 암기”가 아닙니다. 모르는 게 나오면 ChatGPT/Claude에게 어떻게 물어봐야 답을 얻는가를 각 사례의 AI 프롬프트 섹션에서 함께 학습합니다. 실제 AI와 주고받은 대화 중에서 “이 프롬프트로 벽을 뚫었다” 싶은 것들을 선별해 실었습니다.
CASE 1 / 3

Bloomberg RSS로 매일 아침 시장동향 이메일 받기

Bloomberg의 “Commodities” RSS 피드를 가져와, 최근 24시간 기사만 골라 Google Sheets에 축적하고 매일 아침 요약 목록을 Gmail로 발송합니다. n8n 기본기를 다지는 가장 좋은 사례입니다.

데이터 소스Bloomberg RSS
노드 수10개
저장소Google Sheets
발송 채널Gmail

① 비즈니스 상황과 데이터 소스

사업개발 직군에서 매일 아침 글로벌 원자재 시장을 훑어야 하는 담당자가 있다고 상정합니다. 사람이 매번 Bloomberg를 열어 어제 기사를 훑는 대신, n8n이 정해진 시간에 RSS를 당겨 오고, 시간 필터를 걸어 “어제 오전 8시 ~ 오늘 오전 8시”만 남기고, Google Sheets에 축적하면서 Gmail로 목록을 보내 주게 만듭니다.

Bloomberg 워크플로우 전체
그림 1-1. Bloomberg 워크플로우 전체도 — Schedule Trigger부터 Gmail 발송까지
Bloomberg RSS 사이트 확인
그림 1-2. Bloomberg RSS 피드 URL 확인 — 이 URL이 n8n의 RSS Read 노드에 들어갑니다
먼저 할 일 — 데이터 소스 확인: 자동화 노드를 짜기 전에 원본 RSS/웹페이지를 직접 눈으로 열어 보는 단계를 절대 건너뛰지 마십시오. URL이 바뀌었거나 피드가 비었으면 노드에서 아무리 설정을 고쳐도 답이 안 나옵니다.

② 노드별 구성 (총 10개 노드)

NODE 01
RSS Read — Bloomberg 피드 가져오기
n8n-nodes-base.rssFeedRead

Bloomberg RSS 피드 URL: https://feeds.bloomberg.com/markets/news.rss — 이 URL을 노드의 URL 필드에 입력하면 피드 내의 기사 하나당 n8n 아이템 하나로 분리되어 나옵니다. title, link, pubDate, description 등의 필드가 자동으로 추출됩니다.

RSS Read 노드 설정
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "url": "https://feeds.bloomberg.com/markets/news.rss",
        "options": {}
      },
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.2,
      "position": [224, 0],
      "id": "ad16cce4-6f8b-4621-8b8b-514c739d20d5",
      "name": "RSS Read (블룸버그)"
    }
  ],
  "connections": {}
}
NODE 02
Edit Fields — source 필드 추가 (출처 표기)
n8n-nodes-base.set

뒤에서 여러 소스를 합칠 것을 염두에 두고, 지금 단계에서 source = "Bloomberg" 같은 출처 표기 필드를 각 아이템에 붙여 둡니다. Case 2·3에서 데이터가 합쳐질 때 어느 매체에서 온 기사인지 구분할 근거가 됩니다.

Edit Fields 소스 추가
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b9e05ce2-30e6-4ebf-98a6-e593e534d6e7",
              "name": "source",
              "value": "블룸버그",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        448,
        0
      ],
      "id": "5ba65c58-1b5a-4bda-81ab-0289683e159f",
      "name": "Edit Fields (source 기록)"
    }
  ],
  "connections": {}
}
NODE 03
Date & Time — 기사 시간을 KST로 변환
n8n-nodes-base.dateTime

RSS의 pubDate는 UTC/GMT 기준으로 옵니다. 이어지는 필터에서 “한국 시간으로 어제 8시 이후인가?”를 묻기 위해 KST(+09:00)로 변환해 새 필드로 저장합니다.

Date Time KST 변환
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "operation": "formatDate",
        "date": "={{ $('RSS Read (블룸버그)').item.json.pubDate }}",
        "format": "custom",
        "customFormat": "yyyy-MM-dd HH:mm:ss",
        "outputFieldName": "articleDateKST",
        "options": {
          "timezone": true
        }
      },
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 2,
      "position": [
        0,
        208
      ],
      "id": "c60f5ed1-99c2-4ab4-adf0-15cafb6bd8a6",
      "name": "Date & Time (KST 변환)"
    }
  ],
  "connections": {}
}
NODE 04
Edit Fields — 시간 필터링 기준값 계산
n8n-nodes-base.set

“어제 오전 8시” 같은 경계값을 식(expression)으로 미리 계산해 필드로 만들어 둡니다. Filter 노드에서 매 아이템마다 이 기준값과 비교하게 됩니다. 식으로는 n8n의 {{ $now.minus({days:1}).startOf('day').plus({hours:8}) }} 패턴을 주로 사용합니다.

필터 기준 시간 설정
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "da540810-0c95-49c3-94f4-94f5416c4fef",
              "name": "windowStartKST",
              "value": "={{ $now.minus({ days: 1 }).set({ hour: 8, minute: 0, second: 0, millisecond: 0 }).toFormat('yyyy-MM-dd HH:mm:ss') }}",
              "type": "string"
            },
            {
              "id": "89fc7156-596d-423e-ac2c-a9c9fb86361d",
              "name": "windowEndKST",
              "value": "={{ $now.set({ hour: 8, minute: 0, second: 0, millisecond: 0 }).toFormat('yyyy-MM-dd HH:mm:ss') }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        224,
        208
      ],
      "id": "444c5662-2651-4788-aaef-58aaff9507c8",
      "name": "Edit Fields (시간 필터링 기준)"
    }
  ],
  "connections": {}
}
AI 프롬프트
date는 KST로 변환하고, KST 기준으로 어제 아침 8시 이후, 오늘 아침 8시 이전에 해당하는 뉴스만 필터링하자
NODE 05
Filter — 시간 조건으로 기사 걸러내기
n8n-nodes-base.filter

“기사 발행시각이 필터 기준값 이후” 조건을 걸어 어제~오늘 기사만 통과시킵니다. 조건을 만족하지 않는 아이템은 노드의 “Discard” 출력으로 빠져 뒤 단계에 전달되지 않습니다.

Filter 시간 조건
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "ea6179ee-0c93-4544-8fe0-0bba2cb7da6f",
              "leftValue": "={{ $('Date & Time (KST 변환)').item.json.articleDateKST }}",
              "rightValue": "={{ $json.windowStartKST }}",
              "operator": {
                "type": "dateTime",
                "operation": "after"
              }
            },
            {
              "id": "124b24c8-7c07-4ed9-b266-8995324457b1",
              "leftValue": "={{ $('Date & Time (KST 변환)').item.json.articleDateKST }}",
              "rightValue": "={{ $json.windowEndKST }}",
              "operator": {
                "type": "dateTime",
                "operation": "beforeOrEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.3,
      "position": [
        448,
        208
      ],
      "id": "2f7aec37-efad-4465-82c3-224b0f7af57e",
      "name": "Filter (시간 필터링)"
    }
  ],
  "connections": {}
}
NODE 06
Edit Fields — Google Sheets 칼럼명으로 정리
n8n-nodes-base.set

스프레드시트에 넣을 칼럼 순서와 이름을 맞추기 위해 필드를 재정렬합니다. 예: date, source, title, link, description. 이 단계를 건너뛰면 Sheets의 헤더와 아이템 키가 어긋나 적재가 실패합니다.

Edit Fields 칼럼명 지정
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "fa902690-75fd-4bbe-bab8-41fe664a9774",
              "name": "source",
              "value": "={{ $('Edit Fields (source 기록)').item.json.source }}",
              "type": "string"
            },
            {
              "id": "1b815529-08c5-4096-be88-6ea14d97ece8",
              "name": "title",
              "value": "={{ $('RSS Read (블룸버그)').item.json.title }}",
              "type": "string"
            },
            {
              "id": "9f68365a-62af-4d99-9394-bae46aee4708",
              "name": "description",
              "value": "={{ $('RSS Read (블룸버그)').item.json.content }}",
              "type": "string"
            },
            {
              "id": "fbca3bfa-bb26-4b67-b2f5-e54f6a91f702",
              "name": "link",
              "value": "={{ $('RSS Read (블룸버그)').item.json.link }}",
              "type": "string"
            },
            {
              "id": "1bd9e766-4747-49b6-b3ee-043fc7886754",
              "name": "guid",
              "value": "={{ $('RSS Read (블룸버그)').item.json.guid }}",
              "type": "string"
            },
            {
              "id": "d7d401ce-5899-4ba9-8569-adfe50996786",
              "name": "pubDateRaw",
              "value": "={{ $('RSS Read (블룸버그)').item.json.pubDate }}",
              "type": "string"
            },
            {
              "id": "1bd7d061-10fc-4cf7-850e-d4c717d11a7a",
              "name": "articleDateKST",
              "value": "={{ $('Date & Time (KST 변환)').item.json.articleDateKST }}",
              "type": "string"
            },
            {
              "id": "9eb68fc7-b0cf-4aff-8def-ddd709c1639c",
              "name": "author",
              "value": "={{ $('RSS Read (블룸버그)').item.json['dc:creator'] }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        416
      ],
      "id": "a2a5dfc4-687e-4f10-bd06-5a71cce4df7b",
      "name": "Edit Fields (칼럼명 지정)"
    }
  ],
  "connections": {}
}
NODE 07
Google Sheets — DB 준비 (시트 생성 및 헤더)
(사전 준비)

n8n이 쓸 Google Sheets 파일을 미리 만들고 첫 행을 헤더로 채워 둡니다. 노드가 Append 모드로 동작하면 새 기사가 한 행씩 아래에 쌓입니다.

Google Sheets DB 준비
NODE 08
Google Sheets — 계정 연결 (OAuth2)
n8n-nodes-base.googleSheets

n8n에서 Credentials로 Google 계정을 OAuth2 연결합니다. 최초 한 번만 하면 이후 같은 계정의 모든 스프레드시트에 재사용 가능합니다. 회사 계정 사용 시 IT 정책으로 OAuth 차단된 경우는 개인 지메일로 우회하거나 사내 IT에 미리 승인 요청하십시오.

Google Sheets 계정 연결
NODE 09
Google Sheets — 기사 한 행씩 Append
n8n-nodes-base.googleSheets

Operation은 Append Row, 대상은 앞서 만든 DB 파일과 시트를 지정합니다. 아이템 개수만큼 새 행이 생깁니다. 중복 방지가 필요하면 Append or Update로 바꾸고 Key 칼럼(예: link)을 지정하십시오.

Google Sheets 기사 저장 Google Sheets 업데이트 확인
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "1Wf1K7V4BIEJZ7vwV706xwoqCNQcQlkwhjFeOriUAGkU",
          "mode": "list",
          "cachedResultName": "n8n-Bloomberg",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Wf1K7V4BIEJZ7vwV706xwoqCNQcQlkwhjFeOriUAGkU/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "시트1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Wf1K7V4BIEJZ7vwV706xwoqCNQcQlkwhjFeOriUAGkU/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "source": "={{ $json.source }}",
            "title": "={{ $json.title }}",
            "description": "={{ $json.description }}",
            "link": "={{ $json.link }}",
            "guid": "={{ $json.guid }}",
            "pubDateRaw": "={{ $json.pubDateRaw }}",
            "articleDateKST": "={{ $json.articleDateKST }}",
            "author": "={{ $json.author }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "source",
              "displayName": "source",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "title",
              "displayName": "title",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "displayName": "description",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "link",
              "displayName": "link",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "guid",
              "displayName": "guid",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "pubDateRaw",
              "displayName": "pubDateRaw",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "articleDateKST",
              "displayName": "articleDateKST",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "author",
              "displayName": "author",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        224,
        416
      ],
      "id": "064201ec-ee0c-4c57-8ad7-4ac49ffe9c82",
      "name": "Append row in sheet (뉴스 저장)",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "Spx5Q5S3g82eKaQN",
          "name": "hyunsoo-n8n-google-sheets"
        }
      }
    }
  ],
  "connections": {}
}
NODE 10
Aggregate — 여러 기사를 하나의 아이템으로 통합
n8n-nodes-base.aggregate

지금까지는 n8n 관점에서 “기사 1개 = 아이템 1개”였습니다. 이메일 본문에 “10개 기사 전부”를 한 통에 담으려면 아이템을 하나의 배열 필드로 묶어야 합니다. Aggregate 노드가 그 역할을 합니다.

Aggregate 노드
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "include": "specifiedFields",
        "fieldsToInclude": "=source, title, description, link, articleDateKST",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        448,
        416
      ],
      "id": "465018c9-af68-431f-836d-db3fde3adcc3",
      "name": "Aggregate (뉴스를 하나로 통합)"
    }
  ],
  "connections": {}
}
NODE 11
Gmail — 계정 연결 & 뉴스 목록 이메일 발송
n8n-nodes-base.gmail

Gmail도 OAuth2로 한 번 연결해 두면 재사용 가능합니다. 본문은 HTML 또는 텍스트로 작성하는데, Aggregate 결과 배열을 {{ $json.items.map(i => '<li><a href="'+i.link+'">'+i.title+'</a></li>').join('') }} 같은 식으로 HTML <li> 리스트로 렌더링합니다.

Gmail 계정 연결 Gmail 발송 설정
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "sendTo": "hsoo@rnbdp.com",
        "subject": "=[{{ $json.data[0].source || '뉴스' }}] {{$now.setZone('Asia/Seoul').toFormat('yyyy-MM-dd')}} 일간 뉴스 브리핑",
        "emailType": "html",
        "message": "={{ (() => {\n  const articles = Array.isArray($json.data) ? $json.data : [];\n  const source = articles[0]?.source || '뉴스';\n  const today = $now.setZone('Asia/Seoul').toFormat('yyyy-MM-dd');\n\n  const esc = (v) => String(v ?? '')\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;');\n\n  const getUrl = (a) => a.link || a.url || a.originalLink || a.href || '';\n\n  const emptyMessage = `\n    <tr>\n      <td style=\"padding:24px;border:1px solid #e5e7eb;border-radius:14px;background:#ffffff;color:#6b7280;font-size:14px;line-height:1.7;\">\n        수집된 뉴스가 없습니다.\n      </td>\n    </tr>\n  `;\n\n  const topArticles = articles.slice(0, 5);\n\n  const highlightCards = topArticles.length\n    ? topArticles.map((a, i) => {\n        const url = getUrl(a);\n\n        return `\n          <tr>\n            <td style=\"padding:0 0 14px 0;\">\n              <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"border:1px solid #e5e7eb;border-radius:14px;background:#ffffff;\">\n                <tr>\n                  <td style=\"padding:18px 20px;\">\n                    <div style=\"font-size:13px;color:#6b7280;margin-bottom:8px;\">\n                      TOP ${i + 1} · ${esc(a.articleDateKST || '')}\n                    </div>\n\n                    <div style=\"font-size:18px;line-height:1.45;font-weight:700;color:#111827;margin-bottom:8px;\">\n                      ${esc(a.title)}\n                    </div>\n\n                    <div style=\"font-size:14px;line-height:1.7;color:#374151;margin-bottom:14px;\">\n                      ${esc(a.description)}\n                    </div>\n\n                    ${url ? `\n                      <a href=\"${esc(url)}\" style=\"display:inline-block;padding:9px 14px;background:#111827;color:#ffffff;text-decoration:none;border-radius:999px;font-size:13px;font-weight:700;\">\n                        원문 보기\n                      </a>\n                    ` : ''}\n                  </td>\n                </tr>\n              </table>\n            </td>\n          </tr>\n        `;\n      }).join('')\n    : emptyMessage;\n\n  const rows = articles.length\n    ? articles.map((a, i) => {\n        const url = getUrl(a);\n\n        return `\n          <tr>\n            <td style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;color:#6b7280;font-size:13px;text-align:center;\">\n              ${i + 1}\n            </td>\n            <td style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:14px;line-height:1.5;color:#111827;font-weight:700;\">\n              ${esc(a.title)}\n            </td>\n            <td style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:13px;line-height:1.55;color:#4b5563;\">\n              ${esc(a.description)}\n            </td>\n            <td style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#6b7280;white-space:nowrap;\">\n              ${esc(a.articleDateKST)}\n            </td>\n            <td style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:13px;text-align:center;white-space:nowrap;\">\n              ${url ? `<a href=\"${esc(url)}\" style=\"color:#2563eb;text-decoration:none;font-weight:700;\">보기</a>` : ''}\n            </td>\n          </tr>\n        `;\n      }).join('')\n    : `\n      <tr>\n        <td colspan=\"5\" style=\"padding:24px 10px;text-align:center;color:#6b7280;font-size:14px;\">\n          수집된 뉴스가 없습니다.\n        </td>\n      </tr>\n    `;\n\n  return `\n<!doctype html>\n<html>\n  <body style=\"margin:0;padding:0;background:#f3f4f6;font-family:Arial,'Apple SD Gothic Neo','Malgun Gothic',sans-serif;\">\n    <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"background:#f3f4f6;padding:28px 0;\">\n      <tr>\n        <td align=\"center\">\n          <table width=\"720\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"width:720px;max-width:720px;background:#ffffff;border-radius:22px;overflow:hidden;box-shadow:0 8px 28px rgba(15,23,42,0.08);\">\n\n            <tr>\n              <td style=\"padding:34px 36px;background:#111827;color:#ffffff;\">\n                <div style=\"font-size:14px;letter-spacing:0.08em;color:#d1d5db;font-weight:700;margin-bottom:12px;\">\n                  DAILY NEWS BRIEFING\n                </div>\n                <div style=\"font-size:30px;line-height:1.3;font-weight:800;margin-bottom:12px;\">\n                  [${esc(source)}] ${esc(today)}<br>\n                  일간 뉴스 브리핑\n                </div>\n                <div style=\"font-size:14px;line-height:1.6;color:#d1d5db;\">\n                  수집 기준: 어제 오전 8시 ~ 오늘 오전 8시 (KST)<br>\n                  총 ${articles.length}건의 뉴스를 정리했습니다.\n                </div>\n              </td>\n            </tr>\n\n            <tr>\n              <td style=\"padding:26px 36px 8px 36px;\">\n                <div style=\"font-size:15px;line-height:1.7;color:#374151;\">\n                  OOOO팀에서 전해드립니다.<br>\n                  주요 뉴스를 먼저 확인하시고, 하단 전체 목록에서 원문을 확인하실 수 있습니다.\n                </div>\n              </td>\n            </tr>\n\n            <tr>\n              <td style=\"padding:18px 36px 4px 36px;\">\n                <div style=\"font-size:20px;font-weight:800;color:#111827;margin-bottom:14px;\">\n                  핵심 뉴스\n                </div>\n\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">\n                  ${highlightCards}\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td style=\"padding:20px 36px 34px 36px;\">\n                <div style=\"font-size:20px;font-weight:800;color:#111827;margin-bottom:14px;\">\n                  전체 뉴스 목록\n                </div>\n\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"border-collapse:collapse;border:1px solid #e5e7eb;border-radius:14px;overflow:hidden;\">\n                  <thead>\n                    <tr style=\"background:#f9fafb;\">\n                      <th style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#6b7280;text-align:center;width:42px;\">No.</th>\n                      <th style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#6b7280;text-align:left;\">제목</th>\n                      <th style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#6b7280;text-align:left;\">내용</th>\n                      <th style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#6b7280;text-align:left;\">게시시각</th>\n                      <th style=\"padding:12px 10px;border-bottom:1px solid #e5e7eb;font-size:12px;color:#6b7280;text-align:center;\">링크</th>\n                    </tr>\n                  </thead>\n                  <tbody>\n                    ${rows}\n                  </tbody>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td style=\"padding:18px 36px;background:#f9fafb;border-top:1px solid #e5e7eb;\">\n                <div style=\"font-size:12px;line-height:1.6;color:#6b7280;\">\n                  본 메일은 자동 수집된 뉴스 데이터를 기반으로 작성되었습니다.\n                </div>\n              </td>\n            </tr>\n\n          </table>\n        </td>\n      </tr>\n    </table>\n  </body>\n</html>\n  `;\n})() }}",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        672,
        416
      ],
      "id": "278e081a-1e23-4b2b-8af6-26af346eb5d1",
      "name": "Send a message (뉴스 목록 발송)",
      "webhookId": "7c9b29a0-f603-4400-95fe-e7b8a3e013fc",
      "credentials": {
        "gmailOAuth2": {
          "id": "hhw5240RFQieWxBf",
          "name": "hyunsoo-n8n-Gmail"
        }
      }
    }
  ],
  "connections": {}
}
AI 프롬프트 — 이메일 제목·본문 설계
Gmail 노드에 넣을 이메일 제목과 HTML 본문을 만들어 줘. [앞 노드(Aggregate) 출력 데이터] - data: 오늘 수집된 기사 배열 - 각 항목 필드: source(매체명), title, description, articleDateKST, link [이메일 제목] - 형식: "[출처명] YYYY-MM-DD 일간 뉴스 브리핑" - 오늘 날짜는 $now로 자동 생성 [이메일 본문 (HTML)] 1. 상단 헤더 — 뉴스레터 제목 + 발신자 소개 2. 수집 기준 안내 — 예: "어제 오전 8시 ~ 오늘 오전 8시 (KST)" 3. 기사 테이블 — 칼럼: 미디어 / 제목 / 내용 / 게시 시각(KST) / 원문 링크 [디자인 요구사항] - 애플 감성의 미니멀·전문적인 뉴스레터 룩 - 인라인 CSS만 사용 (Gmail HTML 호환) - 매번 자동 발송 시 레이아웃·폰트·색상은 고정, 데이터 부분만 교체 [발신인 표시] - "한국GPT협회 안현수" 결과는 Gmail 노드의 Subject와 Message 필드에 각각 바로 붙여넣을 수 있는 형태로 줘.
발송된 이메일 결과 확인
그림 1-3. 최종 발송된 이메일 — 시트에 쌓인 오늘의 Bloomberg 기사 목록이 그대로 본문에 담겼습니다
CASE 2 / 3

World Steel 뉴스로 동일 패턴 응용하기

Case 1에서 만든 “RSS → 필터 → Sheets → Gmail” 뼈대를 다른 소스(World Steel Association)에 그대로 붙입니다. 구조는 같지만 소스마다 있는 미세한 차이(description 필드 정리, 시간 표기, 인코딩)를 어떻게 맞추는지가 핵심 학습 포인트입니다.

데이터 소스World Steel RSS
노드 수11개
Case 1 대비description 정리 추가
학습 초점패턴 재사용

① Case 1과의 차이점

같은 뼈대이지만, World Steel 피드는 Bloomberg보다 description 필드에 HTML 태그와 불필요한 공백이 섞여 있습니다. 그래서 노드가 하나 더 붙습니다 — “Edit Fields (description 정리)”. 또 시간 필드 포맷도 미묘하게 다를 수 있어 Date & Time 노드에서 포맷 지정이 필요할 수 있습니다.

World Steel 워크플로우 전체
그림 2-1. World Steel 워크플로우 전체도 — Case 1 대비 description 정리 노드가 추가됐습니다
World Steel RSS 확인
그림 2-2. World Steel Association의 뉴스 RSS 확인

② 노드별 구성 (총 11개)

NODE 01
RSS Read — World Steel 피드
n8n-nodes-base.rssFeedRead

World Steel Association RSS 피드 URL: https://worldsteel.org/feed/ — URL만 Case 1의 Bloomberg에서 World Steel로 바꿔 줍니다. 노드 자체의 설정은 완전히 동일합니다.

RSS Read WorldSteel
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "url": "https://worldsteel.org/feed/",
        "options": {}
      },
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.2,
      "position": [
        224,
        0
      ],
      "id": "f3f3286b-334a-4277-91ea-763a79e415d0",
      "name": "RSS Read (웓드스틸)"
    }
  ],
  "connections": {}
}
NODE 02
Edit Fields — description 정리 (소스 특성 대응)
n8n-nodes-base.set

Case 2에서 새로 추가된 노드. World Steel의 description에는 HTML 태그(<p>, <br>)와 줄바꿈 공백이 섞여 있습니다. .replace(/<[^>]+>/g, '')로 태그를 제거하고 공백을 정돈해 “읽을 수 있는 요약문”으로 만듭니다.

description 정리
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1725d5d5-e755-4767-a398-9db7a3608f4e",
              "name": "cleanDescription",
              "value": "={{\n  $json.content\n    // 1. HTML 제거\n    .replace(/<[^>]+>/g, ' ')\n    \n    // 2. \"The post ... appeared first on ...\" 제거 (줄 단위 제거)\n    .replace(/The post .*?appeared first on .*?\\./g, '')\n    \n    // 3. 줄바꿈 기준으로 나누기\n    .split('\\n')\n    \n    // 4. 앞뒤 공백 제거 + 빈 줄 제거\n    .map(s => s.trim())\n    .filter(s => s.length > 30)\n    \n    // 5. 다시 합치기\n    .join('\\n\\n')\n}}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        448,
        0
      ],
      "id": "e7c8ae35-30c4-4dbd-9eca-70e886aac019",
      "name": "Edit Fields (description 정리)"
    }
  ],
  "connections": {}
}
NODE 03
Edit Fields — source 기록
n8n-nodes-base.set

source = "World Steel". Case 1과 완전히 동일한 패턴입니다.

source 기록
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b9e05ce2-30e6-4ebf-98a6-e593e534d6e7",
              "name": "source",
              "value": "월드스틸",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        672,
        0
      ],
      "id": "5a0f2d9c-4cf4-4862-929a-e25595ff8479",
      "name": "Edit Fields (source 기록)"
    }
  ],
  "connections": {}
}
NODE 04
Date & Time — KST 변환
n8n-nodes-base.dateTime

Case 1과 동일. 세계 시장 뉴스이므로 원본이 UTC일 확률이 높습니다.

DateTime KST
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "operation": "formatDate",
        "date": "={{ $('RSS Read (웓드스틸)').item.json.pubDate }}",
        "format": "custom",
        "customFormat": "yyyy-MM-dd HH:mm:ss",
        "outputFieldName": "articleDateKST",
        "options": {
          "timezone": true
        }
      },
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 2,
      "position": [
        0,
        272
      ],
      "id": "e9f43157-c80f-4af7-a937-d6ab26e26614",
      "name": "Date & Time (KST 변환)"
    }
  ],
  "connections": {}
}
NODE 05
Edit Fields — 시간 필터링 기준
n8n-nodes-base.set
시간 필터 기준
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "da540810-0c95-49c3-94f4-94f5416c4fef",
              "name": "windowStartKST",
              "value": "={{ $now.minus({ months: 1 }).set({ hour: 8, minute: 0, second: 0, millisecond: 0 }).toFormat('yyyy-MM-dd HH:mm:ss') }}",
              "type": "string"
            },
            {
              "id": "89fc7156-596d-423e-ac2c-a9c9fb86361d",
              "name": "windowEndKST",
              "value": "={{ $now.set({ hour: 8, minute: 0, second: 0, millisecond: 0 }).toFormat('yyyy-MM-dd HH:mm:ss') }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        224,
        272
      ],
      "id": "9d674cb1-5585-4692-912e-2092888a2c96",
      "name": "Edit Fields (시간 필터링 기준)"
    }
  ],
  "connections": {}
}
NODE 06
Filter — 시간 조건
n8n-nodes-base.filter
Filter 시간 조건
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "ea6179ee-0c93-4544-8fe0-0bba2cb7da6f",
              "leftValue": "={{ $('Date & Time (KST 변환)').item.json.articleDateKST }}",
              "rightValue": "={{ $json.windowStartKST }}",
              "operator": {
                "type": "dateTime",
                "operation": "after"
              }
            },
            {
              "id": "124b24c8-7c07-4ed9-b266-8995324457b1",
              "leftValue": "={{ $('Date & Time (KST 변환)').item.json.articleDateKST }}",
              "rightValue": "={{ $json.windowEndKST }}",
              "operator": {
                "type": "dateTime",
                "operation": "beforeOrEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.3,
      "position": [
        448,
        272
      ],
      "id": "1a32dfa6-ee6c-4aff-b70b-d6dc53e81efe",
      "name": "Filter (시간 필터링)"
    }
  ],
  "connections": {}
}
NODE 07
Edit Fields — Sheets 칼럼명 정리
n8n-nodes-base.set
칼럼명 지정
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "fa902690-75fd-4bbe-bab8-41fe664a9774",
              "name": "source",
              "value": "={{ $('Edit Fields (source 기록)').item.json.source }}",
              "type": "string"
            },
            {
              "id": "1b815529-08c5-4096-be88-6ea14d97ece8",
              "name": "title",
              "value": "={{ $('RSS Read (웓드스틸)').item.json.title }}",
              "type": "string"
            },
            {
              "id": "9f68365a-62af-4d99-9394-bae46aee4708",
              "name": "description",
              "value": "={{ $('RSS Read (웓드스틸)').item.json.content }}",
              "type": "string"
            },
            {
              "id": "fbca3bfa-bb26-4b67-b2f5-e54f6a91f702",
              "name": "link",
              "value": "={{ $('RSS Read (웓드스틸)').item.json.link }}",
              "type": "string"
            },
            {
              "id": "1bd9e766-4747-49b6-b3ee-043fc7886754",
              "name": "guid",
              "value": "={{ $('RSS Read (웓드스틸)').item.json.guid }}",
              "type": "string"
            },
            {
              "id": "d7d401ce-5899-4ba9-8569-adfe50996786",
              "name": "pubDateRaw",
              "value": "={{ $('RSS Read (웓드스틸)').item.json.pubDate }}",
              "type": "string"
            },
            {
              "id": "1bd7d061-10fc-4cf7-850e-d4c717d11a7a",
              "name": "articleDateKST",
              "value": "={{ $('Date & Time (KST 변환)').item.json.articleDateKST }}",
              "type": "string"
            },
            {
              "id": "9eb68fc7-b0cf-4aff-8def-ddd709c1639c",
              "name": "author",
              "value": "={{ $('RSS Read (웓드스틸)').item.json['dc:creator'] }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        512
      ],
      "id": "5237146e-fb72-4026-a47f-47d1f6e12cb9",
      "name": "Edit Fields (칼럼명 지정)"
    }
  ],
  "connections": {}
}
NODE 08
Google Sheets — DB 준비 & Append
n8n-nodes-base.googleSheets

Case 1과 동일한 Sheets 파일을 그대로 재사용하는 것이 실무 팁입니다. source 칼럼으로 두 소스를 구분하면 한 시트 안에 Bloomberg와 World Steel이 나란히 쌓입니다.

Sheets DB 준비 Sheets 저장 Sheets 업데이트 확인
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "1ryecVr-NayL022nKWz6EH1y2atGkAMH1HI7kWzsHH58",
          "mode": "list",
          "cachedResultName": "n8n-WorldSteel",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ryecVr-NayL022nKWz6EH1y2atGkAMH1HI7kWzsHH58/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "news",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ryecVr-NayL022nKWz6EH1y2atGkAMH1HI7kWzsHH58/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "source": "={{ $json.source }}",
            "title": "={{ $json.title }}",
            "link": "={{ $json.link }}",
            "pubDateRaw": "={{ $json.pubDateRaw }}",
            "articleDateKST": "={{ $json.articleDateKST }}",
            "guid": "={{ $json.guid }}",
            "description": "={{ $('Edit Fields (description 정리)').item.json.cleanDescription }}",
            "author": "={{ $json.author }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "source",
              "displayName": "source",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "title",
              "displayName": "title",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "displayName": "description",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "link",
              "displayName": "link",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "guid",
              "displayName": "guid",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "pubDateRaw",
              "displayName": "pubDateRaw",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "articleDateKST",
              "displayName": "articleDateKST",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "author",
              "displayName": "author",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        224,
        512
      ],
      "id": "dff5b979-f353-4d49-bca9-7b95c795f0f2",
      "name": "Append row in sheet (기사 저장)",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "Spx5Q5S3g82eKaQN",
          "name": "hyunsoo-n8n-google-sheets"
        }
      }
    }
  ],
  "connections": {}
}
NODE 09
Aggregate — 하나의 아이템으로 통합
n8n-nodes-base.aggregate
Aggregate
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "include": "specifiedFields",
        "fieldsToInclude": "=source, title, cleanDescription, link, articleDateKST",
        "options": {}
      },
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        448,
        512
      ],
      "id": "ae578a2f-f31e-4b83-8927-01dd6a1ace0c",
      "name": "Aggregate(뉴스를하나로통합)"
    }
  ],
  "connections": {}
}
NODE 10
Gmail — 이메일 발송
n8n-nodes-base.gmail
Gmail 발송 World Steel 뉴스레터 결과
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "sendTo": "hsoo@rnbdp.com",
        "subject": "=[{{ $json.data[0].source }}] {{$now.format('yyyy-MM-dd')}} 일간 뉴스 브리핑",
        "message": "=OOOO팀에서 전해드립니다.\n\n
[{{ $json.data[0].source }}] {{$now.format('yyyy-MM-dd')}} 일간 뉴스입니다.
\n\n\n{{ `

수집 기준: 어제 오전 8시 ~ 오늘 오전 8시 (KST)

\n\n\n${ $json.data.map(a => ` `).join('') }\n
미디어 제목 내용 게시시각(KST) 원문 링크
${a.source || ''} ${a.title || ''} ${a.description || ''} ${a.articleDateKST || ''} 기사 보기
` }}", "options": {} }, "type": "n8n-nodes-base.gmail", "typeVersion": 2.2, "position": [ 672, 512 ], "id": "2bedec9f-420b-4896-8ab2-4a45650b3d48", "name": "Send a message", "webhookId": "268a1ec1-5cba-487b-b3a3-95805aa45030", "credentials": { "gmailOAuth2": { "id": "hhw5240RFQieWxBf", "name": "hyunsoo-n8n-Gmail" } } } ], "connections": {} }
CASE 3 / 3

3개 소스 통합 + AI가 뉴스레터 본문까지 작성

Case 1·2의 “수집-저장-발송” 뼈대를 확장합니다. Trading Economics(뉴스 API) + Investing.com(RSS) + 네이버증권 원자재(HTML 크롤) 3개 소스를 HTTP Request/RSS Read/Code 노드로 각각 수집한 뒤 Merge로 합치고, 마지막에 OpenAI “Message a model” 노드가 뉴스레터 본문을 직접 써 줍니다. 실무에서 쓸 수 있는 가장 현실적인 형태입니다.

데이터 소스3종 (API + RSS + HTML)
노드 수13개
AI 통합OpenAI Message a model
발송디자인된 HTML 뉴스레터

① 복합 아키텍처의 이해

Case 3는 앞 두 사례와 달리 3개의 병렬 브랜치로 시작합니다. 각 브랜치가 다른 형태의 소스를 읽어 오기 때문에 필요한 노드도 다릅니다:

브랜치 A — 네이버증권 원자재

HTTP RequestCode (HTML 파싱) → Aggregate. 페이지 HTML에서 원자재 가격표를 직접 스크래핑합니다.

브랜치 B — Trading Economics

HTTP Request (API 호출) → Code (JSON 정리). API 키를 Header에 붙여 뉴스 데이터를 받아 옵니다.

브랜치 C — Investing.com

RSS ReadAggregate. 가장 단순한 RSS 수집 패턴.

세 브랜치의 결과를 Merge 노드로 합치고, Code 노드에서 하나의 JSON 구조로 정리한 뒤, Message a model(OpenAI)에게 “이걸로 뉴스레터 써줘”라고 넘깁니다. 마지막 Code 노드는 뉴스레터 디자인(HTML) 템플릿에 AI 텍스트를 끼워 넣는 역할을 합니다.

종합 워크플로우 전체
그림 3-1. Case 3 워크플로우 전체도 — 3개 병렬 브랜치가 Merge로 합쳐지고, OpenAI 노드를 거쳐 Gmail로 발송됩니다

② 노드별 구성 (총 13개)

브랜치 A — 네이버증권 원자재 가격
네이버증권 원자재 사이트
그림 3-2. 네이버증권 원자재 페이지 — HTML 구조 확인
NODE A-1
HTTP Request — 네이버증권 HTML 받기
n8n-nodes-base.httpRequest

네이버증권 원자재 페이지 URL: https://m.stock.naver.com/marketindex/home/metals. Method GET, 대상 URL을 넣으면 전체 HTML이 data 필드에 문자열로 들어옵니다. User-Agent 헤더를 브라우저처럼 설정하지 않으면 차단되는 사이트가 많으므로 Header Parameters에서 추가해 두십시오.

HTTP Request 네이버증권
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "url": "https://m.stock.naver.com/marketindex/home/metals",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        224,
        -192
      ],
      "id": "e1a1f0f1-2413-493e-8815-ccf87ce74c8b",
      "name": "HTTP Request(Npay-metal-prices)"
    }
  ],
  "connections": {}
}
NODE A-2
Code — HTML에서 가격표 추출
n8n-nodes-base.code

JavaScript로 받은 HTML을 파싱합니다. cheerio를 쓰거나 정규식으로 <tr>...</tr>만 뽑아 품목명/가격/변동률을 꺼냅니다. 사이트 구조가 바뀌면 이 노드가 가장 먼저 깨지므로 에러 처리와 최소한의 로그를 넣는 것이 실무 팁입니다.

Code 가격표 추출
Code 보기
n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
{
  "nodes": [
    {
      "parameters": {
        "jsCode": "const html = $json.body ?? $json.data ?? $json.html ?? '';\n\nif (!html || typeof html !== 'string') {\n  throw new Error('HTML 문자열이 없습니다.');\n}\n\nfunction stripTags(s) {\n  return s\n    .replace(/<[^>]+>/g, ' ')\n    .replace(/ /g, ' ')\n    .replace(/\\s+/g, ' ')\n    .trim();\n}\n\nfunction toNumber(s) {\n  return Number(String(s).replace(/,/g, '').trim());\n}\n\nconst results = [];\n\n// 상단 4개\nconst mainListRegex = /
  • ([\\s\\S]*?)<\\/li>/g;\nfor (const m of html.matchAll(mainListRegex)) {\n const block = m[1];\n\n const name = stripTags((block.match(/([\\s\\S]*?)<\\/strong>/) || [])[1] || '');\n const current = stripTags((block.match(/([\\s\\S]*?)<\\/span>/) || [])[1] || '');\n\n const flucts = [...block.matchAll(/([\\s\\S]*?)<\\/span>/g)]\n .map(x => stripTags(x[1]));\n\n if (name && current && flucts.length >= 2) {\n results.push({\n name,\n current: toNumber(current),\n change: toNumber(flucts[0].replace(/[^\\d+.-]/g, '')),\n changeRate: Number(flucts[1].replace('%', '').replace(/\\s+/g, '')),\n });\n }\n}\n\n// 하단 9개\nconst tableRegex = /([\\s\\S]*?)<\\/tr>/g;\nfor (const m of html.matchAll(tableRegex)) {\n const block = m[1];\n\n const tds = [...block.matchAll(/([\\s\\S]*?)<\\/td>/g)].map(x => x[1]);\n if (tds.length < 3) continue;\n\n const name = stripTags((tds[0].match(/([\\s\\S]*?)<\\/span>/) || [])[1] || '');\n const current = stripTags((tds[1].match(/^([\\s\\S]*?)
    ([\\s\\S]*?)<\\/span>/) || [])[1] || '');\n const changeRate = stripTags((tds[2].match(/([\\s\\S]*?)<\\/span>/) || [])[1] || '');\n\n if (name && current) {\n results.push({\n name,\n current: toNumber(current),\n change: toNumber(change.replace(/[^\\d+.-]/g, '')),\n changeRate: Number(changeRate.replace('%', '').replace(/\\s+/g, '')),\n });\n }\n}\n\nreturn results.map(item => ({ json: item }));" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 448, -192 ], "id": "cc4e64ab-15c7-4ea8-9cf2-9b9414917df3", "name": "Code in JavaScript(Prices 추출)" } ], "connections": {} }
  • NODE A-3
    Aggregate — 가격 리스트를 하나로
    n8n-nodes-base.aggregate
    Aggregate 가격
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "aggregate": "aggregateAllItemData",
            "destinationFieldName": "metal_prices",
            "options": {}
          },
          "type": "n8n-nodes-base.aggregate",
          "typeVersion": 1,
          "position": [
            672,
            -192
          ],
          "id": "ea147611-cae1-440a-ae5f-ab9fbcde4719",
          "name": "Aggregate(Npay)"
        }
      ],
      "connections": {}
    }
    브랜치 B — Trading Economics 뉴스 API
    Trading Economics 뉴스
    그림 3-3. Trading Economics 뉴스 화면 — 이 데이터를 API로 받아 옵니다
    Trading Economics 개발자 문서
    그림 3-4. API 키 발급 화면 — Developer portal에서 무료 티어 키 발급
    실전 주의: Trading Economics 무료 티어는 4개 국가(멕시코·뉴질랜드·스웨덴·태국)로 데이터가 제한됩니다. 교육 용도로는 괜찮지만 실제 업무 배포 시에는 유료 플랜 필요성을 미리 확인하십시오. 이 제약은 문서 첫 줄에 없고 실제로 데이터를 받아 봐야 드러납니다.
    NODE B-1
    HTTP Request — Trading Economics 뉴스 API
    n8n-nodes-base.httpRequest

    Trading Economics News API. 엔드포인트: https://api.tradingeconomics.com/news. API 키 발급: https://developer.tradingeconomics.com/Home/Keys (회원가입 후 Home → Keys 메뉴). Query Parameters에 c=<apikey>f=json을 추가. 전체 URL 예시: https://api.tradingeconomics.com/news?c=<APIKEY>&f=json.

    HTTP Request Trading Economics
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "url": "https://api.tradingeconomics.com/news?c=91c4a0cf33cf422:9ai3os1z1u6dwqv&f=json",
            "options": {
              "response": {
                "response": {
                  "responseFormat": "json"
                }
              },
              "timeout": 15000
            }
          },
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.4,
          "position": [
            448,
            0
          ],
          "id": "f4b25dee-006f-474e-a493-c6d06bb42f1c",
          "name": "HTTP Request(TradingEconomics-news)"
        }
      ],
      "connections": {}
    }
    NODE B-2
    Code — Trading Economics 결과 정리
    n8n-nodes-base.code

    응답 JSON에서 우리가 쓸 필드만 추려 {title, link, date, country, source:'TE'} 형태로 정규화합니다. 다른 브랜치들과 합칠 때 필드명이 맞아야 Merge가 깔끔합니다.

    Code TE 정리
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "jsCode": "function formatKST(date) {\n  return new Intl.DateTimeFormat('sv-SE', {\n    timeZone: 'Asia/Seoul',\n    year: 'numeric',\n    month: '2-digit',\n    day: '2-digit',\n    hour: '2-digit',\n    minute: '2-digit',\n    second: '2-digit',\n    hour12: false,\n  }).format(date).replace(' ', 'T');\n}\n\nconst now = new Date();\n\n// KST 기준 오늘 08:00\nconst nowKST = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Seoul' }));\nconst today8KST = new Date(nowKST);\ntoday8KST.setHours(8, 0, 0, 0);\n\n// 종료: 오늘 08:00 이전이면 어제 08:00, 아니면 오늘 08:00\nconst endKST = nowKST < today8KST\n  ? new Date(today8KST.getTime() - 24 * 60 * 60 * 1000)\n  : today8KST;\n\n// 시작: 1주일 전 08:00\nconst startKST = new Date(endKST.getTime() - 7 * 24 * 60 * 60 * 1000);\n\n// 비교용 UTC 시각\nconst startUTC = new Date(startKST.getTime() - 9 * 60 * 60 * 1000);\nconst endUTC = new Date(endKST.getTime() - 9 * 60 * 60 * 1000);\n\nconst tradingEconomicsNews = items\n  .map(i => i.json)\n  .filter(n => {\n    const d = new Date(n.date);\n    return !isNaN(d) && d >= startUTC && d < endUTC;\n  })\n  .map(n => {\n    const d = new Date(n.date);\n    return {\n      title: n.title,\n      date_utc: n.date,\n      date_kst: formatKST(d),\n      summary: n.description,\n      url: 'https://tradingeconomics.com' + n.url,\n      country: n.country,\n      category: n.category,\n      symbol: n.symbol,\n    };\n  });\n\nreturn [\n  {\n    json: {\n      'trading_economics_news': tradingEconomicsNews,\n    },\n  },\n];"
          },
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [
            672,
            0
          ],
          "id": "c6090a5b-0cd8-4fce-b8fb-e41fceb693e2",
          "name": "Code in JavaScript(뉴스 정리)"
        }
      ],
      "connections": {}
    }
    브랜치 C — Investing.com RSS
    Investing.com
    그림 3-5. Investing.com Commodities 섹션 — RSS로 받습니다
    NODE C-1
    RSS Read — Investing.com
    n8n-nodes-base.rssFeedRead

    Investing.com Commodities News RSS 피드 URL: https://www.investing.com/rss/news_11.rss. Bloomberg/World Steel과 동일한 RSS Read 노드 사용.

    RSS Read Investing
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "url": "https://www.investing.com/rss/news_11.rss",
            "options": {}
          },
          "type": "n8n-nodes-base.rssFeedRead",
          "typeVersion": 1.2,
          "position": [
            448,
            192
          ],
          "id": "61524e0f-3597-4ef2-b328-ee019e5b855f",
          "name": "RSS Read(investing.com-news)"
        }
      ],
      "connections": {}
    }
    NODE C-2
    Aggregate — Investing.com 기사 묶기
    n8n-nodes-base.aggregate
    Aggregate Investing
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "aggregate": "aggregateAllItemData",
            "destinationFieldName": "investing_com_news",
            "options": {}
          },
          "type": "n8n-nodes-base.aggregate",
          "typeVersion": 1,
          "position": [
            672,
            192
          ],
          "id": "2e00b35a-559a-4ede-bbab-31bc882c5f81",
          "name": "Aggregate(Investing.com)"
        }
      ],
      "connections": {}
    }
    3개 브랜치 합치기 & AI 뉴스레터 생성
    NODE M
    Merge — 3개 브랜치를 하나로
    n8n-nodes-base.merge

    Merge 노드의 Mode는 Combine으로 설정. 세 브랜치의 결과가 하나의 데이터 스트림으로 합쳐집니다. 각 브랜치가 source 필드를 다르게 갖고 있어서 어디서 온 데이터인지 구분됩니다.

    Merge 노드
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "numberInputs": 3
          },
          "type": "n8n-nodes-base.merge",
          "typeVersion": 3.2,
          "position": [
            896,
            144
          ],
          "id": "e4d1aba8-f7dc-42e4-a641-01868eae72b7",
          "name": "Merge(3개의출처를하나로통합)"
        }
      ],
      "connections": {}
    }
    NODE D
    Code — 3개 데이터를 하나의 JSON으로 재구성
    n8n-nodes-base.code

    Merge 결과를 AI가 이해하기 좋은 형태로 재구성합니다. 예: {prices: [...], news: [...], generated_at: '...'} 같은 구조. 이 JSON이 다음 단계 AI 프롬프트에 그대로 들어갑니다.

    Code 통합
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "jsCode": "const result = {\n  trading_economics_news: [],\n  investing_com_news: [],\n  metal_prices: [],\n};\n\nfor (const item of items) {\n  const j = item.json ?? {};\n\n  if (Array.isArray(j.trading_economics_news)) {\n    result.trading_economics_news = j.trading_economics_news;\n    continue;\n  }\n\n  if (Array.isArray(j.investing_com_news)) {\n    result.investing_com_news = j.investing_com_news;\n    continue;\n  }\n\n  if (Array.isArray(j.metal_prices)) {\n    result.metal_prices = j.metal_prices;\n    continue;\n  }\n\n  // Aggregate 등으로 data에 들어온 경우 대비\n  if (Array.isArray(j.data) && j.data.length > 0) {\n    const first = j.data[0];\n\n    if (first.title && (first.link || first.url || first.isoDate || first.pubDate)) {\n      result.investing_com_news = j.data;\n      continue;\n    }\n\n    if (first.name && first.current !== undefined) {\n      result.metal_prices = j.data;\n      continue;\n    }\n  }\n}\n\nreturn [\n  {\n    json: result,\n  },\n];"
          },
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [
            224,
            464
          ],
          "id": "022daf7d-b2eb-4dc4-83ec-a943ad69c880",
          "name": "Code in JavaScript(데이터통합)"
        }
      ],
      "connections": {}
    }
    NODE AI
    Message a model — OpenAI가 뉴스레터 본문 작성
    @n8n/n8n-nodes-langchain.openAi

    모델: GPT-4o 또는 GPT-4o-mini. 시스템 프롬프트에 “당신은 원자재 시장 뉴스레터 편집자입니다. 주어진 가격 데이터와 뉴스를 받아 한국 사업개발 담당자용 일일 브리프를 작성합니다” 같은 역할을 부여하고, 사용자 메시지에 앞 Code 노드의 JSON을 넘깁니다.

    Message a model
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "modelId": {
              "__rl": true,
              "value": "gpt-5-mini",
              "mode": "list",
              "cachedResultName": "GPT-5-MINI"
            },
            "responses": {
              "values": [
                {
                  "role": "system",
                  "content": "=너는 현대코퍼레이션(종합상사) 임직원을 위한 원자재 시장 전문 애널리스트다.\n\n아래 데이터를 기반으로 “원자재 데일리 뉴스레터”를 작성하라.\n\n[작성 목적]\n- 구매팀, 트레이딩팀, 경영진이 빠르게 시장 상황을 이해\n- 실무 의사결정에 도움 (가격 방향성, 리스크, 기회)\n\n[입력 데이터]\n1) Naver 증권 금속 가격 (당일)\n2) Trading Economics 뉴스 (최근 1주일)\n3) Investing.com 뉴스 (최근 1일)\n\n\n---\n\n[작성 규칙]\n\n1. 전체 톤\n- 전문적, 간결, 보고서 스타일\n- 불필요한 수식어 금지\n- 핵심만 전달 (실무용)\n\n2. 구조 (반드시 지켜라)\n\n① 오늘의 핵심 요약 (5줄 이내)\n- 시장 전체 방향 (상승/하락/혼조)\n- 주요 원인 (거시/지정학/수급)\n\n② 주요 원자재 가격 동향\n- 금, 은, 구리, 철광석, 니켈 중심으로 요약\n- 상승/하락 이유를 뉴스와 연결해서 설명\n- 숫자 그대로 나열하지 말고 “의미” 중심\n\n③ 주요 뉴스 TOP 5\n- 각 뉴스 2~3줄 요약\n- 시장 영향까지 포함\n\n④ 실무 인사이트 (가장 중요)\n- 구매팀 관점 리스크 3개\n- 기회 요인 2개\n- 가격 방향성 (단기 전망)\n\n⑤ 경영진 요약 (200자 이내)\n- 가장 중요한 포인트만 압축\n\n---\n\n[중요 규칙]\n\n- 가격 데이터 + 뉴스 내용을 반드시 연결해서 해석\n- 단순 요약 금지 (→ 반드시 “왜 중요한지” 포함)\n- 원자재 이름은 한국어 기준 사용 (구리, 니켈 등)\n- 불확실하면 추측하지 말고 생략\n- 리스트는 가독성 좋게 정리\n\n---\n\n[출력 형식]\n\n마크다운 형식으로 출력:\n\n## 📊 오늘의 원자재 시장 브리프\n\n### 1. 핵심 요약\n...\n\n### 2. 가격 동향\n...\n\n### 3. 주요 뉴스\n...\n\n### 4. 실무 인사이트\n...\n\n### 5. 경영진 요약\n..."
                },
                {
                  "content": "=---\n\n# 데이터:\n\n{{ JSON.stringify($json) }}\n\n---\n"
                }
              ]
            },
            "builtInTools": {},
            "options": {}
          },
          "type": "@n8n/n8n-nodes-langchain.openAi",
          "typeVersion": 2.1,
          "position": [
            448,
            464
          ],
          "id": "a5fece50-a0c5-4b15-9e46-df7551e22967",
          "name": "Message a model(뉴스레터 본문 생성)",
          "credentials": {
            "openAiApi": {
              "id": "kVJMpgD7P9ktluTg",
              "name": "PTC에이전트"
            }
          }
        }
      ],
      "connections": {}
    }
    NODE E
    Code — 뉴스레터 HTML 디자인 생성
    n8n-nodes-base.code

    AI가 만든 본문 텍스트를 회사 색/폰트 스타일이 적용된 HTML 템플릿에 끼워 넣습니다. 여기서는 순수 JavaScript 템플릿 리터럴로 처리합니다.

    Code 뉴스레터 디자인
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "jsCode": "function escapeHtml(str) {\n  return String(str ?? '')\n    .replace(/&/g, '&')\n    .replace(//g, '>')\n    .replace(/\"/g, '"')\n    .replace(/'/g, ''');\n}\n\nfunction nl2br(str) {\n  return escapeHtml(str).replace(/\\n/g, '
    ');\n}\n\nfunction formatNumber(v) {\n const num = Number(v);\n if (Number.isNaN(num)) return escapeHtml(v);\n return num.toLocaleString('en-US');\n}\n\nfunction formatChange(v) {\n const num = Number(v);\n if (Number.isNaN(num)) return escapeHtml(v);\n if (num > 0) return `+${num.toLocaleString('en-US')}`;\n return num.toLocaleString('en-US');\n}\n\nfunction formatRate(v) {\n const num = Number(v);\n if (Number.isNaN(num)) return escapeHtml(v);\n if (num > 0) return `+${num}%`;\n return `${num}%`;\n}\n\nfunction changeColor(v) {\n const num = Number(v);\n if (num > 0) return '#d92d20';\n if (num < 0) return '#2563eb';\n return '#6b7280';\n}\n\n// 이전 노드 데이터 가져오기\nconst unified = $('Code in JavaScript(데이터통합)').first().json;\nconst ai = $('Message a model(뉴스레터 본문 생성)').first().json;\n\n// AI 텍스트 추출\nconst aiNode = $('Message a model(뉴스레터 본문 생성)').first().json;\n\n// n8n OpenAI 노드 실제 구조 대응\nconst aiText =\n aiNode.output_text ||\n aiNode.output?.[0]?.content?.[0]?.text ||\n aiNode.output?.[0]?.content?.[0]?.content ||\n aiNode.data?.[0]?.content?.[0]?.text ||\n aiNode.choices?.[0]?.message?.content ||\n JSON.stringify(aiNode, null, 2);\n\n// 데이터\nconst metalPrices = unified.metal_prices || [];\nconst teNews = unified.trading_economics_news || [];\nconst investingNews = unified.investing_com_news || [];\n\n// 뉴스 합치기\nconst allNews = [\n ...teNews.map((n) => ({\n source: 'Trading Economics',\n title: n.title || '',\n url: n.url || '',\n })),\n ...investingNews.map((n) => ({\n source: 'Investing.com',\n title: n.title || '',\n url: n.link || n.url || '',\n })),\n];\n\n// 가격 테이블 rows\nconst priceRows = metalPrices.map((item, idx) => {\n const color = changeColor(item.changeRate);\n const border = idx === metalPrices.length - 1\n ? ''\n : 'border-bottom:1px solid #eef0f3;';\n\n return `\n \n \n ${escapeHtml(item.name)}\n \n \n ${formatNumber(item.current)}\n \n \n ${formatChange(item.change)}\n \n \n ${formatRate(item.changeRate)}\n \n \n `;\n}).join('');\n\n// 뉴스 목록\nconst newsRows = allNews.map((item, idx) => {\n const border = idx === allNews.length - 1\n ? ''\n : 'border-bottom:1px solid #eef0f3;';\n\n return `\n \n \n
    \n ${escapeHtml(item.source)}\n
    \n \n ${escapeHtml(item.title)}\n \n \n \n `;\n}).join('');\n\n// 날짜\nconst now = new Date();\nconst dateText = new Intl.DateTimeFormat('ko-KR', {\n timeZone: 'Asia/Seoul',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n}).format(now);\n\nconst subject = `[원자재 데일리] ${dateText} 브리프`;\n\nconst html = `\n\n\n\n \n \n ${escapeHtml(subject)}\n\n\n \n \n \n \n
    \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n\n
    \n
    한국GPT협회 안현수
    \n
    \n 현대코퍼레이션 원자재 데일리 뉴스레터\n
    \n
    \n ${escapeHtml(dateText)}\n
    \n
    \n
    \n GPT 요약 보고서\n
    \n \n \n \n \n
    \n ${nl2br(aiText)}\n
    \n
    \n
    \n Npay Metals Prices\n
    \n \n \n \n \n \n \n \n ${priceRows}\n
    품목현재가등락등락률
    \n
    \n
    \n 전체 뉴스 목록\n
    \n \n \n \n \n
    \n \n ${newsRows}\n
    \n
    \n
    \n
    \n 본 메일은 현대코퍼레이션 임직원을 위한 원자재 시장 브리프입니다.
    \n 발신: 한국GPT협회 안현수\n
    \n
    \n
    \n\n\n`;\n\nreturn [\n {\n json: {\n subject,\n html,\n },\n },\n];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 800, 464 ], "id": "258dd1a6-cdbe-408e-b54d-cb227fb70737", "name": "Code in JavaScript(뉴스레터 디자인)" } ], "connections": {} }
    NODE G
    Gmail — 최종 뉴스레터 발송
    n8n-nodes-base.gmail

    HTML 본문, 제목(“[일일 원자재 브리프] 2026-04-19”), 수신자를 설정하면 발송 완료.

    Gmail 발송
    Code 보기
    n8n 노드 JSON — 캔버스에 붙여넣으면 동일 노드 복원
    {
      "nodes": [
        {
          "parameters": {
            "sendTo": "hsoo@rnbdp.com",
            "subject": "={{ $json.subject }}",
            "message": "={{ $json.html }}",
            "options": {}
          },
          "type": "n8n-nodes-base.gmail",
          "typeVersion": 2.2,
          "position": [
            1024,
            464
          ],
          "id": "6832f86f-5450-47e0-bb73-9a76fd1dfc80",
          "name": "Send a message",
          "webhookId": "65ae9e1a-e9b5-404b-b4aa-03fb3e927751",
          "credentials": {
            "gmailOAuth2": {
              "id": "6DhAa3toh8LJSSFT",
              "name": "PSE ZZABIS"
            }
          }
        }
      ],
      "connections": {}
    }
    최종 뉴스레터 결과
    그림 3-6. 최종 발송된 뉴스레터 — AI가 쓴 본문 + 가격 표 + 뉴스 리스트가 한 통에 담깁니다

    Wrap-up — 3개 사례를 관통하는 핵심 정리

    사례가 달라져도 변하지 않는 것, 그리고 변하는 것을 구분합니다.

    ① 뼈대는 같다

    수집 → 정리(Set) → 필터 → 저장(Sheets) → 통합(Aggregate/Merge) → 발송(Gmail). 이 패턴은 Case 1~3에 동일하게 깔려 있습니다. 3번 반복해 보면 자연스럽게 몸에 익습니다.

    ② 소스 특성만 다르다

    RSS는 구조가 정해져 있고 쉽습니다. HTTP+HTML은 사이트 구조가 바뀌면 깨지므로 Code 노드에서 방어 로직이 필요합니다. API는 안정적이지만 티어 제약(국가·호출 수)이 있습니다.

    ③ AI는 도구이자 실행자

    Case 1~2에서 AI는 “만드는 과정의 조력자”였습니다(프롬프트 치고 코드 받기). Case 3에서 AI는 “워크플로우 안의 노드”로 들어갑니다(본문 생성). 이 전이가 자동화의 레벨업입니다.

    ④ 막히는 순간이 학습 시점

    fetch not defined, 403, 타임아웃, 빈 필터 결과 — 이런 에러들이 튀어나올 때가 AI에게 질문을 가장 잘 뽑을 수 있는 때입니다. 에러 텍스트 + 시도한 코드 + 얻고 싶은 결과를 한 번에 던지십시오.

    실습 파일 다운로드