# task-1566.1 완료 보고서: 네이버 블로그 경쟁 분석 크롤링 셀렉터 수정

## SCQA

**S**: 대시보드의 `/api/naver-blog/competition-analysis` 엔드포인트는 네이버 검색 결과에서 블로그 URL을 추출하여 경쟁 분석을 제공한다. 네이버 검색 응답 자체는 정상(200 OK, 516KB, blog.naver.com 99회 언급)이었다.

**C**: `_extract_blog_urls_from_search()` 함수의 CSS 셀렉터 4개(`a.title_link`, `div.title_area a`, `a.api_txt_lines.total_tit`, `div.total_tit a`)가 현재 네이버 HTML 구조(동적 해시 클래스명 `fender-ui_*`, `T4d_*` 사용)와 불일치하여 매칭 0건, 결과 0건 반환. 추가로 DB 저장 시 키 불일치(`homeResults` vs `homePosts`)로 저장 데이터도 항상 빈 배열이었다.

**Q**: CSS 셀렉터를 네이버 HTML 변경에 강건한 방식으로 교체하여 경쟁 분석 결과를 정상 반환할 수 있는가?

**A**: CSS 셀렉터 의존 방식을 href 패턴(`blog.naver.com/{blogId}/{logNo}`) + 텍스트 유무 기반으로 전환하여 해결. 수정 후 homePosts 5건, blogPosts 5건 정상 반환 확인. DB 키 불일치도 함께 수정하여 히스토리 저장 정상 동작 확인.

## 수정 내역

### 수정 파일
- `/home/jay/workspace/dashboard/server.py`

### 변경 1: `_extract_blog_urls_from_search()` (라인 313~350)
- **변경 전**: CSS 셀렉터 4개 순차 매칭 (`a.title_link`, `div.title_area a` 등)
- **변경 후**: `soup.find_all("a", href=True)` 전체 순회 → `blog.naver.com` href 패턴 필터 → `_parse_blog_url()`로 blogId/logNo 추출 → `url_key` 기반 중복 제거 → 제목 텍스트 5자 이상 필터
- **근거**: 네이버가 동적 해시 클래스명(`fender-ui_228e3bd1`, `T4d_tSMrB8qRjbb9_yER`)을 사용하여 CSS 셀렉터가 무효화됨. href 패턴 기반 방식은 클래스명 변경에 영향받지 않아 내구성 향상

### 변경 2: DB 저장 키 수정 (라인 4377~4378)
- **변경 전**: `result.get("homeResults", [])`, `result.get("blogResults", [])`
- **변경 후**: `result.get("homePosts", [])`, `result.get("blogPosts", [])`
- **근거**: `_analyze_competition()` 반환값의 실제 키와 일치시킴

## 발견 이슈 및 해결

### 자체 해결 (2건)
1. **CSS 셀렉터 전량 매칭 실패** — href 패턴 + 텍스트 기반 추출로 전환
2. **DB 저장 키 불일치로 빈 배열 저장** — `homeResults`→`homePosts`, `blogResults`→`blogPosts`로 수정

### 범위 외 미해결 (1건)
1. **`_parse_blog_url()`이 미래 URL 패턴 변경에 취약할 수 있음** — 현재 2개 패턴(`/blogId/logNo`, `?blogId=&logNo=`)은 정상 동작. 추가 패턴 발생 시 별도 대응 필요. 범위 외 사유: 현재 정상 동작하며 예방적 수정은 불필요

## 테스트 결과

### API 테스트
- `POST /api/naver-blog/competition-analysis {"keyword":"보험대리점"}`
  - status: success
  - homePosts: 5건 반환
  - blogPosts: 5건 반환
  - comparison: 정상 생성
  - 각 post에 charCount, keywordCount, keywordDensity, imageCount, headingCount 포함 확인
  - 샘플: rank=1, title="보험대리점순위 말고 내 실적 순위 올려줄 GA", charCount=1329, keywordCount=3, imageCount=12

### DB 히스토리 저장 검증
- competition_analysis 테이블: ID=2, home_results 5건, blog_results 5건 저장 확인
- keyword_analysis 테이블: `POST /api/naver-blog/keyword-analysis` 호출 후 ID=1 저장 확인 (results 529건, recommended 5건)

## 셀프 QC

- [x] 1. 영향 파일: server.py 단일 파일
- [x] 2. 엣지 케이스: 빈 검색 결과→빈 리스트, 타이틀 없는 링크→필터링, logNo 없는 프로필 URL→제외
- [x] 3. 작업 지시 정확 일치: 셀렉터 수정 + 테스트 + DB 검증 + 키워드 분석 검증
- [x] 4. 에러 처리/보안: 기존 try/except 유지, 신규 보안 이슈 없음
- [x] 5. 테스트: API 호출→결과 10건→DB 저장 전체 경로 검증
- [x] 6. 발견 이슈 직접 해결: DB 키 불일치 발견 및 수정 완료
- [x] 7. 코드 아키텍처: 단순 로직 교체, SOLID/DRY 위반 없음
- [x] 8. 인터페이스 변경 없음: 외부 API 시그니처/응답 구조 동일
- [x] 9. 이미지/배너: 해당 없음

## 모델 사용 기록

- 팀원: 토르(백엔드) / 작업 내용: server.py 셀렉터 수정 + DB 키 수정 / 사용 모델: sonnet / 정당성: -
