# 평일 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. ` 행으로 정렬(번호는 01부터) - 푸터(클로저): 기본 “가볍게 시작해 힘차게 달려봐요. 로빙이 함께합니다.” - 줄 간격: 기본 이중 줄바꿈(ENV로 단일 전환 가능) - 출처 표기: `출처: ` ## 환경변수(스킬) - 필수/기본 - `NAVER_RSS_URL`(기본: 깡뉴스 RSS), `NAVER_MAX_ITEMS`(표시 개수, 무제한은 크게 설정) - `SCRAPE_TIMEOUT`(기본 30초) - 포맷팅 - `HEADLINES_OPENER`, `HEADLINES_CLOSER`, `HEADLINES_SOURCE_URL` - `HEADLINES_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 적용 완료) 1) rb8001 시작 시 APScheduler가 cron job 자동 등록(평일 09:00, 서버 재시작 불필요). 2) 환경변수: `HEADLINES_SCHEDULE_DAYS`(mon-fri), `HEADLINES_TEST_NOW`(즉시 테스트). 3) 관리 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 && git push` → Actions로 자동 재배포. - 튜닝 포인트: `NAVER_MAX_ITEMS`, 줄간격, 오프너/클로저, URL 필터(내부 도메인 목록) 등 ENV로 조정. ## 결론 - 목표(제목+링크 헤드라인 자동 전송) 달성을 위한 최소·안정 경로를 구현 완료. - 파싱은 HTML 앵커 우선, 포맷은 Slack 멘트/번호/간격으로 가독성 확보. - 스케줄은 rb8001에서 관리하며, 기능은 HTTP API로만 결합(함수형·무하드코딩 원칙 준수).