n8n으로 중소벤처기업부 신규 사업공고 텔레그램으로 받아보기

소개

정부가 올려주는 사업공고를 제 때 확인하는 것은 사업에서 굉장히 중요한 부분입니다. 대개 공고기간은 짧으면 7일, 길면 14일 정도인데요, 긴급 공고의 경우 늦게 확인하면 영업 손실로 이어질 수 있어요.

n8n 박정기 스터디장님께 영감을 받아 RSS 과제를 시도해 본 사례입니다. 공공부문 사업에 첫 단추인 사업 공고를 빠르게 받아보기를 목적으로 신규 사업공고 알림 서비스 텔레그램을 제작해 보았습니다.

👉 중소벤처기업부 신규사업공고 텔레그램 들어가기
(현재 n8n 무료 구독자라 지속적 서비스 여부는 미정)

중소기업벤처부 사업공고 게시판

한국 웹 사이트의 스크린 샷

진행 방법

13기 때 make 따라하기 1번 해보고, n8n에 도전하게 되었네요.

앱의 프로세스를 보여주는 흐름도

단계 설명

1단계 정보 가져오기 (RSS Read)

중소기업벤처기업부 홈페이지에서 사업공고 RSS를 제공
RSS 제공정보(3종) : 공고제목, 링크, 게시일시

여담이지만, 작업 개시 당시 중기부 사업공고 RSS 정보와 게시판 최신 글을 비교하는데 상이 -> RSS 정보가 최신 정보가 아님을 확신하고 중소기업벤처기업부에 전화하여 RSS 제공 정보를 개선함

이메일 목록을 보여주는 화면의 스크린 샷

RSS 제공하는 서비스를 찾기 전, RSS 미제공 공공기관 홈페이지 몇 곳을 뚫어 보려고 했으나(HTTP Request의 POST 이용) 실패하여 RSS 제공하는 곳에서 해보는 것으로 계획 변경

2단계 정보 가공하기

2-1. 주소 가공(Edit Field)
링크 주소가 localhost로 시작되서 localhost를 Edit Field 이용 www.mss.go.kr로 일괄 변경함.
(이 마저도 사례글 쓰다가 RSS 최신화를 중기부에 요청할 때 함께 개선된 것을 발견)

2-2. 글의 순서 조정(Sort) : 내림 차순(최신글부터 보이도록)
대부분 RSS는 내림차순(최신글부터)이나, 혹시나 하는 마음에 재확인 차원에서 추가

2-3. 최신 글만 보기(Filter)
필터에서 최근 이틀 사이 글만 보도록 설정
RSS들은 대부분 최근 30개, 이런 식으로 제공하고 있으므로 필터도 굳이 필요 없을 수 있으나 이 마저도 많다고 생각해서 기간(최근 2일간)으로 1차 필터(1~5개 수준으로 축소됨)

전화에서 중국어 텍스트 편집기의 스크린 샷


2-4. 각 사업공고 페이지 들어가기(HTTP Request)
사업공고 페이지에 직접 들어가서 RSS에서 제공하지 않는 정보를 가져와야 했음

2-5. 각 사업공고 페이지에서 내가 원하는 정보만 빼오기(HTML)
* 필요 정보 : 제목(RSS에서 기 제공), 공고번호, 신청기간, 공고문 본문
CSS Selector이 어려워서 GPT 도움을 받았음

항목 목록을 보여주는 모바일 앱의 스크린 샷

2-6. 공고문 본문에 띄어쓰기, 줄바꿈 기호들 없애기(Code)
HTML에서 가져온 텍스트에서 줄바꿈, 띄어쓰기 기호를 없애는 것은 항상 해야하는 것 같음.
파이썬 코드로 제거함

# HTML 태그와 특수 문자를 제거하는 함수
import re
import html

def clean_text(text):
    if not text or not isinstance(text, str):
        return ''
    
    # HTML 디코딩 (', " 등의 엔티티 변환)
    text = html.unescape(text)
    
    # HTML 공백 문자 변환
    text = text.replace(' ', ' ')
    
    # HTML 태그 제거 (정규표현식 사용)
    text = re.sub(r'<[^>]+>', ' ', text)
    
    # <br> 태그를 공백으로 변환
    text = re.sub(r'<br\s*/?>', ' ', text, flags=re.IGNORECASE)
    
    # 줄바꿈을 공백으로 변환
    text = text.replace('\n', ' ')
    
    # 캐리지 리턴 제거
    text = text.replace('\r', '')
    
    # 연속된 공백을 하나로 변환
    text = re.sub(r'\s+', ' ', text)
    
    # 앞뒤 공백 제거
    return text.strip()

# 모든 입력 항목을 순회하며 '내용' 필드 정리
for item in _input.all():
    # '내용' 필드가 있는 경우에만 처리
    if '내용' in item.json and item.json['내용'] is not None:
        item.json['내용'] = clean_text(item.json['내용'])
    
    # 다른 모든 문자열 필드도 정리 (옵션)
    for key in item.json:
        if isinstance(item.json[key], str):
            item.json[key] = clean_text(item.json[key])

# 처리된 항목들 반환
return _input.all()

2-7. 텔레그램 발송 기록(구글 시트)에서 발송 기록 가져오기(Google Sheet)
텔레그램 발송 일지를 구글 시트에 만듦.

한자가있는 Google 스프레드 시트의 스크린 샷


2-8. RSS에서 가져온 최신 글 목록 중 발송 기록 사업공고는 없애기(Code 1)
새로 가져온 공고글 중에서 이미 발송한 것은 식별하여 제거함.

new_items = []

# 공고번호 정규화 함수
def normalize_number(number):
    if not number or not isinstance(number, str):
        return ""
    # 숫자와 하이픈만 남김
    return ''.join(char for char in number if char.isdigit() or char == '-')

# 구글 시트의 기존 공고번호 추출 및 정규화
existing_numbers = []
for sheet_item in _('Google Sheets1').all():
    number = sheet_item.json.get('공고번호', '')
    normalized = normalize_number(number)
    if normalized:
        existing_numbers.append(normalized)

# Code 노드의 모든 아이템 추출
code_items = []
for code_item in _('Code').all():
    # 신청기간이 있는 아이템만 추가
    if code_item.json.get('신청기간'):
        code_items.append(code_item.json)

# Filter 노드의 모든 아이템 추출
filter_items = []
for filter_item in _('Filter').all():
    filter_items.append(filter_item.json)

# 필터링 로직 - 공고번호만 비교
for code_item in code_items:
    current_number = code_item.get('공고번호', '')
    normalized_current = normalize_number(current_number)
    
    # 공고번호가 있고, 기존 공고번호 목록에 없는 경우에만 추가
    if normalized_current and normalized_current not in existing_numbers:
        # 해당 공고번호와 일치하는 Filter 아이템 찾기
        matching_filter_item = None
        
        for filter_item in filter_items:
            # 여기서는 제목 비교 없이 바로 정보를 추가합니다
            matching_filter_item = filter_item
            break
        
        if matching_filter_item:
            # 추가 정보 포함
            new_item = {
                **code_item,
                'pubDate': matching_filter_item.get('pubDate', ''),
                'link': matching_filter_item.get('link', '')
            }
            new_items.append(new_item)
          

# 신규 사업공고 반환
return [{"json": item} for item in new_items] if new_items else []

2-9. 신규사업공고(RSS)에서 기 발송 사업공고(구글시트) 제외 후 0건일 때 예외처리(if)
신규 공고가 0건 일 때 n8n flow 멈추도록 하는 모듈

2-10. 텔레그램 발송은 과거 것부터 최신 것 순으로 발송하기 위해 순서를 다시 재정렬(Sort)
결국 발송 순서는 과거 것부터 발송해 주어야 수신하는 사용자가 받았을 때 가장 최근것부터 확인 가능.

3단계 텔레그램 발송

3-1. 텔레그램에 제목, 공고번호, 신청기간, 공고본문, 공고문 바로가기 링크(이상 5가지 정보)를 양식에 맞춰 보냄(Telegram)

휴대 전화에서 한국 문자 메시지 스크린 샷

3-2. 텔레그램 발송현황 최신화(Google Sheet1)
텔레그램 발송 현황을 구글 시트에 최신화하여 2-8단계(기 발송한 사업공고 제외)에서 계속 재활용

결과와 배운 점

개인적으로 n8n 생각보다 너무 어렵고, 힘들었습니다.
중기부를 시작으로 모든 공공기관 사업공고를 이렇게 만들어 서비스해 볼 생각입니다.

## 도움 받은 글 (옵션)

문과생도 n8n 글들을 두루두루 읽어보고 참고하였음.

13
10개의 답글

👉 이 게시글도 읽어보세요