Skip to content

twbeatles/navernews-tabsearch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

뉴스 스크래퍼 Pro v32.7.3

네이버 뉴스 검색 API를 기반으로 동작하는 탭형 뉴스 수집/관리 도구입니다.

문서 인코딩

이 문서는 UTF-8로 작성되어 있습니다.

개발/검증 기준

  • 개발/정적 분석 기준: Windows, Python 3.14, PyQt6
  • 소스 실행 최소 기준: Python 3.10+
  • 기본 검증 명령: pyright(pyrightconfig.json) + pytest -q

주요 기능

  • 탭 기반 키워드 검색 및 독립 관리
  • 같은 첫 키워드를 공유해도 전체 검색 의미(query_key) 기준으로 탭 범위 분리
  • 같은 canonical query 탭은 중복 생성하지 않고 기존 탭으로 이동
  • 제외어(-키워드)와 날짜 조건을 포함한 고급 검색
  • 자동 새로고침(10분~6시간) 및 수동 전체 새로고침
  • 기사 북마크/읽음 처리/메모 작성
  • 열린 탭/북마크 탭 사이의 기사 상태 즉시 동기화
  • 알림 키워드와 새 기사 알림은 이번 fetch에서 새로 감지된 링크(new_items / new_count)에만 적용
  • 현재 탭 필터 전체 결과 CSV 내보내기
  • 키워드 그룹 관리
  • 시스템 트레이 동작(최소화/닫기 동작 커스터마이징)
  • 단일 인스턴스 실행 보장(중복 실행 방지)
  • 설정 자동 백업 + 수동 DB 포함 백업 및 재시작 적용형 복원(pending restore)

안정화 포인트 (v32.7.3+ 작업 브랜치 반영)

  • 중앙 HTTP 구성 계층(core.http_client.HttpClientConfig)을 도입하고 ApiWorker가 worker-owned session으로 API를 호출하도록 정리
  • fetch 성공 기준을 "API 응답 수신"이 아니라 "DB upsert 완료"로 강화하고, DatabaseWriteError를 통해 저장 실패를 명시적으로 error 경로로 승격
  • ApiWorker.last_error_meta와 전역 fetch cooldown을 추가해 429/quota 성격 오류 후 수동/자동/순차 refresh와 더 불러오기를 일관되게 차단/재개
  • 500/502/503/5045xx는 네트워크/timeout과 같은 재시도 경로로 편입하고, 최종 실패 시에도 retryable=Truehttp_error 메타를 유지
  • CSV 내보내기를 단일 read snapshot 기반으로 고정해 export 중 DB가 변해도 결과가 시작 시점 기준으로 일관되게 유지
  • DBWorker가 interrupt 가능한 dedicated read connection을 사용하도록 바꿔 유지보수/종료/탭 정리 시 실제 취소 성공률을 높임
  • 통계/언론사 분석 다이얼로그는 즉시 열고 InterruptibleReadWorker로 비동기 로드해 닫힘 시 SQLite read interruption까지 함께 요청
  • SQLite FTS5(news_fts) + trigger + app_meta 기반 증분 backfill을 추가하고, backfill 완료 전에는 기존 LIKE/NOT LIKE 경로를 진실 원본으로 유지
  • SQLite FTS5(news_fts) 증분 backfill은 유지보수/전체 fetch/순차 refresh/종료 경계에서 pause/retry/resume 되며 5초 -> 15초 -> 30초 cap backoff로 재개
  • 레거시 DB의 title_hash IS NULL / pubDate_ts IS NULL backfill을 반복 배치 루프로 바꿔 대용량 DB에서도 startup migration 누락이 남지 않도록 보강
  • 시작 시 북마크와 현재 탭만 즉시 로드하고, 나머지 뉴스 탭은 hydration queue로 순차 로드하며 초기 hydration 취소는 request-id 기반 late cleanup으로 정리
  • 설정 창의 API 키 검증도 HttpClientConfig 기반 공용 session과 현재 api_timeout 값을 사용하도록 통합
  • 설정 import는 stage -> persist -> apply-runtime -> startup reconcile 순서로 처리해 중간 실패 시 부분 적용된 UI/runtime 상태를 남기지 않도록 보강
  • 모두 읽음, 오래된 기사 삭제, 전체 기사 삭제는 chunked IterativeJobWorker 경로로 옮겨 유지보수/종료 시 stop()이 실제 효력을 갖도록 조정
  • RuntimePaths를 도입해 config/db/log/pending_restore/backups/lock/crash 경로를 단일 객체로 통합하고, 실행 폴더 대신 DATA_DIR 기준으로 런타임 저장 위치를 고정
  • 레거시 런타임 파일 마이그레이션은 core/runtime_support/migration.py로 분리하고, DB는 SQLite backup API 우선 + fallback integrity 검증, pending_restore.jsonbackup_dir rebasing, backups/는 폴더 단위 merge로 강화
  • MainAppNewsTab은 얇은 facade로 유지하고, 실제 책임은 ui/main_window_support/ui/news_tab_support/ 아래 모듈로 분리해 SOLID 기준의 변경 지점을 더 명확히 했다
  • 저장소 주요 텍스트 자산의 mojibake 문자열을 정리하고, 인코딩 스모크 테스트를 다중 suspicious token/패턴 감시로 강화
  • 백업 메타는 legacy include_db 누락을 실제 payload 파일 기준으로 자동 판별하고, 수동 검증/복원 직전 검증 결과(verification_state, last_verified_at 등)를 backup_info.json에 다시 기록
  • 설정 창의 오래된 기사 정리/전체 기사 삭제는 완료 결과를 유지보수 해제 직후에 flush해 열린 탭, 배지, 트레이 툴팁 동기화가 skip되지 않도록 보장
  • 순차 새로고침도 수동 새로고침과 같은 성공 후처리를 사용해 탭별 즉시 새 기사 알림, 트레이 알림, 알림 키워드 체크를 동일하게 수행
  • fetch 성공 메시지와 최종 순차 요약은 added_count가 아니라 new_items / new_count를 기본 의미로 사용해 "중복 제목이지만 새 링크"도 새 기사로 취급
  • ApiWorker는 HTTP 429에서 Retry-After의 delta-seconds / HTTP-date를 모두 해석하고, 재시도 대기와 최종 cooldown 메타 계산을 같은 값으로 맞춤
  • 시작 시 단일 인스턴스 가드 적용
  • 설정 반영 누락 보완(sound_enabled, api_timeout)
  • 설정 창의 API 키 검증/정리 작업 비동기 처리
  • 설정 가져오기 시 탭 중복 병합(dedupe) 강화
  • 자동 시작 최소화 옵션 변경 시 레지스트리 재등록
  • 자동 시작 상태를 정상/수리 필요/비활성화로 진단하고 설정 창에서 즉시 수리 지원
  • 설정 저장 시 메인 config와 .backup을 원자적으로 회전 저장하여 직전 정상 파일 보존
  • 자동 새로고침 조기 종료 경로에서 락 플래그 복구 보장
  • 탭 이름 변경 후 더 불러오기가 최신 탭 키워드를 사용하도록 보정
  • 제외어-only(예: -광고 -코인) 탭 입력 차단
  • keyword_groups 저장 위치를 news_scraper_config.json으로 일원화(레거시 마이그레이션 지원)
  • 최소화 시 트레이 동작(minimize_to_tray) 실제 반영
  • 자동 새로고침 간격을 2시간 기준으로 정렬
  • 설정 가져오기 정규화(타입/범위 보정) + 보정 항목 로그/알림
  • 설정 가져오기 시 keyword_groups를 덮어쓰기 대신 병합+중복 제거로 처리
  • 탭 리네임 시 fetch key 변경 여부에 따라 페이지네이션 상태를 안전하게 재설정
  • 모두 읽음현재 표시 결과만/탭 전체 2모드로 확장
  • 통계의 중복 기사 집계를 news_keywords.is_duplicate 기준으로 보정
  • 기사 단건/일괄 삭제 후 news_keywords.is_duplicate를 영향 집합 기준으로 재계산
  • 컨텍스트 메뉴 삭제를 raw SQL에서 DatabaseManager.delete_link(link)로 일원화
  • pending restore에서 restore_db=true인 경우 DB 백업 파일 누락 시 실패 처리 + pending 유지
  • 설정 로드 정규화 강화(theme_index, refresh_interval_index, api_timeout, bool 계열, alert_keywords)
  • fetch key 커서 영속화(pagination_state) 및 더 불러오기 DB 건수 fallback 제거
  • pagination_totals를 추가 저장해 재시작/필터 변경 후에도 더 불러오기 상태 복원
  • 단일 인스턴스 stale lock 복구(notify -> stale 제거 -> 재시도) 및 상태 로깅 강화
  • 트레이 미지원 환경에서 --minimized/start_minimized 요청 시 숨김 시작 차단
  • 검색어 정책 분리: API 검색어(parse_search_query)와 대표 키워드(parse_tab_query)를 분리하고 실제 탭 범위는 query_key로 판정
  • news_keywords(link, query_key) 기준으로 마이그레이션해 멀티 키워드 탭을 독립 범위로 조회/분석/배지 집계
  • CSV 내보내기를 청크 기반 백그라운드 작업으로 전환하고 *.tmp 저장 후 atomic rename 적용
  • 백업 목록은 빠른 메타데이터로 먼저 노출하고, 무거운 SQLite integrity/sidecar 검사는 필요 시 수동 검증으로 실행
  • 백업 복원 전 현재 상태 보호 백업을 먼저 시도하고, 실패 시 사용자 확인 없이는 복원을 진행하지 않음
  • SQLite 비상 연결 수를 제한하고 포화 시 명시적으로 실패/로그 남김
  • 설정 창 닫힘 시 백그라운드 워커 정리(콜백 안전 가드)
  • PyInstaller spec 동기화: PyQt6.QtNetwork 포함 보장(단일 인스턴스 IPC 런타임 안정화)
  • 백업 복원 예약 시 백업 메타(include_db) 기반으로 설정만/설정+DB 범위를 자동 판별
  • 기사 열기/안읽음 처리에서 DB 반영 실패 시 UI 캐시를 갱신하지 않도록 동기화 보강
  • 설정 창 워커 종료 경로 강화(종료 대기 초과 시 수명 분리 정리)
  • 탭 배지 미읽음 집계를 제외어 조건까지 반영하도록 보정
  • 시작 시 생성되는 자동 백업을 auto 메타로 구분하고 수동 백업과 보존 정책을 분리
  • 백업 목록에서 마이크로초 타임스탬프를 정상 표시하고 자동/수동 출처를 함께 표기
  • 설정 창의 데이터 정리/전체 삭제 완료 후 열린 탭/북마크/배지/트레이 툴팁을 즉시 동기화
  • 모두 읽음 -> 탭 전체가 제외어(exclude_words)를 포함한 현재 탭 의미를 유지하도록 보정
  • 언론사 분석이 탭의 제외어 조건까지 반영하도록 집계 경로를 확장
  • 탭 닫기/리네임 시 활성 fetch 워커를 먼저 정리하여 stale 콜백 반영을 방지
  • 탭 타이틀 구성(아이콘+배지)을 _format_tab_title(...)로 일원화
  • pending restore 적용을 스테이징+롤백 방식으로 전환하여 부분 적용을 방지
  • 즉시 복원 API(restore_backup)와 startup pending restore가 동일한 원자적 restore helper를 사용
  • 손상된 백업 메타(backup_info.json)가 있어도 정상 백업 항목은 계속 표시
  • 손상 백업 항목은 UI에서 손상됨으로 표시하고 삭제/무시 선택 제공
  • 설정의 client_secret 저장은 Windows에서 DPAPI 암호화(client_secret_enc) 우선 사용
  • 설정 로드 실패 시 news_scraper_config.json.backup 자동 복구 fallback 지원
  • 자동 정리(delete_old_news)는 pubDate_ts <= 0 레코드를 삭제 대상에서 제외
  • 트레이 미읽음 수는 탭 캐시 합산 대신 DB 총계(get_total_unread_count) 기준으로 계산
  • 트레이 미지원 환경에서도 show_desktop_notification()이 토스트 fallback + 알림음으로 동작
  • pyrightconfig.json을 추가하고 core.protocols/ui.protocols 기반 타입 계약과 검사 범위를 명시해 정적 분석 대상면을 고정
  • UTF-8 인코딩 스모크 테스트를 리포지토리 주요 텍스트 자산 전체로 확장
  • 단건 기사 상태 변경(읽음/북마크/메모/삭제) 시 열린 뉴스/북마크 탭 캐시를 link 기준으로 즉시 동기화
  • 모두 읽음/DB 유지보수 완료 후에는 열린 탭과 북마크 탭을 full refresh 경로로 재정렬해 정합성 보장
  • 알림 키워드는 이번 fetch에서 실제로 새로 추가된 기사(new_items)에만 적용
  • 탭 중복 방지, 설정 import dedupe, 검색 이력 dedupe를 canonical query 기준으로 통일
  • CSV 내보내기는 현재 로드된 slice가 아니라 현재 탭 필터 조건 전체 결과를 DB에서 다시 조회해 저장
  • 자동 시작 백업은 계속 설정만 대상으로 유지하고, UI/문서에서 DB 포함 수동 백업 필요성을 명시
  • DBQueryScope로 탭 조회 scope 계산을 단일화하고, append 경로에서는 known_total_count를 재사용해 count_news()를 다시 호출하지 않음
  • NewsTab_item_by_link 인덱스로 단건 상태 변경 대상을 O(1)에 찾고, fragment cache + coalesced render로 HTML flush를 줄임
  • news_keywords(query_key, keyword), news_keywords(query_key, keyword, is_duplicate), news(is_bookmarked, is_read, pubDate_ts DESC) 복합 인덱스를 추가해 현재 조회 패턴에 맞춤
  • 도움말은 설정 저장 경로와 분리된 read-only help_mode로 열려 저장 가능한 설정 화면처럼 보이지 않도록 분리
  • 기사 외부 열기 실패 시 읽음 처리/캐시 갱신을 하지 않도록 보정
  • 기간 필터는 즉시 반영형 대신 적용/해제 흐름으로 바꾸고, 역전된 날짜 범위는 자동 정규화
  • 탭 하단 안 읽음 수치와 배지 기준을 현재 로드된 slice가 아니라 현재 DB scope 전체 기준으로 일치시킴
  • 자동 새로고침 카운트다운은 전용 상태바 라벨로 분리해 일반 상태 메시지를 덮어쓰지 않도록 조정
  • 트레이 미지원 환경에서도 자동 새로고침 완료 알림이 토스트/알림음 fallback으로 전달되도록 통일
  • 키워드 그룹 관리는 즉시 저장형에서 staged 저장/취소 다이얼로그로 전환
  • 로그 검색은 debounce를 적용해 빠른 연속 입력에서 불필요한 reload를 줄임
  • 앱 종료 시 열린 NewsTab의 DB/job worker와 타이머를 먼저 정리한 뒤 DB/session 종료를 진행해 late callback 반영을 차단
  • CSV export, 설정 export/import, 백업 create/restore/delete는 공통 dialog adapter를 사용해 테스트와 실제 Qt wiring을 분리
  • 백업 생성은 복원 가능한 payload 기준으로만 성공 처리되며, 설정 파일이 없으면 수동 백업은 실패로 안내되고 시작 시 자동 백업은 조용히 건너뜀
  • 설정 가져오기로 새 탭이 추가되면 해당 탭만 지금 새로고침할지 한 번 확인하고, 동의 시 선택 탭만 순차 새로고침
  • 유지보수 모드는 fetch뿐 아니라 탭 DB 재조회, 필터/정렬/기간 변경 reload, CSV export, 통계/분석, 모두 읽음, import 직후 선택 refresh까지 전역 차단한다
  • DB 조회/집계 실패는 더 이상 빈 결과나 0건으로 숨기지 않고 DatabaseQueryError 계약으로 올려 상태바/토스트와 명시적 경고 다이얼로그로 노출한다
  • DB 쓰기 실패는 더 이상 (0, 0) 성공값으로 숨기지 않고 DatabaseWriteError 계약으로 올려 fetch 성공 토스트/알림이 잘못 발생하지 않게 한다
  • 키워드 그룹 저장 실패는 로그만 남기고 끝내지 않으며, KeywordGroupDialog는 실패 시 닫히지 않고 사용자 입력을 유지한 채 재시도할 수 있다
  • 백업 생성은 payload 작성 직후 self-verify를 수행하며, 검증 실패한 백업은 삭제하지 않고 목록에서 복원 불가 상태로 남긴다
  • 설정 import 뒤 새 탭 즉시 새로고침 프롬프트는 유지보수 중 여부, 순차 새로고침 진행 상태, API 자격증명 유효성을 먼저 통과한 경우에만 노출된다

최신 검증 메모 (2026-04-22)

  • python -m pytest -q => 251 passed, 5 subtests passed
  • pyright => 0 errors, 0 warnings, 0 informations
  • pyinstaller --noconfirm --clean news_scraper_pro.spec => 성공 (dist/NewsScraperPro_Safe.exe)
  • 산출물: dist/NewsScraperPro_Safe.exe

프로젝트 구조

navernews-tabsearch/
├── news_scraper_pro.py          # 엔트리포인트 + 호환 re-export 레이어
├── news_scraper_pro.spec        # PyInstaller 빌드 설정
├── pyrightconfig.json           # Pyright/Pylance 기준 설정 (Windows, Python 3.14)
├── pytest.ini                   # pytest 진입점/수집 경로 고정
├── tests/conftest.py            # pytest 임시 디렉터리/세션 공통 설정
├── core/                        # 코어 로직 패키지
│   ├── __init__.py
│   ├── bootstrap.py             # 앱 부팅(main), 전역 예외 처리, 단일 인스턴스 가드
│   ├── constants.py             # RuntimePaths facade + 경로/버전 상수 호환 export
│   ├── config_store.py          # 설정 스키마 정규화 + 원자 저장/.backup 회전
│   ├── database.py              # DatabaseManager facade (연결 풀 수명 주기)
│   ├── http_client.py           # 중앙 HTTP 구성 + worker-owned requests.Session factory
│   ├── runtime_support/         # runtime path 계산 + 레거시 파일 마이그레이션
│   │   ├── paths.py
│   │   └── migration.py
│   ├── _db_schema.py            # 스키마 초기화 / 무결성 검사 / 복구
│   ├── _db_duplicates.py        # 제목 해시 / 중복 플래그 재계산
│   ├── _db_queries.py           # 조회 / 개수 / 미읽음 집계
│   ├── _db_mutations.py         # upsert / 상태 변경 / 삭제 / 읽음 처리
│   ├── _db_analytics.py         # 통계 / 언론사 분석
│   ├── protocols.py             # lock/session capability Protocol 정의
│   ├── workers.py               # ApiWorker/DBWorker/AsyncJobWorker/IterativeJobWorker/InterruptibleReadWorker/DBQueryScope
│   ├── worker_registry.py       # WorkerHandle/WorkerRegistry (요청 ID 기반 관리)
│   ├── query_parser.py          # parse_tab_query/parse_search_query/build_fetch_key
│   ├── backup.py                # AutoBackup/on-demand backup verification/apply_pending_restore_if_any
│   ├── backup_guard.py          # 리팩토링 백업 유틸리티
│   ├── startup.py               # StartupManager/StartupStatus (Windows 자동 시작 상태/레지스트리)
│   ├── keyword_groups.py        # KeywordGroupManager
│   ├── logging_setup.py         # configure_logging
│   ├── notifications.py         # NotificationSound
│   ├── text_utils.py            # TextUtils, parse_date_string, perf_timer, LRU 캐시
│   └── validation.py            # ValidationUtils
├── ui/                          # UI 로직 패키지
│   ├── __init__.py
│   ├── main_window.py           # MainApp facade / composition root
│   ├── main_window_support/     # MainApp 세부 책임 분리
│   │   ├── base.py
│   │   ├── config.py
│   │   └── ui_shell.py
│   ├── _main_window_tabs.py     # 탭 추가/닫기/리네임/그룹 연결
│   ├── _main_window_fetch.py    # fetch orchestration / worker 수명 주기
│   ├── _main_window_settings_io.py # 설정 import/export / 유지보수 동기화
│   ├── _main_window_tray.py     # 트레이 / 종료 / closeEvent 처리
│   ├── _main_window_analysis.py # 통계 / 언론사 분석 UI
│   ├── news_tab.py              # NewsTab facade / compatibility root
│   ├── news_tab_support/        # NewsTab 상태/로딩/렌더링/액션 분리
│   │   ├── state.py
│   │   ├── loading.py
│   │   ├── rendering.py
│   │   ├── ui_controls.py
│   │   └── actions.py
│   ├── dialog_adapters.py       # QFileDialog/QMessageBox adapter
│   ├── protocols.py             # 메인 윈도우/부모 capability Protocol 정의
│   ├── settings_dialog.py       # SettingsDialog facade
│   ├── _settings_dialog_content.py # 설정/도움말/단축키 탭 조립
│   ├── _settings_dialog_docs.py # 도움말 / 단축키 HTML
│   ├── _settings_dialog_tasks.py # API 검증 / 데이터 정리 / 워커 정리
│   ├── dialogs.py               # NoteDialog/LogViewerDialog/KeywordGroupDialog/BackupDialog
│   ├── styles.py                # Colors/UIConstants/ToastType/AppStyle
│   ├── toast.py                 # ToastQueue/ToastMessage
│   └── widgets.py               # NewsBrowser/NoScrollComboBox
├── tests/                       # 회귀/호환성/안정성 테스트
│   ├── test_db_queries.py
│   ├── test_encoding_smoke.py
│   ├── test_entrypoint_bootstrap.py
│   ├── test_import_settings_dedupe.py
│   ├── test_import_settings_normalization.py
│   ├── test_db_integrity_recovery.py
│   ├── test_pagination_state_persistence.py
│   ├── test_plan_regression.py
│   ├── test_pending_restore_strict.py
│   ├── test_query_parser_search_policy.py
│   ├── test_refactor_backup_guard.py
│   ├── test_refactor_compat.py
│   ├── test_settings_roundtrip.py
│   ├── test_single_instance_guard.py
│   ├── test_start_minimized_guard.py
│   ├── test_stability.py
│   ├── test_startup_registry_command.py
│   ├── test_symbol_resolution.py
│   ├── test_keyword_groups_storage.py
│   ├── test_risk_fixes.py
│   ├── test_worker_cancellation.py
│   ├── test_backup_collision_and_restore.py
│   ├── test_backup_restore_mode.py
│   ├── test_audit_followthrough.py
│   ├── test_dialog_adapters_smoke.py
│   ├── test_import_refresh_prompt.py
│   ├── test_shutdown_cleanup.py
│   ├── test_stabilization_round1.py
│   ├── test_load_more_total_guard.py
│   ├── test_fetch_cooldown.py
│   ├── test_async_analysis.py
│   ├── test_fts_search_acceleration.py
│   ├── test_news_tab_ext_read_policy.py
│   ├── test_news_tab_performance.py
│   ├── test_settings_validation_http_policy.py
│   ├── test_settings_dialog_maintenance.py
│   ├── test_runtime_storage_paths.py
│   └── test_version_history_guard.py
├── query_parser.py              # 호환 래퍼 (→ core.query_parser)
├── config_store.py              # 호환 래퍼 (→ core.config_store)
├── backup_manager.py            # 호환 래퍼 (→ core.backup)
├── worker_registry.py           # 호환 래퍼 (→ core.worker_registry)
├── workers.py                   # 호환 래퍼 (→ core.workers)
├── database_manager.py          # 호환 래퍼 (→ core.database)
├── styles.py                    # 호환 래퍼 (→ ui.styles)
├── implementation_audit_2026-04-18.md # 2026-04-18 감사 후속 기록
├── backups/                     # 레거시 실행 폴더 백업(현재 런타임 백업은 DATA_DIR 하위 사용)
└── dist/                        # PyInstaller 빌드 결과물

실행 방법

1) 패키징된 실행 파일 사용

  • dist/NewsScraperPro_Safe.exe를 바로 실행합니다.

2) 소스 코드 실행

pip install PyQt6 requests
python news_scraper_pro.py

테스트 실행

기본 권장 명령:

python -m pytest -q

pytest.ini가 추가되어 아래 명령도 동일하게 수집/실행됩니다.

pytest -q

정적 타입 검사는 아래 명령을 사용합니다.

pyright

참고:

  • pyrightconfig.json은 루트 Python 파일, core/, ui/, tests/를 검사 대상으로 고정합니다.
  • tests/test_encoding_smoke.py는 저장소 주요 텍스트 자산(.py, .md, .json, .ini, .spec, .txt, .yml, .yaml)의 UTF-8 decode 실패, \ufffd, 알려진 깨진 토큰, 대표적인 mojibake 정규식 패턴 재등장을 함께 감시합니다.
  • tests/test_settings_validation_http_policy.py는 설정 창 API 검증이 raw requests.get(...)가 아니라 공용 session + 현재 timeout 정책을 쓰는지 회귀 테스트로 검증합니다.
  • facade 공개 경로(ui.main_window.MainApp, core.database.DatabaseManager, ui.settings_dialog.SettingsDialog)는 유지하고, 내부 구현만 private helper module로 분리했습니다.

PyInstaller 빌드 (onefile)

현재 스펙(news_scraper_pro.spec)은 onefile 기준으로 구성되어 있습니다.

pyinstaller --noconfirm --clean news_scraper_pro.spec
  • 산출물: dist/NewsScraperPro_Safe.exe
  • 아이콘 리소스: news_icon.ico 포함 (news_icon.png는 존재할 경우 fallback으로 함께 번들)
  • v32.7.2 핵심 안정화 1차(2026-02-25)는 런타임 로직/스키마 변경만 포함하며 .spec 추가 수정은 필요하지 않습니다.
  • v32.7.2 핵심+테스트 보강 2차(2026-02-28)도 런타임/테스트/문서 변경만 포함하며 .spec 수정은 필요하지 않습니다.
  • v32.7.2 감사 반영(2026-03-02)에서는 단일 인스턴스 IPC(QLocalServer/QLocalSocket) 경로 보호를 위해 PyQt6.QtNetwork.spec에 명시 반영했습니다.
  • v32.7.2 감사 후속(2026-03-03)에서는 requests optional 경로와 정합성을 맞추기 위해 .spec의 강제 hidden import에서 chardet를 제거했습니다.
  • v32.7.2 감사 후속 2차(2026-03-06)에서는 백업 정책/탭 의미 보존/문서 정합성 보강만 포함하며 .spec 추가 수정은 필요하지 않습니다.
  • v32.7.2 감사 후속 3차(2026-03-07)에서도 .spec을 재검토했으며, DPAPI 비밀값 저장 전환은 표준 라이브러리 기반(ctypes, base64)이라 추가 hidden import 수정이 필요하지 않습니다.
  • v32.7.2 타입/인코딩 정리(2026-03-09)에서는 개발용 pyrightconfig.json, 문서, .gitignore만 동기화했으며 .spec 추가 수정은 필요하지 않습니다.
  • v32.7.2 감사 후속 4차(2026-03-14)에서도 .spec을 다시 재검토했으며, query_key 범위화, pagination_totals, restore helper 공통화, export/import 1.1 확대는 기존 번들 의존성만 사용하므로 추가 hidden import 수정이 필요하지 않습니다.
  • v32.7.2 감사 후속 5차(2026-03-16)에서도 .spec을 다시 재검토했으며, 열린 탭 동기화/가시 결과 CSV/canonical dedupe/신규 기사 알림 분리는 기존 번들 의존성만 사용하므로 추가 hidden import 수정이 필요하지 않습니다.
  • v32.7.2 실행형 리스크 전면 수정(2026-03-18)에서도 .spec을 다시 재검토했으며, 유지보수 모드, DB 기반 로컬 페이지네이션, 백업 복원 가능 메타, export/import 1.2는 기존 번들 의존성만 사용하므로 추가 hidden import 수정이 필요하지 않습니다.
  • v32.7.2 성능 최적화 리팩토링(2026-03-21)에서도 .spec을 다시 재검토했으며, DBQueryScope, append skip-count, NewsTab fragment cache/coalesced render, 복합 인덱스 추가는 기존 번들 의존성만 사용하므로 추가 hidden import/exclude/data 수정이 필요하지 않습니다.
  • v32.7.3 운영 안정화 1차(2026-03-25)에서도 .spec을 다시 재검토했으며, IterativeJobWorker, 백업 full verification, 자동 시작 health/repair, config .backup 회전, DB emergency cap은 기존 번들 의존성만 사용하므로 추가 hidden import/exclude/data 수정이 필요하지 않습니다.
  • v32.7.3 구현 리스크 전면 반영(2026-04-09)에서도 .spec을 다시 재검토했으며, HttpClientConfig, fetch cooldown, snapshot export, dedicated read connection, async analysis, SQLite FTS5 backfill은 기존 번들 의존성/표준 라이브러리만 사용하므로 추가 hidden import/exclude/data 수정이 필요하지 않습니다.
  • v32.7.3 구현 리스크 후속 정합화(2026-04-13)에서도 .spec을 다시 재검토했으며, DatabaseWriteError, 반복 backfill loop, 설정 검증 HTTP 정책 통합, 인코딩 가드 강화는 기존 번들 의존성/표준 라이브러리만 사용하므로 추가 hidden import/exclude/data 수정이 필요하지 않습니다.
  • v32.7.3 구현 리스크 후속/문서 정합화(2026-04-16)에서도 .spec.gitignore를 다시 재검토했으며, 5xx retry 승격, hydration cancellation hardening, staged import atomicity, legacy backup metadata compatibility, persisted verification metadata, interruptible analysis reads, FTS retry scheduler는 기존 번들 의존성/표준 라이브러리만 사용하므로 추가 hidden import/exclude/data 수정이 필요하지 않습니다.
  • 2026-03-25 기준으로 .gitignore.pytest_tmp/를 명시 추가했고, 동일한 명령 pyinstaller --noconfirm --clean news_scraper_pro.spec로 클린 빌드가 다시 성공해 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됨을 재확인했습니다.
  • 2026-03-21 기준 pyinstaller --noconfirm --clean news_scraper_pro.spec 클린 빌드를 다시 검증했으며, 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됩니다.
  • 2026-03-24 기준으로도 .spec.gitignore를 다시 재검토했고, 동일한 명령 pyinstaller --noconfirm --clean news_scraper_pro.spec로 클린 빌드가 성공해 추가 packaging/ignore 수정이 필요하지 않음을 재확인했습니다.
  • 2026-03-27 기준으로 .spec.gitignore를 다시 재검토했고, help/read-only 설정 다이얼로그, 수동 백업 검증 UX, unread count bookkeeping, 트레이 fallback 알림 추가 이후에도 별도 packaging/ignore 수정은 필요하지 않았습니다.
  • 2026-03-27 기준 pyinstaller --noconfirm --clean news_scraper_pro.spec 클린 빌드가 다시 성공했으며, 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됩니다.
  • 2026-04-02 기준으로 .spec.gitignore를 다시 재검토했고, dialog adapter 도입, 종료 cleanup 강화, 백업 restorable preflight, import 후 선택 refresh 추가 이후에도 별도 packaging/ignore 수정은 필요하지 않았습니다.
  • 2026-04-02 기준 pyinstaller --noconfirm --clean news_scraper_pro.spec 클린 빌드가 다시 성공했으며, 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됩니다.
  • 2026-04-05 기준으로 .spec을 다시 재검토했고, 유지보수 모드의 DB 작업 전면 차단, DatabaseQueryError 기반 조회 실패 표면화, 키워드 그룹 저장 실패 노출, 백업 self-verify, import 후 refresh 가능 여부 선검사는 기존 번들 의존성만 사용하므로 추가 hidden import/exclude/data 수정이 필요하지 않습니다.
  • 2026-04-05 기준 pyinstaller --noconfirm --clean news_scraper_pro.spec 클린 빌드가 다시 성공했으며, 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됩니다.
  • 2026-04-13 기준으로 .spec.gitignore를 다시 재검토했고, DB write failure 승격, legacy backfill 반복 처리, 설정 검증 HTTP 정책 통합, mojibake 정리/인코딩 가드 강화 이후에도 추가 packaging/ignore 수정은 필요하지 않음을 재확인했습니다.
  • 2026-04-13 기준 pyinstaller --noconfirm --clean news_scraper_pro.spec 클린 빌드가 다시 성공했으며, 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됩니다.
  • 2026-04-16 기준으로 .spec.gitignore를 다시 재검토했고, hydration late-cleanup, import staged persistence, legacy backup metadata 보정/검증 결과 영속화, interruptible analysis read, FTS retry/resume 추가 이후에도 별도 packaging/ignore 수정은 필요하지 않음을 재확인했습니다.
  • 2026-04-16 기준 pyinstaller --noconfirm --clean news_scraper_pro.spec 클린 빌드가 다시 성공했으며, 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됩니다.
  • 2026-04-18 기준으로 .spec.gitignore를 다시 재검토했고, 유지보수 완료 sync 순서 고정, 순차 새로고침 즉시 알림, new_count 의미 통일, Retry-After 지원, 남은 pyright 정리 추가 이후에도 별도 packaging/ignore 수정은 필요하지 않음을 재확인했습니다.
  • 2026-04-18 기준 git status --ignored --shortbuild/, dist/, __pycache__/, .pytest_cache/, 런타임 DB/설정/로그 산출물이 계속 무시되는 것도 확인했습니다.
  • 2026-04-18 기준 pyinstaller --noconfirm --clean news_scraper_pro.spec 클린 빌드가 다시 성공했으며, 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됩니다.
  • 2026-04-22 기준으로 .spec.gitignore를 다시 재검토했고, RuntimePaths 통합, SQLite-safe legacy migration hardening, core/runtime_support / ui/main_window_support / ui/news_tab_support 구조 분할은 기존 번들 의존성/표준 라이브러리만 사용하므로 추가 hidden import/exclude/data 변경이 필요하지 않음을 확인했습니다.
  • 2026-04-22 기준 .gitignore에는 portable/legacy 실행 폴더에서 다시 생길 수 있는 keyword_groups.json, news_scraper_pro.lock을 추가로 명시했습니다.
  • 2026-04-22 기준 pyinstaller --noconfirm --clean news_scraper_pro.spec 클린 빌드가 다시 성공했으며, 산출물 dist/NewsScraperPro_Safe.exe가 정상 생성됩니다.

네이버 API 키 설정

  1. 네이버 개발자센터에서 애플리케이션을 등록합니다.
  2. 검색(Search) API 권한을 활성화합니다.
  3. 앱 실행 후 설정(Ctrl+,)에서 Client IDClient Secret을 입력합니다.

단축키

단축키 기능
Ctrl+R / F5 모든 탭 새로고침
Ctrl+T 새 탭 추가
Ctrl+W 현재 탭 닫기
Ctrl+F 검색/필터 포커스
Ctrl+S 현재 탭 필터 전체 결과 CSV 내보내기
Ctrl+, 설정 열기
Alt+1~9 탭 바로가기

데이터/설정 파일

앱은 기본적으로 사용자 런타임 디렉터리(DATA_DIR)에 아래 파일을 저장합니다.

  • Windows 기본값: %LOCALAPPDATA%\NaverNewsScraperPro

  • macOS 기본값: ~/Library/Application Support/NaverNewsScraperPro

  • Linux 기본값: $XDG_DATA_HOME/NaverNewsScraperPro 또는 ~/.local/share/NaverNewsScraperPro

  • 예외: NEWS_SCRAPER_DATA_DIR로 강제 지정 가능, NEWS_SCRAPER_PORTABLE=1이면 APP_DIR 사용

  • news_scraper_config.json

  • news_database.db

  • news_scraper.log

  • pending_restore.json

  • backups/

  • news_scraper_pro.lock

  • crash_log.txt

참고:

  • 시작 시 실행 폴더(APP_DIR)에 남아 있는 레거시 런타임 파일은 비파괴적으로 DATA_DIR로 1회 복사 마이그레이션됩니다.
  • 레거시 pending_restore.json는 가능하면 새 DATA_DIR/backups 기준으로 backup_dir를 재기록해 복사합니다.
  • 레거시 backups/는 폴더 단위 merge로 옮기며, 같은 이름의 백업이 이미 있으면 DATA_DIR 쪽 항목을 유지합니다.
  • 레거시 DB 마이그레이션은 SQLite backup API 우선, 실패 시 raw copy fallback + integrity 검증으로 수행됩니다.
  • keyword_groups는 별도 파일이 아니라 news_scraper_config.json 내부 필드로 저장됩니다.
  • 단일 인스턴스 잠금 파일(news_scraper_pro.lock)과 crash log(crash_log.txt)도 같은 runtime 경로 기준으로 관리됩니다.
  • pagination_statefetch_key -> 마지막 API start index 매핑이며, 필드가 없으면 기본값 {}로 로드됩니다.
  • pagination_state 값은 1..1000 범위로 정규화됩니다.
  • pagination_totalsfetch_key -> 마지막으로 확인한 API total 매핑이며 0도 유효한 값으로 저장됩니다.
  • search_historycanonical query 기준으로 dedupe되며 공백/대소문자만 다른 변형은 별도 항목으로 누적되지 않습니다.
  • 백업 복원 예약은 선택한 백업의 include_db 메타를 우선 사용하고, legacy 백업처럼 메타가 없으면 실제 DB payload 존재 여부로 복원 범위(설정만/설정+DB)를 자동 판별합니다.
  • 백업 메타의 triggerauto/manual 값을 가지며, 자동 시작 백업은 수동 백업과 별도 보존 정책으로 관리됩니다.
  • 자동 시작 백업은 설정만 포함합니다. DB 복원 지점이 필요하면 수동 백업에서 데이터베이스 포함을 선택해야 합니다.
  • 수동 백업은 news_scraper_config.json이 있어 실제로 복원 가능한 payload를 만들 수 있을 때만 성공합니다.
  • 백업 생성은 payload 기록 직후 self-verify를 수행하며, 검증 실패 항목은 폴더를 지우지 않고 백업 목록에서 복원 불가 상태로 남깁니다.
  • 수동 검증과 복원 직전 검증은 verification_state, verification_error, is_restorable, restore_error, is_corrupt, error, last_verified_atbackup_info.json에 다시 기록합니다.
  • 시작 시 자동 백업은 설정 파일이 없으면 사용자 차단 없이 skip되고 로그만 남깁니다.
  • 알림 키워드 매칭은 fetch 결과 전체가 아니라 이번 요청에서 새로 추가된 기사 집합에만 적용됩니다.
  • app_settingsclient_secret_enc, client_secret_storage 필드를 지원하며 Windows에서는 평문 client_secret를 비우고 암호문을 저장합니다.
  • pending restore 실패(검증/적용 실패) 시 pending 파일은 유지되며, 적용 중 오류가 나면 변경 파일을 롤백합니다.

예시:

{
  "pagination_state": {
    "<fetch_key>": 301
  },
  "pagination_totals": {
    "<fetch_key>": 542
  }
}

공개 API 참고:

  • DatabaseManager.connection(timeout: float = 10.0) 컨텍스트 매니저를 제공하며, 권장 DB 접근 패턴입니다.
  • DatabaseManager.get_total_unread_count() -> int를 통해 전체 미읽음 수를 직접 조회할 수 있습니다.
  • DatabaseManager.delete_link(link: str) -> bool가 추가되어 UI 삭제 경로에서 중복 플래그 재계산을 일원화합니다.
  • DatabaseManager.count_news(..., exclude_words: Optional[List[str]] = None)가 확장되어 미읽음 배지 집계 시 제외어 조건을 반영할 수 있습니다.
  • DatabaseManager.fetch_news(...), count_news(...), get_counts(...), get_unread_count(...), mark_query_as_read(...), get_top_publishers(...)query_key를 받아 대표 키워드가 같은 탭도 독립 범위로 조회할 수 있습니다.
  • core.workers.DBWorkerDBQueryScope + include_total + known_total_count 계약을 사용해 append 시 total count round-trip을 생략하고, full reload에서만 count_news(...)를 실행합니다.
  • core.workers.ApiWorkerlast_error_meta(kind/status_code/cooldown_seconds/retryable)를 남기며, MainApp.on_fetch_error(...)는 이를 읽어 전역 fetch cooldown을 갱신합니다.
  • DatabaseManager.iter_news_snapshot_batches(...)는 현재 탭 필터 전체 결과를 단일 read snapshot 위에서 순회해 CSV export 일관성을 보장합니다.
  • DatabaseManager.open_read_connection(...), close_read_connection(...), interrupt_connection(...)DBWorker 취소/종료 경로에서 사용하는 dedicated read connection helper입니다.
  • DatabaseManager.is_news_fts_backfill_complete()backfill_news_fts_chunk(...)news_fts 증분 백필 상태를 app_meta에 저장하며, FTS acceleration은 backfill 완료 후 positive token filter에만 사용됩니다.
  • ui.news_tab.NewsTab은 scope signature별 append/replace를 구분하고, HTML 렌더는 fragment cache를 재사용하면서 event-loop tick당 한 번만 flush합니다.
  • DatabaseManager.get_unread_counts_by_query_keys(query_keys: List[str]) -> Dict[str, int]가 추가되어 탭 배지를 query_key 기준으로 일괄 집계합니다.
  • AutoBackup.get_backup_list()는 항목별 is_corrupt, error, is_restorable, restore_error 메타를 포함해 UI가 손상/복원 불가 항목을 분리 표시할 수 있습니다.
  • core.database.DatabaseQueryError는 조회/집계 계열 DB 실패를 빈 결과로 삼키지 않는 표준 예외 계약이며, UI는 기존 캐시를 보존한 채 실패를 노출합니다.
  • core.database.DatabaseWriteError는 쓰기/변경 계열 DB 실패를 성공처럼 삼키지 않는 표준 예외 계약이며, ApiWorker와 fetch UI는 저장 실패를 성공 완료와 명확히 분리합니다.

키워드 입력 규칙

  • 최소 1개 이상의 일반 키워드가 필요합니다.
  • 제외어-only 입력(예: -광고 -코인)은 탭 추가/이름 변경/설정 가져오기에서 차단됩니다.
  • 같은 의미의 canonical query가 이미 열린 경우(예: 공백/대소문자 차이) 새 탭 대신 기존 탭으로 이동합니다.
  • API 검색어는 모든 양(+) 키워드를 공백 결합해 사용합니다. 예: 인공지능 AI -광고 → API query: 인공지능 AI
  • 대표 키워드(db_keyword)는 첫 번째 양(+) 키워드를 사용합니다. 예: 인공지능 AI -광고 → 대표 키워드: 인공지능
  • 실제 탭 범위/배지/분석/중복 판정/페이지 상태는 query_key = build_fetch_key(parse_search_query(raw_tab_query)) 기준으로 동작합니다.
  • 기존 DB에서 마이그레이션된 멀티 키워드 탭은 각 탭을 한 번 새로고침한 뒤부터 정확히 분리됩니다.

설정 Export/Import

  • export 포맷 버전은 1.2이며 settings, tabs, keyword_groups, search_history, pagination_state, pagination_totals, window_geometry를 포함합니다.
  • API 자격증명(client_id, client_secret, client_secret_enc)은 export/import 대상에서 제외되고, settings.auto_start_enabled는 export/import 대상에 포함됩니다.
  • import 시 tabscanonical query 기준 dedupe, keyword_groups는 merge, search_historycanonical query 기준 imported-first dedupe 후 최대 10개로 정리합니다.
  • pagination_statepagination_totals는 fetch key별로 병합하며 충돌 시 더 큰 값을 유지합니다.
  • import는 1.11.2를 모두 허용하며, 자동 시작/시작 최소화는 환경 가용성에 따라 안전한 값으로 보정됩니다.
  • 트레이를 사용할 수 없는 환경에서 import된 start_minimized=trueFalse로 강제되고 경고 토스트를 표시합니다.
  • 시작프로그램 기능을 사용할 수 없는 환경에서 import된 auto_start_enabled=trueFalse로 강제되고, 가능한 환경에서는 실제 레지스트리 상태까지 동기화합니다.
  • import로 새 탭이 추가되면 해당 탭들을 지금 새로고침할지 한 번 묻고, 동의하면 새 탭만 순차 새로고침합니다.
  • 이 prompt는 실제 refresh가 가능한 경우에만 표시되며, 유지보수 중이거나 이미 순차 새로고침이 실행 중이거나 API 자격증명이 유효하지 않으면 이유를 먼저 안내하고 prompt는 생략합니다.

트레이/시작 최소화 규칙

  • --minimized 또는 start_minimized=true는 시스템 트레이를 사용할 수 있을 때만 적용됩니다.
  • 시스템 트레이를 사용할 수 없는 환경에서는 앱이 숨겨지지 않고 일반 창으로 시작합니다.
  • 트레이 미지원 환경에서는 설정 창의 시작 시 최소화 상태로 시작 옵션이 비활성화됩니다.
  • 시스템 트레이가 없더라도 데스크톱 알림은 토스트 fallback과 알림음으로 동작합니다.

라이선스

MIT License

About

네이버 뉴스 탭 검색(키워드 기반)

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages