Robust Article Assistant for Dynamic News Sites
브라우저에서 현재 읽고 있는 기사 본문을 기준으로 질문에 답하는 Tampermonkey 사용자 스크립트입니다.
The Economist, WSJ, FT, NYT 등 레이아웃 변화가 심한 뉴스 사이트에서,
정확한 본문 추출과 강건한 UI 생존성을 보장하는 데 집중합니다.
본 프로젝트에서의 핵심 문제의식은 다음 2가지로부터 시작하였습니다.
- 휘발되는 UI: 뉴스 사이트는 SPA 전환, 지연 로딩, 우측 rail 교체, 광고 영역 재구성이 잦아 주입한 UI가 쉽게 사라집니다.
- 불완전한 문맥: 기사 본문이 초기에 불완전하게 렌더링되는 경우가 많아, 너무 빨리 추출하면 문맥이 비거나 끊깁니다.
이 프로젝트는 본문 추출 정확도와 페이지 라이프사이클 안정성을 최우선순위로 두고, 진행되었습니다. 기본 모델은 별도의 서버 없이 브라우저에서 직접 호출되는 gemini-3-flash-preview를 사용합니다.
- 📡 3단계 본문 추출 (Fallback System)
- JSON-LD
articleBody→ Mozilla Readability → 텍스트 밀도(Density) 기반 fallback
- JSON-LD
- 🎯 기사 페이지 정밀 판별
isProbablyReaderable/ JSON-LD 기사 메타데이터 /main/article문단 수 휴리스틱
- 🛡️ SPA-safe 재초기화 및 생존
pushState,replaceState,popstate감지- host 제거 감지용 lightweight
MutationObserver및 debounce 기반 재초기화
- 🧠 Local RAG-lite
- 문단 단위 분리 및 질문 토큰 매칭 (점수화)
- 앞/뒤 문단 보강 및 전송 문맥 길이 제한으로 효율성 극대화
- UI/UX: Shadow DOM & 스마트 도킹
- 호스트 페이지 CSS와 격리된 Shadow DOM 렌더링 (상태줄, 메시지, 입력창, 최소화 제공)
- 우측 rail 후보를 탐색해 도킹하고, 실패 시 우측 플로팅 카드로 안전하게 Fallback
- ⚙️ 견고한 API 파이프라인
- role alternation 정리, system instruction 분리
- timeout, in-flight 복구, 에러 노출 및 본문 지연 로딩 시 backoff 기반 추출 재시도
해당 도메인에 매치되더라도, 실제 UI는 기사 페이지로 판단될 때만 표시됩니다.
economist.com/*|wsj.com/*|ft.com/*|nytimes.com/*
추가 빌드 단계는 없습니다. (Readability.js는 userscript 메타데이터의 @require로 로드합니다.)
- 브라우저에 Tampermonkey를 설치합니다.
- 새 userscript를 생성하여 이 저장소의 스크립트 내용을 붙여넣고 저장합니다.
- 지원 사이트의 기사 페이지에 접속합니다.
- 우측 위젯의
🔑버튼 또는 Tampermonkey 메뉴에서 Gemini API Key를 입력합니다.
- 기사 페이지 접속 시 스크립트가 페이지를 판별하고 본문을 추출합니다 (상태줄 표시).
- 하단 입력창에 질문을 넣고 전송합니다.
- UI 컨트롤 버튼:
🔑API Key 설정 |↻본문 재추출 |📌도킹/플로팅 전환 |🧹대화 초기화 |—최소화
1) 페이지 판별 및 본문 추출 (클릭하여 펼치기)
- 판별:
isProbablyReaderable, JSON-LD(NewsArticle,Article,Report), 문단 수 휴리스틱을 조합해 기사 페이지 여부를 판단합니다. 기사가 아니면 UI를 제거합니다. - 추출: JSON-LD → Readability(DOM clone) → Density fallback 순으로 시도합니다. 본문이 너무 짧으면 지연 로딩을 의심해 재시도합니다.
- 상태 갱신: URL, 제목, 발췌, 전체 텍스트, 문단 배열, 소스, fingerprint를 갱신합니다. fingerprint가 바뀌면 새 기사 문맥으로 간주합니다.
2) Local RAG-lite 및 Gemini 요청 (클릭하여 펼치기)
- 문맥 구성: 질문을 토큰화하여 겹치는 토큰이 많은 문단에 점수를 줍니다. 상위 문단 주변과 문서 앞/뒤 문단을 포함하되,
maxContextChars를 넘지 않게 잘라냅니다. (임베딩 검색이 아닌 가벼운 lexical matching) - API 요청: system instruction을 분리하고, 대화 이력(
user→model) 순서를 강제합니다. 현재 질문과 기사 문맥은 마지막user메시지에 합쳐 전송합니다.
3) UI 라이프사이클 및 성능 고려 (클릭하여 펼치기)
- UI 복구: 라우팅이나 DOM 교체 발생 시
historyAPI hook,popstatelistener, 경량MutationObserver를 통해 복구합니다. - 성능 관리: 판별/추출 로직은 Observer 내부에서 직접 돌리지 않고
requestIdleCallback이나 timeout으로 미루며, geometry 갱신은ResizeObserver와 debounce를 사용합니다.
아래 값은 스크립트 코드 상의 기본값(DEFAULTS)입니다.
| 카테고리 | 설정 키 | 기본값 | 설명 |
|---|---|---|---|
| UI | topOffsetPx / bottomMarginPx |
80 / 16 |
상/하단 여백 및 오프셋 |
floatRightPx / floatWidthPx |
16 / 380 |
플로팅 우측 여백 및 폭 | |
dockMode / hideDockTargetIfAd |
true / true |
우측 rail 도킹 시도 및 광고 숨김 여부 | |
| RAG | minUsefulChars |
1200 |
로드 완료로 판단할 최소 본문 길이 |
maxContextChars |
14000 |
API에 전송할 최대 컨텍스트 길이 | |
ragTopK / ragWindow |
10 / 1 |
상위 관련 문단 개수 및 이웃 문단 범위 | |
includeHeadTailParas |
2 |
문서 앞/뒤 필수 포함 문단 수 | |
| API | temperature / maxOutputTokens |
0.2 / 2000 |
생성 다양성 및 최대 출력 토큰 |
본 모델은 일반 목적 챗봇이 아닌 기사 근거 우선 Q&A 도구입니다.
- 기본 응답 언어는 한국어입니다.
- 가능한 한 기사 본문을 근거로 답하며, 기사 문맥을 벗어난 질문일 경우 반드시 아래 문구를 먼저 출력한 뒤 외부 지식을 사용합니다.
"말씀하신 질문은 기사 내용을 벗어납니다. 외부 지식을 사용해 답변드리겠습니다."
- 중간 서버나 백엔드 프록시가 전혀 없습니다. 브라우저에서 API로 직접 통신합니다.
- API Key는 Tampermonkey 로컬 저장소에 저장됩니다. (단, 비밀관리 솔루션 수준의 보안은 아니므로 고가치 운영 키 사용은 권장하지 않습니다.)
- Paywall을 우회하지 않으며, 브라우저에 렌더링된 텍스트만 처리합니다.
- Lexical matching 기반이므로 질문 표현이 완전히 다르면 관련 문단을 놓칠 수 있습니다.
- 우측 rail 도킹은 휴리스틱이므로, 대대적인 사이트 레이아웃 개편에 민감할 수 있습니다.
안정적인 라이프사이클 유지를 위해 다음 규칙을 준수하세요.
- 디바운스 활용:
MutationObserver안에서 무거운 판별/추출 로직을 직접 돌리지 마세요. (복구 트리거로만 사용) - 재귀 호출 주의: UI helper 안에서
ensureUI()재진입을 조심하세요. (특히 최소화 상태나 geometry 갱신) - 상태 원복:
STATE.inFlight는 반드시finally에서 원복하여 버튼 영구 disable을 방지하세요. - 대화 이력 유지: Gemini payload는
user와model의 교차 순서를 엄격히 맞춰야 합니다. - Fallback 허용: 레이아웃 도킹은 실패할 수 있습니다. 예외 처리하지 말고 자연스럽게 플로팅 카드로 내려오게 두세요.
Troubleshooting 팁: UI가 보이지 않으면 기사 페이지 판별에 실패했거나 로딩 지연일 수 있습니다. 모델 답변이 이상하다면
↻버튼으로 본문을 다시 추출해 보세요.