들어가며
이전 글에서 라즈베리파이에 n8n을 셀프 호스팅했다. 이번 글에서는 n8n으로 간단한 워크플로우를 구성하는 과정을 정리한다.
매일 아침 네이버 경제 뉴스를 확인하는데, 매번 네이버에 접속해서 기사를 하나하나 클릭하는 과정이 번거로웠다. AI가 기사를 요약해서 Slack으로 보내준다면 이 과정을 줄일 수 있을 거라고 생각했다.
전체 구조

워크플로우의 전체 흐름은 다음과 같다.
Schedule Trigger (매일 09:00)
-> HTTP Request (네이버 경제 뉴스 페이지 크롤링)
-> HTML (헤드라인 + 링크 추출)
-> Code (상위 10개 기사 선택)
-> Loop Over Items (기사별 반복)
-> HTTP Request (개별 기사 본문 크롤링)
-> Wait (1초 대기)
-> HTML (본문 + 제목 + URL 추출)
-> Aggregate (10개 기사 데이터 합치기)
-> OpenAI (GPT로 요약)
-> Slack (채널에 메시지 전송)
총 11개 노드로 구성되어 있고, 크게 네 단계로 나눌 수 있다.
| 단계 | 설명 | 사용 노드 |
|---|---|---|
| 1. 뉴스 목록 수집 | 네이버 경제 섹션에서 헤드라인과 링크 추출 | Schedule Trigger, HTTP Request, HTML, Code |
| 2. 개별 기사 크롤링 | 10개 기사의 본문을 하나씩 가져오기 | Loop Over Items, HTTP Request, Wait, HTML |
| 3. LLM 요약 | 수집한 본문을 GPT에 넘겨 구조화된 요약 생성 | Aggregate, OpenAI |
| 4. 슬랙 전송 | 요약 결과를 슬랙 채널에 전송 | Slack |
1. 뉴스 목록 수집
Schedule Trigger

워크플로우의 시작점이다. 매일 아침 9시에 자동으로 실행되도록 설정했다.
HTTP Request - 뉴스 페이지 가져오기

네이버 뉴스 경제 섹션의 HTML을 가져온다.
| 설정 | 값 |
|---|---|
| Method | GET |
| URL | https://news.naver.com/section/101 |
HTML - 헤드라인과 링크 추출

가져온 HTML에서 CSS 셀렉터로 기사 제목과 링크를 배열로 추출한다.
| 추출 대상 | CSS Selector | 반환 형태 |
|---|---|---|
| 헤드라인 | .sa_text_title |
배열 (텍스트) |
| 링크 | .sa_text_title |
배열 (href 속성) |
Code - 상위 10개 기사 선택

HTML 노드에서 추출한 배열을 받아 상위 10개만 잘라내고, 각각을 개별 아이템으로 변환한다. n8n에서 반복 처리를 하려면 데이터가 개별 아이템 형태여야 한다.
const headlines = $input.first().json.headlines;
const links = $input.first().json.links;
const articles = [];
const limit = Math.min(10, headlines.length);
for (let i = 0; i < limit; i++) {
articles.push({
json: {
index: i + 1,
title: headlines[i],
link: links[i]
}
});
}
return articles;
이 시점에서 { index, title, link } 형태의 아이템 10개가 만들어진다.
2. 개별 기사 크롤링
Loop Over Items

n8n의 Split In Batches 노드로 10개 기사를 하나씩 순회한다. 이 노드는 두 개의 출력을 가진다.
| 출력 | 조건 | 다음 노드 |
|---|---|---|
| Done | 모든 아이템 처리 완료 | Aggregate |
| Loop | 아직 처리할 아이템 있음 | HTTP Request |
HTTP Request - 기사 본문 가져오기

각 기사 URL로 접근해서 본문 HTML을 가져온다. User-Agent와 Accept-Language 헤더를 설정해야 정상적인 응답을 받을 수 있다.
| 헤더 | 값 |
|---|---|
| User-Agent | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 |
| Accept-Language | ko-KR,ko;q=0.9 |
헤더 누락 시
헤더 없이 요청하면 네이버가 봇으로 인식해서 정상적인 HTML 대신 에러 페이지를 반환할 수 있다.
Wait - 1초 대기

차단 위험
기사를 연속으로 요청하면 서버에서 차단당할 수 있다. 1초 간격을 두어 네이버 서버에 부담을 주지 않도록 했다.
HTML - 본문 데이터 추출

각 기사 페이지에서 필요한 정보를 CSS 셀렉터로 추출한다.
| 추출 대상 | CSS Selector | 설명 |
|---|---|---|
| 본문 | #newsct_article |
기사 전문 텍스트 |
| 제목 | h2#title_area span |
기사 제목 |
| URL | meta[property="og:url"] (content 속성) |
기사 원문 링크 |
추출이 끝나면 다시 Loop 노드로 돌아가서 다음 기사를 처리한다.
3. LLM 요약
Aggregate

10개 기사 크롤링이 모두 끝나면 Loop의 Done 출력으로 넘어온다. Aggregate 노드는 개별 아이템 10개를 하나의 배열로 합쳐준다. GPT에 한 번에 넘기기 위한 전처리다.
OpenAI - GPT로 요약

합쳐진 10개 기사 데이터를 GPT에 전달한다. 프롬프트는 다음과 같이 구성했다.
당신은 전문 경제 뉴스 분석가입니다.
아래는 오늘의 네이버 경제 헤드라인 뉴스 10개의 전문(본문)입니다.
각 기사를 자세히 읽고 다음 형식으로 심층 요약해주세요:
## 📰 [기사 번호] 기사 제목
- 🔍 핵심 내용: 기사의 핵심 사실 2-3줄로 요약
- 💡 시사점: 이 뉴스가 경제/시장에 미치는 영향
- 📊 관련 키워드: 핵심 키워드 2-3개
- 🔗 원문 링크: 기사 원문 URL
---
마지막에 📈 오늘의 경제 트렌드 종합 섹션을 추가하여
10개 기사를 종합한 오늘의 경제 동향을 3-4줄로 분석해주세요.
기사 데이터는 n8n의 Expression 문법으로 동적 주입한다.
{{ $json.data.map((item, i) =>
`[기사 ${i+1}]\n${item.body}\n🔗 원문: ${item.link}`
).join('\n\n---\n\n') }}

모델 선택
사용한 모델은
gpt-5-nano다. 높은 성능이 필요하지 않은 간단한 요약 작업이면서 매일 실행되는 워크플로우인 만큼 비용을 고려해서 가벼운 모델을 선택했다.
4. 슬랙 전송

GPT의 응답을 슬랙 채널로 전송한다.
| 설정 | 값 |
|---|---|
| 채널 | news |
| 메시지 내용 | {{ $json.output[0].content[0].text }} |

이제 매일 아침 9시에 슬랙을 열면 경제 뉴스 10개의 요약이 정리되어 있다.
API 설정
OpenAI API 키 설정
ChatGPT를 이용하기 위해서는 API 키를 설정해 주어야 한다.
우선 여기에서 API key를 설정한 후 n8n에서 아래 사진과 같이 credential을 설정해주면 된다.


Slack API 설정
참고 영상
Slack API 연결은 생각 외로 복잡하고 시간이 꽤 걸리는 작업이었다. 여기에서 설명하기보다는 내가 참고했던 영상 링크를 남긴다.
개선 아이디어
지금은 기본적인 뉴스 요약만 하고 있지만, 확장할 수 있는 방향이 몇 가지 있다.
- 경제(101) 외에 정치(100), IT/과학(105) 등 다른 섹션 추가
- 특정 키워드가 포함된 기사만 별도 알림
- 요약 결과를 Notion이나 Google Sheets에 저장해서 트렌드 추적
- 긍정/부정 감성 분석 추가
마치며
n8n으로 첫 번째 워크플로우를 만들었다. 정리하면:
- HTTP Request + HTML 노드로 네이버 뉴스 크롤링
- Split In Batches + Wait으로 개별 기사 안정적으로 수집
- OpenAI 노드로 GPT 요약 생성
- Slack 노드로 매일 아침 자동 전송
'Infra' 카테고리의 다른 글
| 라즈베리파이에 n8n 셀프 호스팅하기 (0) | 2026.05.20 |
|---|---|
| 라즈베리파이 5 초기 설정 가이드 (0) | 2026.05.20 |
