# task-571.2 완료 보고서

> 팀: dev2-team | 팀장: 오딘 | 작업일: 2026-03-15

---

## SCQA 보고서

**S**: Phase 1(task-571.1)에서 browser.py에 55개 스텔스 플래그를 추가하여 기본 안티봇 회피 인프라를 구축했다. Phase 2에서는 Scrapling 라이브러리를 설치하고 크롤링 유틸리티 모듈 + 스킬 문서를 구축해야 한다.

**C**: Scrapling 0.4.2는 3단계 Fetcher(HTTP/Dynamic/Stealthy)를 제공하지만, 프록시 로테이션/자동 재시도/HTML→마크다운 변환 등 실무 크롤링에 필요한 유틸리티는 별도 래핑이 필요하다. 또한 AI 에이전트가 크롤링 패턴을 참조할 스킬 문서가 없다.

**Q**: Scrapling 설치 + 프록시/재시도/변환 유틸리티 + 크롤링 스킬 문서를 한 번에 구축하여 Phase 3(크롤러 프로토타입)의 기반을 마련할 수 있는가?

**A**: Scrapling 0.4.2를 전체 의존성(`[all]`)으로 설치하고, crawl_utils.py(290줄, 6개 공개 API)를 구현하고, advanced-crawling SKILL.md(307줄)를 작성했다. pytest 53건 신규 + 601건 기존 = 654건 전체 통과, pyright 에러 0건.

---

## 작업 내용

### 1. Scrapling 설치 (완료)
- `pip install scrapling[all]` — scrapling 0.4.2 + curl_cffi 0.14.0 + patchright 1.58.2 + markdownify 1.2.2 등 21개 패키지 설치
- `scrapling install` — install-deps 권한 문제로 부분 실패하나, 기존 Playwright Chromium 1208/1212 활용 가능
- import 테스트: `Fetcher`, `DynamicFetcher`, `StealthyFetcher` 모두 정상 import 확인

### 2. crawl_utils.py (신규, 290줄)
- **ProxyRotator**: round_robin/random 전략, get_next/remove/__len__ API
- **is_proxy_error()**: OSError 계열(ConnectionError/TimeoutError) → True, HTTP 에러 → False
- **fetch_with_retry()**: 최대 N회 재시도 + 지수 백오프 + 프록시 에러 시 자동 제거
- **get_resource_block_types()**: default/minimal/aggressive 3개 프리셋
- **html_to_markdown()**: markdownify 기반 변환 + script/style/noscript/svg 노이즈 제거 + 빈줄 정리
- **clean_html()**: lxml.html 기반 노이즈 태그 + 이벤트 핸들러/style 속성 제거

### 3. SKILL.md (신규, 307줄)
- 7개 섹션: 3단계 Fetcher 가이드, Smart Matching, CSS/XPath 셀렉터, 프록시/재시도, 리소스 차단, HTML→Markdown, 주의사항
- 복사-붙여넣기 가능한 코드 예시 포함

---

## 생성/수정 파일

- `/home/jay/workspace/scripts/crawl_utils.py` — 신규 290줄
- `/home/jay/workspace/scripts/tests/test_crawl_utils.py` — 신규 53건 테스트
- `/home/jay/workspace/skills/advanced-crawling/SKILL.md` — 신규 307줄

---

## 테스트 결과

- **신규 테스트**: 53/53 PASSED (6.21s)
  - ProxyRotator: round_robin 3건, random 2건, 빈 리스트 3건, remove/len 5건
  - is_proxy_error: 7건 (ConnectionError/TimeoutError/OSError=True, ValueError/Exception/HTTP=False)
  - fetch_with_retry: 5건 (성공, 재시도 후 성공, 전체 실패, rotator 사용, 프록시 제거)
  - get_resource_block_types: 8건 (3 프리셋 + 타입 검증)
  - html_to_markdown: 10건 (변환 + 노이즈 제거 + 빈줄)
  - clean_html: 10건 (태그 제거 + 속성 제거 + 텍스트 보존)
- **기존 테스트**: 601/601 PASSED — 회귀 없음
- **전체 합계**: 654/654 PASSED (12.09s)
- **pyright**: 0 errors, 0 warnings

---

## 발견 이슈

1. **scrapling install 부분 실패** (LOW): `playwright install-deps chromium` 실행 시 시스템 패키지 설치 권한 부족. 이미 Playwright Chromium 1208/1212가 설치되어 있으므로 실제 사용에 문제 없음.
2. **markdownify script 태그 무시** (RESOLVED): markdownify는 기본적으로 script 태그 내용을 무시. `remove_noise=False`일 때 BS4로 script를 텍스트 노드로 교체하는 우회 로직 적용.
3. **lxml.html 한국어 인코딩** (RESOLVED): `etree.HTMLParser` + `etree.tostring(encoding="unicode")`는 한국어를 Latin-1로 해석. `lxml.html` 모듈 전용 함수로 전환하여 해결.

---

## 다음 Phase 연결

이 작업은 한정승인 5-Phase 체인의 Phase 2이다.
- **다음 Phase 지시서**: `memory/tasks/task-571.3.md`
- 내용: Smart Matching 활용 보험사 크롤러 프로토타입

---

## 셀프 QC

- [x] 1. 다른 파일 영향: 신규 파일 3개만 생성. 기존 601건 테스트 전체 통과.
- [x] 2. 엣지 케이스: 빈 프록시 리스트→None, 빈 HTML→빈 문자열, lxml 실패→정규식 fallback
- [x] 3. 작업 지시 일치: Scrapling 설치 + crawl_utils.py(N-1,N-2,N-3,P-5) + SKILL.md 모두 구현
- [x] 4. 에러/보안: 합법적 용도 경고 SKILL.md에 명시, clean_html에서 이벤트 핸들러 제거
- [x] 5. 테스트 커버리지: 53건 (요구 최소 8건의 6.6배)

---

## QC 자동 검증

```json
{
  "task_id": "task-571.2",
  "verified_at": "2026-03-15T04:18:08",
  "overall": "PASS",
  "summary": "7 PASS, 3 SKIP",
  "checks": {
    "file_check": "PASS (crawl_utils.py 10185 bytes, test 27326 bytes, SKILL.md 8499 bytes, report 4855 bytes)",
    "data_integrity": "PASS",
    "test_runner": "PASS (654 passed in 12.12s)",
    "tdd_check": "PASS (테스트+구현 파일 모두 존재)",
    "pyright_check": "PASS (0 errors, 0 warnings)",
    "style_check": "PASS (black OK, isort OK)",
    "critical_gap": "PASS",
    "api_health": "SKIP (서버 작업 아님)",
    "schema_contract": "SKIP",
    "scope_check": "SKIP"
  }
}
```
