n8n RSS 중복 발행 문제를 겪어본 적 있는가.
매일 정해진 시간에 RSS 피드를 수집해서 WordPress에 자동으로 글을 발행하는 워크플로우를 만들었는데,
어제 발행한 기사가 오늘 또 발행되어 있다.
분명히 잘 돌아가는 줄 알았는데 블로그에 똑같은 글이 두 개, 세 개씩 쌓여 있는 걸 보면 당황스럽다.
RSS 피드는 최신 기사 목록을 계속 내려주기 때문에, 이미 처리한 기사인지 구분하는 로직이 없으면 이런 일이 반복된다.
왜 n8n RSS 중복 발행이 생기는가
RSS 피드의 특성을 이해해야 한다.
RSS 피드는 “지금 현재 최신 기사 N개”를 보여주는 목록이다.
새 기사가 올라오면 목록에 추가되고, 오래된 기사는 밀려나간다.
문제는 기사가 올라온 직후에는 며칠 동안 계속 목록에 남아있다는 점이다.
예를 들어 어떤 블로그의 RSS 피드가 최신 20개 기사를 보여준다고 하자.
오늘 새 기사 1개가 올라왔다면, 어제 수집했던 기사 19개는 여전히 목록에 있다.
n8n 워크플로우가 매일 이 피드를 읽어서 글을 발행하면, 어제 이미 발행한 기사를 오늘 또 발행하게 된다.
결국 “이 기사는 이미 처리했는지”를 어딘가에 기록해두고 확인하는 로직이 필수다.
처음 시도한 방법과 실패
n8n에는 staticData라는 기능이 있다. 워크플로우 실행 사이에 데이터를 저장해두는 메모리 같은 역할이다.
처음엔 이걸 활용하려고 했다. 발행한 기사의 URL 목록을 staticData에 저장해두고, 새 기사를 처리하기 전에 이 목록을 확인하는 방식이다.
그런데 문제가 생겼다. 워크플로우를 수정해서 재배포하거나, n8n 서버를 재시작하면 staticData가 초기화되는 경우가 있었다.
어느 날 갑자기 지난 일주일치 기사가 전부 다시 발행되어 있는 걸 발견했다. 메모리 기반 저장은 안정적이지 않다는 걸 깨달았다.
외부 데이터베이스를 따로 붙이는 방법도 있지만, 이미 WordPress를 운영하고 있는 상황에서 DB를 하나 더 관리하는 건 비효율적이었다.
WordPress를 진실의 원천으로 사용하기
해결책은 의외로 단순했다. WordPress 자체를 “진실의 원천(source of truth)”으로 사용하는 것이다.
진실의 원천이란 “이 데이터가 맞는지 확인하려면 여기를 보면 된다”는 기준점을 말한다. 생각해보면 당연한 접근이다.
중복 발행을 막으려는 이유가 WordPress에 같은 글이 두 번 올라가는 걸 방지하기 위함이니까,
WordPress에 이미 뭐가 발행되어 있는지 직접 물어보면 된다.
구체적인 방식은 이렇다.
- 새 글을 발행할 때 원문 RSS 기사의 URL을 WordPress 커스텀 메타 필드에 저장한다.
- 새 기사를 처리하기 전에 WordPress API로 기존 발행 글 목록을 조회한다.
- 원문 URL이 이미 존재하면 중복으로 판단하고 건너뛴다.
이렇게 하면 n8n이 재시작되든, 워크플로우가 재배포되든 상관없다.
WordPress에 기록된 정보는 그대로 남아있으니까. WordPress와 n8n 연결 설정이 이미 되어 있다면 구현은 어렵지 않다.
WordPress 메타 필드 설정하기
먼저 WordPress에서 커스텀 메타 필드를 REST API로 노출시켜야 한다.
기본적으로 커스텀 메타 필드는 REST API에서 조회되지 않는다.
테마의 functions.php 파일이나 별도 플러그인에 아래 코드를 추가한다.
register_post_meta('post', '_rss_source_url', array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'auth_callback' => function() {
return current_user_can('edit_posts');
}
));
이 코드는 _rss_source_url이라는 메타 필드를 등록하고, REST API에서 읽고 쓸 수 있게 해준다.
show_in_rest를 true로 설정하는 게 핵심이다.
n8n 워크플로우에서 중복 체크 구현
이제 n8n 워크플로우를 수정할 차례다. 크게 세 단계로 나뉜다.
1단계: 기존 발행글 조회
HTTP Request 노드를 사용해서 WordPress에 이미 발행된 글 목록을 가져온다.
GET /wp-json/wp/v2/posts?per_page=100&status=publish,draft&_fields=id,title,link,meta
per_page=100으로 최근 100개 글을 가져온다.
필요에 따라 숫자를 조절하면 된다. status에 draft도 포함시킨 이유는 임시저장 상태로 남아있는 글도 중복 체크에 포함시키기 위함이다.
_fields 파라미터로 필요한 필드만 요청해서 응답 크기를 줄인다.
2단계: 중복 체크 로직
Code 노드나 Function 노드에서 중복 여부를 판단한다.
// 기존 발행글에서 원문 URL 목록 추출
const existingPosts = $input.all();
const publishedUrls = existingPosts.map(p => p.json.meta._rss_source_url).filter(url => url);
// RSS에서 수집한 기사 중 아직 발행되지 않은 것만 필터링
const rssArticles = $('RSS Feed 노드 이름').all();
const newArticles = rssArticles.filter(article => {
return !publishedUrls.includes(article.json.link);
});
return newArticles;
핵심은 publishedUrls.includes(article.json.link) 부분이다.
RSS 기사의 원문 URL이 이미 발행된 글 목록에 있으면 건너뛴다.
RSS 필터링 자동화와 함께 사용하면 불필요한 기사를 먼저 걸러내고 중복 체크를 할 수 있다.
3단계: 발행 시 메타 필드 저장
WordPress에 글을 발행할 때 원문 URL을 메타 필드에 함께 저장한다.
POST /wp-json/wp/v2/posts
{
"title": "기사 제목",
"content": "기사 본문",
"status": "publish",
"meta": {
"_rss_source_url": "https://원문기사URL.com/article/123"
}
}
이미 존재하는 글을 수정할 때도 마찬가지다. POST /wp-json/wp/v2/posts/{id} 엔드포인트로 메타 필드를 업데이트할 수 있다.
제목 비교는 보조 수단으로만
n8n RSS 중복 발행 문제를 해결할 때 제목으로 비교하면 안 되냐는 생각이 들 수 있다.
결론부터 말하면, 제목 비교는 보조 기준으로만 사용해야 한다.
이유는 여러 가지다. 같은 주제로 비슷한 제목의 기사가 나올 수 있다.
RSS 피드에서 내려주는 제목과 실제 발행 시 가공한 제목이 다를 수 있다.
특수문자나 공백 처리 차이로 같은 제목이 다르게 인식될 수 있다.
반면 원문 URL은 고유하다. 같은 기사가 URL이 두 개일 수는 없다.
그래서 URL을 주 기준으로 삼고, 제목은 URL이 없는 예외 상황에서만 참고하는 게 안전하다.
FAQ
Q. per_page=100이면 100개 넘는 글은 어떻게 되나?
100개 이전에 발행된 글은 조회되지 않는다.
하지만 RSS 피드 특성상 100개 이전에 발행된 기사가 다시 RSS에 나타날 가능성은 거의 없다.
만약 더 많은 글을 확인해야 한다면 per_page 값을 늘리거나, 페이지네이션을 구현하면 된다.
Q. 메타 필드가 저장되지 않는데 왜 그런가?
register_post_meta 코드가 제대로 등록되지 않았을 가능성이 높다.
WordPress 테마 업데이트 후 functions.php가 초기화되었거나, 플러그인이 비활성화되었을 수 있다.
REST API 응답에서 meta 필드가 나오는지 먼저 확인해보자.
Q. 기존에 발행된 글에는 메타 필드가 없는데?
기존 글은 수동으로 메타 필드를 추가하거나, 일괄 업데이트 스크립트를 돌려야 한다.
또는 기존 글은 무시하고 앞으로 발행되는 글부터 적용하는 방법도 있다.
어차피 오래된 기사는 RSS 피드에서 밀려나기 때문에 중복 발행될 가능성이 낮다.
Q. 여러 RSS 피드를 수집할 때는 어떻게 하나?
동일한 방식으로 처리하면 된다.
각 RSS 피드에서 수집한 기사의 원문 URL을 메타 필드에 저장하고, 중복 체크할 때 전체 발행글을 대상으로 확인한다.
출처별로 구분이 필요하다면 _rss_source_feed 같은 메타 필드를 추가로 만들어서 피드 URL도 함께 저장하면 된다.
Q. staticData를 아예 안 쓰는 게 좋은가?
상황에 따라 다르다. 임시 데이터나 캐시 용도로는 staticData가 편리하다.
하지만 “이 데이터가 사라지면 문제가 생기는” 중요한 정보는 외부 저장소에 두는 게 안전하다.
n8n RSS 중복 발행 문제처럼 데이터 손실이 직접적인 문제로 이어지는 경우에는 특히 그렇다.