야구 덕질도 효율적으로: 데이터 분석가의 n8n 활용 AI 뉴스레터 제작기
혹시 ... 야구 좋아하시나요? ⚾️
오늘은 업무하면서 굉장히 많이 사용하기도하고,
자동화로 업무 효율화의 효과를 톡톡히 본 n8n workflow를 사용해
제가 좋아하는 야구 뉴스 레터를 만들어보려고 합니다.

업무를 하면서 배우게 된 내용들은 회사 내용들이 담겨져 있으니,
진짜 내 것으로 만들기 위해서는 내가 좋아하는 것을
나의 방식대로 재해석해서 기록해보려고 한다 ✍🏻✍🏻✍🏻✍🏻
일단 1차로 간략하게 만들어보고, 그 이후에 성능을 보면서 수정해나가려고 했다.
1차로 구성했던 플로우는
네이버 뉴스를 가져온다 -> 가져온 뉴스를 요약한다 -> 메일로 보낸다!
끝 아닐까라고 생각했지만 마냥 쉽지는 않았다... 🥲
그렇게 설계한 최종 workflow.

[목차]
1. HTTP Request로 뉴스 기사 스크래핑하기
2. 프롬프트 작성 & LLM model로 요약하기
3. 정제 후 뉴스레터(Gmail)로 보내기
4. 이제 진짜 전송 해보자 (피드백 & 수정의 과정 .. !)
5. Lesson Learn & 회고
1. HTTP Request로 뉴스 기사 스크래핑하기
원래 같으면 파이썬 코드를 짜서 하나씩 코드짜고, 에러나면 하나씩 찾아가며 오류를 발견해야 했을텐데,
JinaAI를 사용하면 자동으로 데이터를 가져올 수 있어서 처음으로 사용해봤다
근데 후기 : .. 대만족 👍🏻
뉴스 기사는 반복적인 구조로 되어 있어서 간단하긴하지만 네이버는 워낙 스크래핑하기 어렵기 때문에 사용하길 잘했다 생각했다
- JinaAI : https://jina.ai/api-dashboard
Jina Search Foundation API
Manage API keys for all Jina AI services—Embeddings, Reader, Reranker, and more.
jina.ai
여기서 받아온 URL을 n8n에 가져와서 아래와 같이 구성했음
1. &date= 뒤에는 오늘 날짜가 들어가야하고, 2.&team= 뒤에는 구단 정보가 들어가야한다.
그래서 http request 노드 전에 code 블럭에 팀 정보가 담긴 코드를 넣어두었음
그럼 아래 첨부한 사진과 같이 데이터가 들어가는 것을 볼 수 있다
https://r.jina.ai/https://m.sports.naver.com/kbaseball/news?sectionId=kbo&team={{ $json.id }}&sort=lastest&date=date={{ $now.format('yyyyMMdd') }}&isPhoto=N&random={{ $now.timestamp }}

그치만 예쁘게 코딩해서 원하는 값만 가져오는게 아니라 불필요한 header, footer, image 정보들이 많이 들어가게 된다
이렇게 되면 데이터를 한번에 LLM 모델에 돌릴때, 한번에 너무 많은 양이 들어가고 & 쓸데없는 기호때문에 토큰을 많이 쓰게 될 수 있음
그래서 code 블럭을 넣어서 10개 구단의 정보를 가져온 뒤,
순서대로 구단 정보를 매핑하고 + image, header, footer를 제외시킨다
그러면 10개 구단의 데이터들이 아주 예뽀니 데이터 프레임으로 들어올 수 있다!!
// 1. KBO_Teams에서 정의했던 원본 구단 리스트 (KBO_Teams 노드의 10개 아이템)
const teamMetadata = $('KBO_Teams').all();
// 2. HTTP Request를 통해 들어온 10개 결과물
const httpResults = $input.all();
// 3. 구단별로 오늘/어제 데이터를 묶어줄 임시 저장소
const mergedTeams = {};
// 4. 수집된 순서(index)에 맞춰 원본 구단 메타데이터를 1:1로 결합하며 합산합니다.
httpResults.forEach((result, index) => {
// HTTP Request의 본문 텍스트 가져오기
const newsText = String(result.json.data || '');
// 중요: HTTP Request 결과에는 구단 정보가 없으므로, 실행 순서(index)가 같은 KBO_Teams 노드의 메타데이터를 강제로 가져옵니다.
const meta = teamMetadata[index] ? teamMetadata[index].json : null;
if (!meta) return; // 매칭되는 구단 정보가 없으면 패스
const teamId = meta.id; // 예: "LG"
const teamName = meta.krName; // 예: "LG 트윈스"
const date = meta.targetDate; // 수집 대상 날짜 (예: 20260510)
if (!mergedTeams[teamId]) {
// 처음 등록되는 구단이면 기본 틀 세팅
mergedTeams[teamId] = {
teamCode: teamId,
teamNameKr: teamName,
rawContents: [newsText], // 오늘 혹은 어제 중 먼저 들어온 텍스트 저장
date: date
};
} else {
// 이미 등록된 구단이면 기존 텍스트 뒤에 덧붙임 (오늘 + 어제 뉴스가 합쳐짐)
mergedTeams[teamId].rawContents.push(newsText);
}
});
// 5. 구단별로 합쳐진 텍스트를 정제(이미지 마크다운 제거, 5,000자 컷)하여 최종 아웃풋으로 만듭니다.
const finalOutput = Object.values(mergedTeams).map(team => {
// 오늘 기사와 어제 기사 사이에 구분선을 넣고 하나로 합칩니다.
const joinedText = team.rawContents.join('\n\n---\n\n');
// 이미지 제거 및 5,000글자 자르기 전처리
const cleanContent = joinedText
.replace(/!\[.*?\]\(.*?\)/g, '') // 일반 이미지 마크다운 제거
.replace(/\[!\[Image.*?\]\(.*?\)/g, '') // jina ai 특유의 중첩 이미지 마크다운 제거
.slice(0, 5000); // 정확히 앞에서부터 5,000자만 커트
return {
json: {
teamCode: team.teamCode,
teamNameKr: team.teamNameKr,
raw_content: cleanContent,
date: team.date
}
};
});
return finalOutput;
2. 프롬프트 작성 & LLM model로 요약하기
일단은 10개의 구단 정보를 모두 받는 것이기 때문에 구단별로
1. 가장 많이 언급된 키워드 2~4개 추출 + 그 키워드에 맞는 뉴스기사 형태의 제목
2. 키워드와 제목을 잘 설명하는 요약문장 2개
3. 전체 키워드 2~4개와 가장 관련이 높은 기사 2-3개
뉴스레터로 보내야하니까 HTML로 화면을 구성하기 위해 json 파일로 데이터가 출력되어야 했음.
이런 부분들도 모두 프롬프트에 담았다. 그 프롬프트의 일부를 아래와 같이 공개한다


모델은 Google Gemini Chat Model gemini 2.5 flash 모델을 사용했다
이때, 모든 구단의 뉴스를 1개의 item으로 llm에 넣게되면
모델의 할루시네이션이 발생하기도하고, 성능이 떨어질 수도 있어서
구단별로 다 나눠놓고, 나중에 aggregate를 하는 방식을 선택했다.
3. 정제 후 뉴스레터(Gmail)로 보내기

요약 모델을 돌린 10개의 items를 하나의 데이터로 합친다.
llm에서 json의 형식에 맞게 받은 것이지 실제로 json 파일로 받은 것이 아니기 때문에
code 노드를 사용해서 json으로 만들어주는 과정이 필요하다.
그 후, 이메일로 정제된 화면을 구성하기 위해 메일 HTML 과정을 만들었다.
그 결과!

이런식으로 총 10개의 구단의 주요 키워드, 키워드를 설명하는 문장
그리고 그 키워드에 대한 요약문장 2개로 깔끔하게 구현해냈다.
상세한 html 코드들은 내가 예시로 하고 싶은 뉴스레터를 클로드에게 요청하고,
1차로 만든 다음, 필요에 맞게 수정하는 시간을 거쳤다.
html 코드는 조금만 아는 정도인데 늘 어렵고 헷갈려서 계속 제미나이와 클로드에게 물어가면서 했음..!!
4. 이제 진짜 전송 해보자 (피드백 & 수정의 과정 .. ! )

n8n에서 모든 설정들에 대한 테스트를 마치고 나면, 이렇게 꼭 ⭐️ Published ⭐️를 눌러줘야한다.
이때 모든 것을 다 수정한 뒤에 눌러야 한다! published를 하고 난뒤 시간을 몇번 바꾼적이 있는데, 자동화가 돌아가지 않아서 찾아보니
모든 설정을 다 마무리한 뒤에 published를 해야한다는 것을 깨달았음
이제 주변에 야구를 좋아하는 친구들을 찾아서 3일간 뉴스를 받아보고, 피드백을 받아보기로 했다!
피드백 링크는 레터 footer 부분에 아래와 같이 추가했다.

아래는 ver1으로 배포를 한 뒤 발생했던 오류들과, 받았던 피드백들을 기록해본다.
| 받았던 피드백들 | ver1 일때 발생했던 잔잔바리 오류들 |
![]() |
![]() - 주요 키워드에 맞지 않는 요약 설명 ![]() - 지난 뉴스레터에서 이미 받았던 내용들의 중복 ![]() - 이런식의 기사 부분의 html 깨짐 현상 |
BEFORE 😈 -------> ✨ AFTER ✨
- 관련 기사 영역의 html 깨짐 현상
- ➡️ 처음에는 jinaAI를 통해 데이터를 가져오고 별도로 데이터 정제를 하지 않았다면, 반복해서 사용할경우 프롬프트가 말을 듣지 않아 오류가 날수 있다고 생각함. 따라서 http request를 통해 불러온 데이터를 순서대로 정리하기 시작함, 그리고 html 코드를 통해 자동으로 불러오면서 수정 완료!
- 팀별로 이모지가 구분이 되거나 표시가 되었으면 좋겠다.
- ➡️ 단별 데이터, 이름, 색상을 모두 미리 KBO_Team이라는 변수에 저장해두어서, 구단별 정보를 가져올때 부가적인 조건을 함께 호출하였음
- 대표 키워드에 선발 투구, 마무리 투구 등 표현이 어색함
- ➡️ 프롬프트에 대표 키워드를 추출할때 예시로 들어있던 단어들이었음. 예시 단어들은 꼭 필요한 경우가 아니면 사용하지 않도록 프롬프트 수정.
- 일부 내용이 중복되는 현상
- ➡️ 베타서비스 발송일자가 일/월/화요일이라 각 요일별 특성의 차이가 발생함. 최대한 24시간 이내에 발생한 뉴스만 담을수 있도록 프롬프트 수정.
- 월요일(야구가 없는 날)에는 지난주 경기 요약보다는 구단 이슈, 내일 대진과 선발 투수 정보 등이 추가되면 좋겠다
- ➡️ 월요일이라는 특수성을 반영할 수 있도록 최대한 프롬프트를 다르게 생성해 수정하였음.
5. Lesson Learn / 회고
이번 프로젝트를 하면서 배운점은 굉장히 많았지만
크게는 아래와 같음!
1. 정말 생각보다 간단하다.
예전에는 회사에서 경쟁사 크롤링 때문에 꼬박 일주일을 쓴 적이 있고 오류날까봐 전전긍긍했었다.
그때와 비교하면 크롤링하는 부분에서의 답답함이 많이 해소가 되어서
분석, 요약 프롬프트를 작성하는데 시간을 더 투자할 수 있었음.
2. 프롬프트 작성의 중요성
중복된 조건을 두번 언급할 경우 LLM의 할루시네이션이 발생한다.
업무할 때는 많은 조건을 쓰지 않아 몰랐는데 이번 프로젝트 하면서 많이 느꼈다.
그래서 클로드 코드를 쓸때는 각각의 명령어에 조건을 세분화하는 편인데,
이번 프로젝트에도 최대한 명료하고 분명하게 작성하려고 하다보니 프롬프트 작성법에 대해 많이 알게된 것 같고
더 공부해야 할 것 같다고 생각했다
3. 재밌다
재밌다.. 내가 좋아하는 것을 기반으로 내가 필요한 것을 만드는 것에 대해 정말 큰 재미를 느꼈다.
이 프로젝트를 해야겠다고 마음 먹은지 4일만에 프로젝트를 완성하고 3일간의 베타테스트를 완료했다.
4. 혼자서 되는 것 같으면서도 혼자 되는 것은 없었다
왜냐? 제미나이랑 클로드랑 같이했으니까...
대학생때 모델을 만든뒤 사이트도 만든적 있었는데, 그때 정말 하나씩 알아가면서 어렵게 해냈던 기억이 난다.
그때 ai가 있었다면... 일주일동안 밤새면서 힘들어하지 않았겠지 싶어졌음
혼자 코드를 쓰고 프롬프트를 작성해 제작했지만,
결국 베타테스트 기간에 친구, 지인들에게 받은 피드백들이 가장 도움이 많이됐고 그 기반으로 여러번의 수정이 발생했다.
회사 다니면서 빠르게 MVP버전을 배포하는 것의 중요성을 깨닫긴했지만, 이렇게 체감을 하게되니 감회가 새로웠다.
앞으로도 ’이게 될까? 아니야, 할까 말까?‘ 생각 말고 먼저 시도하고,
먼저 부딪혀봐야겠다고 생각했다.
🎯 앞으로 더 해보고 싶은 것 🎯
1. 요일별 & 시간대별 최적화
크게 야구가 있는 날, 야구가 없는날(월요일)의 콘텐츠가 달라야하고 시간대도 굉장히 중요하다.
야구가 있는 날에는 오후 11시로 세팅하여 자기 전 오늘 경기들의 요약을 볼 수 있어서 좋고,
경기 전 5시 30분에 발송하면 바뀐 라인업이나 감독 인터뷰의 요약을 볼 수 있어서 좋다
그치만 후자의 경우는, 기사가 많이 발행되지 않는 비교적 비인기구단의 경우는 유익한 정보를 받을수가 없다.
계속 중복된 이슈의 공유를 받게되는 경우를 살펴보니
기사의 양 자체가 현저히 차이가 나서 요약의 퀄리티가 떨어질수 밖에 없었음.
만약 야없날 콘텐츠를 만든다면, 좀 더 디테일하게 기사 원문에 접근하는 등의 시도를 해야할 것으로 보여졌다.
2. 프롬프트 작성과 토큰 안정화
여러번 호출을 하면서 토큰을 많이썼다... 일주일 사용하는데 4천원정도 쓴거 같다
예를들어 이미 1-7번째 모델은 돌았으나 8번째 모델에서 오류나서 전체 결과가 fail이 되는 경우가 있었는데,
이럴땐 이미 사용된 토큰이 너무너무 아까웠다
계속 찾아가면서 완성이 되면 바로 저장하는 방법을 찾는등 안정화 방안을 고민해보아야겠다
너무너무 재밌었던 n8n workflow로
KBO 뉴스 레터 만들기 끝 !
다음엔 뭘 해볼까나



