5.4 KiB
5.4 KiB
평일 09:00 Slack 헤드라인 전달: 네이버 RSS + Playwright 구현 문서
작성일: 2025-09-05
상태: 배포 완료(운영 확인 2025-09-06)
목표/배경
- 평일 09:00에 컴퍼니엑스 Slack 채널/DM로 당일 스타트업 헤드라인(제목+링크) 자동 전송.
- 소스: 네이버 블로그 ‘깡프로 스타트업 트렌드 연구소’ 당일 포스트.
- 문제: RSS description이 중간에서 잘려(약 8개) 본문 전체를 담지 못함 → 원문(PostView iframe) 파싱 필요.
아키텍처(23/24 서버 맥락)
- 23: 메인(Nginx, Gitea, frontend, auth). 24: 로빙/스킬(rb8001, skill-*).
- skill-news(24, 8505): 헤드라인 생성 API 제공(HTTP). 저장/전송은 상위에서 담당.
- rb8001(24, 8001): 스케줄러(테스트 단발, 향후 09:00 등록) + Slack 전송.
최종 엔드포인트(구현됨)
POST /api/news/naver/fetch-headlines- 요청:
{ "format": "json|slack" }(기본 json) - 응답(json):
{ success, count, items:[{title,url}], source_post_url } - 응답(slack):
{ success, count, text, items, source_post_url } - 동작: RSS→최신 포스트 링크→PostView iframe 렌더링→본문 컨테이너에서 제목/URL 추출→Slack 텍스트 생성
- 요청:
파싱 로직(단순·안정화)
- 렌더링: Playwright(Chromium, headless)로 PostView 본문 컨테이너 텍스트/HTML 확보.
- 추출 규칙(HTML 우선):
- 본문 전체의 모든
a[href]를 순회. - URL 검증(urlparse netloc 존재, 내부 뷰어/블로그 제외:
postview.naver,blog.naver.com). - 제목은 해당 앵커의 가장 가까운 블록(
p/li/div/span) 텍스트에서 앵커 텍스트·URL·선행번호 제거 후 생성. - 비어 있으면 앵커 텍스트 사용(텍스트가 URL이면 제외). 80자 초과 시 말줄임.
- 본문 전체의 모든
- 보조(텍스트 기반): HTML 추출 실패 시 텍스트 패턴에서 URL 인접 텍스트로 보조 매칭.
- 번호: 1..N은 포맷 단계에서 부여(연속번호 제약 제거).
Slack 포맷(멘트/가독성)
- 헤더(오프너): 기본 “안녕하세요, 로빙입니다. 오늘 스타트업 헤드라인만 모았어요.”
- 본문:
NN. <URL|제목>행으로 정렬(번호는 01부터) - 푸터(클로저): 기본 “가볍게 시작해 힘차게 달려봐요. 로빙이 함께합니다.”
- 줄 간격: 기본 이중 줄바꿈(ENV로 단일 전환 가능)
- 출처 표기:
출처: <https://rss.blog.naver.com/startupventure.xml|네이버 깡뉴스>
환경변수(스킬)
- 필수/기본
NAVER_RSS_URL(기본: 깡뉴스 RSS),NAVER_MAX_ITEMS(표시 개수, 무제한은 크게 설정)SCRAPE_TIMEOUT(기본 30초)
- 포맷팅
HEADLINES_OPENER,HEADLINES_CLOSER,HEADLINES_SOURCE_URLHEADLINES_DOUBLE_SPACING(true/false)
환경변수(로빙 rb8001) - 운영 설정 확인됨
HEADLINES_TEST_CHANNEL_ID=C0920L68267 (실제 채널)HEADLINES_TEST_RUN_AT=09:00 (KST)HEADLINES_SCHEDULE_DAYS=mon-fri (평일만)HEADLINES_MIN_COUNT=1 (모든 뉴스 전송)SLACK_BOT_TOKEN=xoxb-... (설정됨)
스케줄링/흐름 (APScheduler 적용 완료)
- rb8001 시작 시 APScheduler가 cron job 자동 등록(평일 09:00, 서버 재시작 불필요).
- 환경변수:
HEADLINES_SCHEDULE_DAYS(mon-fri),HEADLINES_TEST_NOW(즉시 테스트). - 관리 API:
/api/schedule/list(목록),/api/schedule/test(즉시 실행).
테스트 결과(요약)
- 배포/헬스: skill-news 8505 정상, rb8001 재기동 후 스케줄 등록 로그 확인.
- 22:10 실전 전송: 채널로 전송 성공(초기엔 잡음/가독성 이슈 있었음).
- 개선 과정:
- 초기: RSS 절단 → 본문 전체 파싱으로 잡음 포함.
- 정규식 기반 번호/URL 패턴 → 0건 회귀 케이스 발생.
- HTML 앵커 우선 + 근접 텍스트 제목 생성 → 제목 정상화, 유효 URL만 유지.
- 현재: 제목이 URL로 표시되던 문제 해결, 표시 개수는
NAVER_MAX_ITEMS로 조정.
운영 가이드
- 전송 정책:
count < 임계치면 운영자 경고 메시지로 대체. - 로그: “수집 N건/필터 제외 M건/최종 K건” 요약을 INFO로 기록.
- 헬스체크: 외부 핑은 TTL 캐시(예: 60초) 또는
/healthz경량 엔드포인트 병행 권장. - 실패 대응: 1→2→4초 백오프 3회 재시도, 최종 실패는 운영 채널 알림.
리스크/제약
- 네이버 마크업 변경 시 셀렉터/타이밍 재조정 필요 (자동 모니터링 없음).
- 내부 링크 정책:
blog.naver.com,PostView.naver는 제외,naver.me/언론 도메인은 허용.
검증 시나리오
- Slack 텍스트:
curl -X POST http://localhost:8505/api/news/naver/fetch-headlines -H 'Content-Type: application/json' -d '{"format":"slack"}'
- JSON:
curl -X POST http://localhost:8505/api/news/naver/fetch-headlines -H 'Content-Type: application/json' -d '{"format":"json"}' | python3 -m json.tool
롤백/튜닝
- 롤백: skill-news 최근 커밋
git revert <hash> && git push→ Actions로 자동 재배포. - 튜닝 포인트:
NAVER_MAX_ITEMS, 줄간격, 오프너/클로저, URL 필터(내부 도메인 목록) 등 ENV로 조정.
결론
- 목표(제목+링크 헤드라인 자동 전송) 달성을 위한 최소·안정 경로를 구현 완료.
- 파싱은 HTML 앵커 우선, 포맷은 Slack 멘트/번호/간격으로 가독성 확보.
- 스케줄은 rb8001에서 관리하며, 기능은 HTTP API로만 결합(함수형·무하드코딩 원칙 준수).