• 배경 : 직접 네이버뉴스에 접속하기는 귀찮고, 하루에 3번정도 부동산관련 뉴스 목록을 받고 싶었다. 
    더보기
    작년에 회사 세미나준비 하면서 n8n을 알게되었는데, 올해 다시 n8n키워드 검색해보니 인기가 식은것 같아(?) 
    다른 방법을 gemini와 고민해보다가.. GitHub Actions 서버없이 완전히 무료로 24시간 작동하는 자동화 기능(매달 2000분무료)을 이용할수 있대서 GitHub Actions를 사용해보기로 결정!  컴퓨터를 켜지 않아도 cron 자동화 수행되는게  마음에 들었다. 

GEMINI와 고심끝에 진행하게된 방법!! 올바르지 않은 뉴스 URL 을 제공해주지않나..
여러 시행착오가 있었지만 최종 완성된 버전 제작기 

 

  • 인프라: GitHub Actions (개인 저장소 매달 2,000분 무료 실행 제공)
  • 흐름:
    1. 파이썬(Python)으로 RSS를 파싱하고 키워드를 필터링하는 코드를 짭니다 (feedparser, requests 라이브러리 사용).
    2. 이 코드를 GitHub 저장소에 올립니다.
    3. GitHub Actions 설정 파일(.github/workflows/cron.yml)에 "매일 아침 9시마다 이 파이썬 코드를 실행해라"라고 크론탭(Crontab) 스케줄러를 등록합니다.
    4. 파이썬 코드 끝단에 카카오톡 웹훅이나 텔레그램 봇 API 코드를 넣어 알림을 받습니다.

 

컴퓨터를 꺼두어도 24시간 지정된 시간에 알아서 작동하는 GitHub Actions + Python 부동산 뉴스레터 봇 구축 로드맵

🛠️ 전체 아키텍처 (구조)

  1. GitHub Repository: 파이썬 코드와 자동화 스케줄을 저장할 개인 저장소
  2. Python 스크립트: 네이버 뉴스 open api를 이용해서 “부동산” 뉴스 긁어서 기사 추출후 텔레그램 발송
  3. GitHub Actions (Cron): 매일 지정된 시간(예: 아침 9시)에 가상 서버를 띄워 파이썬 코드를 실행하는 스케줄러
    Github Actions의 cron은 UTC 기준이므로, KST보다 9시간 빠름.
    오전 7시 → UTC전날 오후10시 (22:00)
    오후12시 → UTC 오전 03시
    오후 6시 → UTC 오전 8시
    • cron: '0 22,3,9 * * *'
  4. Telegram 알림 (추천): 카카오톡 API는 1~2달마다 만료되는 액세스 토큰을 수동 갱신해야 해서 24시간 완전 무인 자동화가 불가능합니다. 반면 텔레그램은 토큰 만료가 없어 평생 완전 무료/무인 자동화가 가능하므로, 우선 텔레그램으로 핵심 로직을 뚫은 뒤 카카오톡 확장을 고려하는 것이 정석입니다.

🏃 1단계: 텔레그램 봇 만들기 (5분 소요)

스마트폰 텔레그램 앱에서 알림을 받을 방과 비서를 만듭니다.

  1. 텔레그램 앱 검색창에 @BotFather를 검색해 대화를 시작합니다.
  2. /newbot을 입력합니다.
  3. 봇의 이름과 아이디(끝이 _bot으로 끝나야 함)를 정해줍니다.
  4. 생성이 완료되면 HTTP API token이 발급됩니다. (예: 123456789:ABCdefGhI...) 이 값을 메모장에 적어둡니다. (보안 유의)
  5. 생성된 봇 링크를 눌러 대화창에 들어간 뒤 /start를 누릅니다.
  6. 봇이 나에게 메시지를 보내려면 내 고유 ID가 필요합니다. 검색창에 @userinfobot을 검색해 대화를 시작하면 본인의 고유 Id 숫자(예: 987654321)를 알려줍니다. 이 값도 적어둡니다.

[#1] BotFather

[#2] User Info

 

 

📰  2단계: 네이버 개발자센터 등록 (5분 소요)

[#1] 애플리케이션 등록

[#2] Client Id, Secret 복사

🐍 3단계: 파이썬 핵심 코드 작성 (NewsSender.py)

RSS를 읽고 키워드를 필터링하여 텔레그램으로 쏘는 파이썬 코드입니다. 로컬 PC나 GitHub 환경에서 동일하게 작동합니다

 

* Github Actions (서버)에서 수행할때는 환경변수에 값이 들어옴

* 로컬에서 수행할때는 환경변수가 없으므로 변수를 수동추가해서 수행 

NAVER_CLIENT_ID = "값"
NAVER_CLIENT_SECRET = "값"
BOT_TOKEN ="값"
CHAT_ID = "값"

(소스는 최종 소스기준)  

import requests
import os
import urllib.parse
from datetime import datetime

# 1. 환경 정보 가져오기
NAVER_CLIENT_ID = os.environ.get("NAVER_CLIENT_ID")
NAVER_CLIENT_SECRET = os.environ.get("NAVER_CLIENT_SECRET")
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN")
CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID")

SEARCH_KEYWORD = "부동산"
DB_FILE = "sent_urls.txt"  # 발송 완료된 링크를 저장할 파일명
MAX_MESSAGE_LENGTH = 3500  # 텔레그램 제한(4096자)보다 여유를 둔 안전선 설정

def load_sent_urls():
    if os.path.exists(DB_FILE):
        with open(DB_FILE, "r", encoding="utf-8") as f:
            return set(line.strip() for line in f if line.strip())
    return set()

def save_sent_urls(new_urls):
    # 1. 기존에 저장되어 있던 모든 링크를 순서대로 읽어옵니다.
    existing_urls = []
    if os.path.exists(DB_FILE):
        with open(DB_FILE, "r", encoding="utf-8") as f:
            existing_urls = [line.strip() for line in f if line.strip()]

    # 2. 이번에 새로 발송 성공한 링크들을 뒤에 추가합니다.
    total_urls = existing_urls + new_urls

    # 3. 전체 개수가 1000개를 넘어가면, 오래된 앞부분을 잘라내고 최신 1000개만 남깁니다.
    if len(total_urls) > 1000:
        print(f"데이터 최적화 진행: 총 {len(total_urls)}개 중 오래된 {len(total_urls) - 1000}개의 기록을 삭제합니다.")
        total_urls = total_urls[-1000:]

    # 4. 'w' 모드로 파일을 새로 열어 정제된 1000개 데이터만 덮어씁니다.
    with open(DB_FILE, "w", encoding="utf-8") as f:
        for url in total_urls:
            f.write(f"{url}\n")

def fetch_and_filter_news():
    if not NAVER_CLIENT_ID or not NAVER_CLIENT_SECRET:
        print("에러: 네이버 API 클라이언트 ID 또는 Secret이 설정되지 않았습니다.")
        return

    encText = urllib.parse.quote(SEARCH_KEYWORD)
    url = f"https://openapi.naver.com/v1/search/news.json?query={encText}&display=30&sort=date"
    
    headers = {
        "X-Naver-Client-Id": NAVER_CLIENT_ID,
        "X-Naver-Client-Secret": NAVER_CLIENT_SECRET
    }
    
    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
        news_data = response.json()
    except Exception as e:
        print(f"네이버 API 호출 중 오류 발생: {e}")
        return

    items = news_data.get("items", [])
    print(f"가져온 검색 뉴스 개수: {len(items)}개")

    sent_urls = load_sent_urls()
    matched_articles = []
    new_sent_list = []

    now = datetime.now()
    today_str = now.strftime("%d %b %Y")

    for item in items:
        title = item["title"].replace("<b>", "").replace("</b>", "").replace("&quot;", "\"")
        link = item["originallink"] or item["link"]
        pub_date_str = item.get("pubDate", "")

        if today_str not in pub_date_str:
            continue

        if link in sent_urls:
            continue

        try:
            # 네이버 날짜 규격: "Sat, 18 Jul 2026 14:20:00 +0900"
            clean_date_str = pub_date_str.rsplit(" ", 1)[0]
            dt = datetime.strptime(clean_date_str, "%a, %d %b %Y %H:%M:%S")
            # 💡 [yy.mm.dd] 형식으로 포맷 축소 (예: 26.07.18)
            formatted_date = dt.strftime("%y.%m.%d")
        except Exception:
            # 예외 발생 시 현재 시간 기준 포맷팅
            formatted_date = now.strftime("%y.%m.%d")

        # 💡 요 청 사 항 : 제목 뒤에 [yy.mm.dd] 구조로 배치 변경
        matched_articles.append(f"📌 {title} [{formatted_date}]\n🔗 {link}")
        new_sent_list.append(link)

    if matched_articles:
        current_message = "<b>🏠 [오늘의 부동산 뉴스 알림]</b>\n\n"
        send_count = 0

        for article in matched_articles:
            if len(current_message) + len(article) + 2 > MAX_MESSAGE_LENGTH:
                send_telegram_message(current_message)
                send_count += 1
                current_message = "<b>🏠 [오늘의 부동산 뉴스 알림 - 계속]</b>\n\n"

            current_message += article + "\n\n"

        if current_message.strip() and current_message != "<b>🏠 [오늘의 부동산 뉴스 알림 - 계속]</b>\n\n":
            send_telegram_message(current_message)
            send_count += 1

        save_sent_urls(new_sent_list)
        print(f"새로운 {len(matched_articles)}개의 뉴스를 총 {send_count}개의 메시지로 분할 발송 완료했습니다.")
    else:
        print("오늘 작성된 새로운 부동산 뉴스가 없거나 이미 모두 발송되었습니다.")

def send_telegram_message(text):
    if not BOT_TOKEN or not CHAT_ID:
        print("에러: 텔레그램 토큰 또는 채팅 ID가 환경변수에 설정되지 않았습니다.")
        return

    text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
    text = text.replace("&lt;b&gt;", "<b>").replace("&lt;/b&gt;", "</b>")

    url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
    payload = {
        "chat_id": CHAT_ID,
        "text": text,
        "parse_mode": "HTML",
        "disable_web_page_preview": True #링크밑 미리보기 강제 비활성화
    }
    response = requests.post(url, json=payload)
    if response.status_code != 200:
        print(f"텔레그램 전송 실패! 응답 코드: {response.status_code}, 메시지: {response.text}")

if __name__ == "__main__":
    fetch_and_filter_news()

 

🚀 4단계: GitHub Actions 스케줄러 설정 (.github/workflows/cron.yml)

핵심 설정 파일입니다. 프로젝트 루트에 폴더를 만들고 이 파일을 위치시키면 GitHub이 알아서 읽어갑니다.

폴더구조

내 저장소 (루트)
├── .github/              <-- 폴더 이름 앞에 반드시 점(.)이 있어야 합니다.
│   └── workflows/        <-- 뒤에 s가 붙은 복수형이어야 합니다.
│       └── cron.yml      <-- 파일 확장자가 yaml 또는 yml이어야 합니다.
└── NewsSender.py

 

 

cron.yml

name: Daily Real Estate News Bot

on:
  schedule:
    # 💡 한국 시간(KST) 오전 7시, 오후 12시, 오후 6시에 각각 실행
    # UTC 기준: 22:00(전날), 03:00, 09:00
    - cron: '0 22,3,9 * * *'
  workflow_dispatch:

jobs:
  run-bot:
    runs-on: ubuntu-latest

    steps:
      # 1. 저장소의 코드를 가상 서버로 가져옴
      - name: Checkout repository code
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

      # 2. 파이썬 환경 세팅
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      # 3. 필요한 라이브러리 설치
      - name: Install dependencies
        run: |
          pip install requests

      # 4. 파이썬 스크립트 실행 (보안 데이터는 Secrets로 주입)
      - name: Run Python Script
        env:
          NAVER_CLIENT_ID: ${{ secrets.NAVER_CLIENT_ID }}
          NAVER_CLIENT_SECRET: ${{ secrets.NAVER_CLIENT_SECRET }}
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
        run: python NewsSender.py

      # 5. 파일 변경 사항이 있다면 저장소에 자동으로 커밋 및 푸시
      - name: Commit and Push sent_urls.txt Changes
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          
          if [ -f "sent_urls.txt" ]; then
            git add sent_urls.txt
            
            if ! git diff --cached --exit-code; then
              git commit -m "Chore: Optimize and update sent_urls.txt [skip ci]"
              git push origin main
            else
              echo "변경되거나 추가된 링크 데이터가 없습니다."
            fi
          fi

 

🔒 5단계: GitHub 저장소 생성 및 Secrets 설정

  1. GitHub에 새로운 개인 저장소(Private 추천)를 만듭니다.
  2. 저장소 상단 메뉴에서 Settings > Secrets and variables > Actions로 이동합니다.
  3. New repository secret 버튼을 눌러 아래 두 가지를 추가합니다.
    • Name: TELEGRAM_BOT_TOKEN / Value: 1단계에서 받은 API 토큰 값
    • Name: TELEGRAM_CHAT_ID / Value: 1단계에서 받은 본인 고유 ID 숫자
    • Name: NAVER_CLIENT_ID / Value: 2단계에서 받은 클라이언트ID 값
    • Name: NAVER_CLIENT_SECRET / Value: 2단계에서 받은 시크릿 값숫자
     

    4. 중복 발송 url 코드관리하는 파일을 gitActions에서 수정할수있도록, 읽기 권한설정
       - Settings > Actions > General > Read and write permissions

    5. 작성한 NewsSender

      .py 파일과 .github/workflows/cron.yml 파일을 저장소에 푸시(Push)합니다.

git add .
git commit -m "커밋 변경할 내용"
git push origin main
git pull origin main

 

 

GitHub Actions에서 수동실행하기

  1. Actions > 생성된 job > Run workflow > Run Workflow

 

2. 텔레그램 문자수신 확인

+ Recent posts