네이버 뉴스 검색 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)
- 중앙 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/504등5xx는 네트워크/timeout과 같은 재시도 경로로 편입하고, 최종 실패 시에도retryable=True인http_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초 capbackoff로 재개 - 레거시 DB의
title_hash IS NULL/pubDate_ts IS NULLbackfill을 반복 배치 루프로 바꿔 대용량 DB에서도 startup migration 누락이 남지 않도록 보강 - 시작 시 북마크와 현재 탭만 즉시 로드하고, 나머지 뉴스 탭은 hydration queue로 순차 로드하며 초기 hydration 취소는 request-id 기반 late cleanup으로 정리
- 설정 창의 API 키 검증도
HttpClientConfig기반 공용 session과 현재api_timeout값을 사용하도록 통합 - 설정 import는
stage -> persist -> apply-runtime -> startup reconcile순서로 처리해 중간 실패 시 부분 적용된 UI/runtime 상태를 남기지 않도록 보강 모두 읽음, 오래된 기사 삭제, 전체 기사 삭제는 chunkedIterativeJobWorker경로로 옮겨 유지보수/종료 시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.json은backup_dirrebasing,backups/는 폴더 단위 merge로 강화 MainApp과NewsTab은 얇은 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 자격증명 유효성을 먼저 통과한 경우에만 노출된다
python -m pytest -q=>251 passed, 5 subtests passedpyright=>0 errors, 0 warnings, 0 informationspyinstaller --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 빌드 결과물
dist/NewsScraperPro_Safe.exe를 바로 실행합니다.
pip install PyQt6 requests
python news_scraper_pro.py기본 권장 명령:
python -m pytest -qpytest.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 검증이 rawrequests.get(...)가 아니라 공용 session + 현재 timeout 정책을 쓰는지 회귀 테스트로 검증합니다.- facade 공개 경로(
ui.main_window.MainApp,core.database.DatabaseManager,ui.settings_dialog.SettingsDialog)는 유지하고, 내부 구현만 private helper module로 분리했습니다.
현재 스펙(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,NewsTabfragment 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를 다시 재검토했으며,5xxretry 승격, 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 --short로build/,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가 정상 생성됩니다.
- 네이버 개발자센터에서 애플리케이션을 등록합니다.
- 검색(Search) API 권한을 활성화합니다.
- 앱 실행 후
설정(Ctrl+,)에서Client ID와Client 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_state는fetch_key -> 마지막 API start index매핑이며, 필드가 없으면 기본값{}로 로드됩니다.pagination_state값은1..1000범위로 정규화됩니다.pagination_totals는fetch_key -> 마지막으로 확인한 API total매핑이며0도 유효한 값으로 저장됩니다.search_history는canonical query기준으로 dedupe되며 공백/대소문자만 다른 변형은 별도 항목으로 누적되지 않습니다.- 백업 복원 예약은 선택한 백업의
include_db메타를 우선 사용하고, legacy 백업처럼 메타가 없으면 실제 DB payload 존재 여부로 복원 범위(설정만/설정+DB)를 자동 판별합니다. - 백업 메타의
trigger는auto/manual값을 가지며, 자동 시작 백업은 수동 백업과 별도 보존 정책으로 관리됩니다. - 자동 시작 백업은
설정만포함합니다. DB 복원 지점이 필요하면 수동 백업에서데이터베이스 포함을 선택해야 합니다. - 수동 백업은
news_scraper_config.json이 있어 실제로 복원 가능한 payload를 만들 수 있을 때만 성공합니다. - 백업 생성은 payload 기록 직후 self-verify를 수행하며, 검증 실패 항목은 폴더를 지우지 않고 백업 목록에서
복원 불가상태로 남깁니다. - 수동 검증과 복원 직전 검증은
verification_state,verification_error,is_restorable,restore_error,is_corrupt,error,last_verified_at를backup_info.json에 다시 기록합니다. - 시작 시 자동 백업은 설정 파일이 없으면 사용자 차단 없이 skip되고 로그만 남깁니다.
- 알림 키워드 매칭은 fetch 결과 전체가 아니라 이번 요청에서 새로 추가된 기사 집합에만 적용됩니다.
app_settings는client_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.DBWorker는DBQueryScope + include_total + known_total_count계약을 사용해 append 시 total count round-trip을 생략하고, full reload에서만count_news(...)를 실행합니다.core.workers.ApiWorker는last_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 포맷 버전은
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 시
tabs는canonical query기준 dedupe,keyword_groups는 merge,search_history는canonical query기준 imported-first dedupe 후 최대 10개로 정리합니다. pagination_state와pagination_totals는 fetch key별로 병합하며 충돌 시 더 큰 값을 유지합니다.- import는
1.1과1.2를 모두 허용하며, 자동 시작/시작 최소화는 환경 가용성에 따라 안전한 값으로 보정됩니다. - 트레이를 사용할 수 없는 환경에서 import된
start_minimized=true는False로 강제되고 경고 토스트를 표시합니다. - 시작프로그램 기능을 사용할 수 없는 환경에서 import된
auto_start_enabled=true는False로 강제되고, 가능한 환경에서는 실제 레지스트리 상태까지 동기화합니다. - import로 새 탭이 추가되면 해당 탭들을 지금 새로고침할지 한 번 묻고, 동의하면 새 탭만 순차 새로고침합니다.
- 이 prompt는 실제 refresh가 가능한 경우에만 표시되며, 유지보수 중이거나 이미 순차 새로고침이 실행 중이거나 API 자격증명이 유효하지 않으면 이유를 먼저 안내하고 prompt는 생략합니다.
--minimized또는start_minimized=true는 시스템 트레이를 사용할 수 있을 때만 적용됩니다.- 시스템 트레이를 사용할 수 없는 환경에서는 앱이 숨겨지지 않고 일반 창으로 시작합니다.
- 트레이 미지원 환경에서는 설정 창의
시작 시 최소화 상태로 시작옵션이 비활성화됩니다. - 시스템 트레이가 없더라도
데스크톱 알림은 토스트 fallback과 알림음으로 동작합니다.
MIT License