---
description: "Scrapling 기반 고급 웹 크롤링/스크래핑. 안티봇 우회, HTML 데이터 추출, Smart Matching 반복 크롤링. Use when: 웹 크롤링, 스크래핑, 안티봇 우회, HTML 데이터 추출."
---

# Advanced Web Crawling Skill

> Scrapling + crawl_utils 기반 고급 웹 크롤링 가이드

## 언제 이 스킬을 사용하는가

- 웹 크롤링/스크래핑 작업
- 안티봇 우회가 필요한 사이트 접근
- HTML 데이터 추출 + LLM 입력 변환
- Smart Matching이 필요한 반복 크롤링

---

## 1. 3단계 Fetcher 사용 가이드

### 선택 기준

| 시나리오 | 추천 Fetcher | 이유 |
|---|---|---|
| 단순 HTTP API / 정적 페이지 | `Fetcher` | curl_cffi 기반, TLS 핑거프린트 위장, 가장 빠름 |
| JS 렌더링 필요 (SPA, 동적 콘텐츠) | `DynamicFetcher` | Playwright Chromium, 실제 브라우저 렌더링 |
| 안티봇 / Cloudflare 차단 | `StealthyFetcher` | Patchright 기반, Turnstile 자동 솔버 |

### 사용 예시

**Fetcher — 빠른 HTTP 요청**

```python
from scrapling.fetchers import Fetcher, FetcherSession

# 단발성 요청
page = Fetcher.get('https://example.com', stealthy_headers=True)

# 세션 유지 (쿠키 공유)
with FetcherSession(impersonate='chrome') as session:
    page = session.get('https://example.com', stealthy_headers=True)
    titles = page.css('h1::text').getall()
```

**DynamicFetcher — JS 렌더링**

```python
from scrapling.fetchers import DynamicFetcher, DynamicSession

# 단발성 요청 (네트워크 완료 대기)
page = DynamicFetcher.fetch('https://example.com', network_idle=True)
items = page.css('.product::text').getall()

# 세션 유지
with DynamicSession(headless=True, network_idle=True) as session:
    page = session.fetch('https://example.com')
    data = page.xpath('//div[@class="price"]/text()').getall()
```

**StealthyFetcher — 안티봇 우회**

```python
from scrapling.fetchers import StealthyFetcher, StealthySession

# 단발성 요청
page = StealthyFetcher.fetch('https://protected.example.com', headless=True)

# Cloudflare Turnstile 자동 해제
with StealthySession(headless=True, solve_cloudflare=True) as session:
    page = session.fetch('https://cloudflare-site.example.com', google_search=False)
    data = page.css('#content a').getall()
```

---

## 2. Smart Matching 활용법

사이트 구조가 바뀌어도 요소를 자동으로 재탐지하는 기능입니다.

### auto_save — 최초 크롤링 시 지문 저장

```python
from scrapling.fetchers import Fetcher

page = Fetcher.get('https://example.com/products')

# 요소 구조(태그, 텍스트, 속성, 부모 정보)를 로컬에 저장
products = page.css('.product-card', auto_save=True)
for p in products:
    print(p.css('.name::text').get())
```

### adaptive — 재크롤링 시 자동 재매칭

```python
# 사이트가 리디자인된 이후에도 동일 셀렉터로 요소 탐지
page = Fetcher.get('https://example.com/products')
products = page.css('.product-card', adaptive=True)  # 유사도 알고리즘으로 재탐지
```

### find_similar — 유사 요소 탐색

```python
page = Fetcher.get('https://example.com/products')
first_product = page.css('.product-card').first

# 페이지 내 구조적으로 유사한 모든 요소 반환
all_products = first_product.find_similar()
for p in all_products:
    print(p.css('.price::text').get())
```

---

## 3. CSS/XPath 셀렉터 가이드

### 기본 셀렉터

```python
# CSS — 단일 요소
title = page.css('h1').first

# CSS — 복수 요소
items = page.css('.item')

# XPath
prices = page.xpath('//span[@class="price"]')

# BeautifulSoup 스타일
divs = page.find_all('div', class_='quote')
```

### `::text` 의사요소 — 텍스트 추출

```python
# 단일 텍스트
title = page.css('h1::text').get()

# 전체 텍스트 목록
all_titles = page.css('h2::text').getall()
```

### `::attr(name)` 의사요소 — 속성 추출

```python
# 단일 href
link = page.css('a::attr(href)').get()

# 전체 링크 목록
links = page.css('a::attr(href)').getall()

# data-* 속성
ids = page.css('.item::attr(data-id)').getall()
```

### TextHandler 체이닝

`::text` 결과는 `TextHandler` (str 서브클래스)로 반환됩니다. 표준 str 메서드를 그대로 체이닝할 수 있습니다.

```python
# strip, split, replace 등 str 메서드 직접 사용
price_text = page.css('.price::text').get()
price_value = price_text.strip().replace(',', '')  # "1,200" → "1200"

# 정규식 검색
import re
match = re.search(r'\d+', price_text)

# 하위 셀렉터 체이닝
author = page.css('.quote').css('.author::text').get()
```

---

## 4. 프록시 로테이션 + 자동 재시도

`/home/jay/workspace/scripts/crawl_utils.py` 의 유틸리티를 사용합니다.

### ProxyRotator 사용법 (crawl_utils)

```python
from crawl_utils import ProxyRotator

proxies = [
    'http://user:pass@proxy1.example.com:8080',
    'http://user:pass@proxy2.example.com:8080',
    'http://user:pass@proxy3.example.com:8080',
]

# round_robin (기본) 또는 random 전략
rotator = ProxyRotator(proxies, strategy="round_robin")
proxy = rotator.get_next()  # 순서대로 반환
rotator.remove(proxy)       # 실패한 프록시 제거
print(len(rotator))         # 남은 프록시 수
```

### fetch_with_retry 사용법 (crawl_utils)

```python
from crawl_utils import ProxyRotator, fetch_with_retry
from scrapling.fetchers import Fetcher

# 기본 사용 (3회 재시도, 지수 백오프)
page = fetch_with_retry('https://example.com')

# 프록시 로테이션 + 재시도
rotator = ProxyRotator(['http://p1:8080', 'http://p2:8080'])
page = fetch_with_retry(
    'https://example.com',
    max_retries=5,
    proxy_rotator=rotator,
    fetcher_class=Fetcher,
)
# 프록시 오류 시 자동으로 해당 프록시 제거 + 다음 프록시로 재시도
```

---

## 5. 리소스 차단 설정 가이드

`DynamicFetcher` / `DynamicSession` 에서 불필요한 리소스를 차단해 속도를 높입니다.

### 프리셋

```python
from scrapling.fetchers import DynamicSession

# default — 이미지·폰트만 차단 (일반 크롤링)
with DynamicSession(headless=True) as session:
    page = session.fetch('https://example.com', disable_resources=True)

# blocked_domains — 특정 도메인(광고·분석 트래커) 차단
with DynamicSession(
    headless=True,
    blocked_domains=['ads.example.com', 'tracker.example.com'],
) as session:
    page = session.fetch('https://example.com')
```

### 커스터마이즈 — browser.py CDP 래퍼 활용

`/home/jay/workspace/scripts/browser.py` 의 스텔스 모드 Playwright CDP 래퍼를 사용하면 리소스 타입별로 세밀하게 제어할 수 있습니다.

```python
from browser import StealthBrowser

# 이미지·스타일시트·폰트·미디어 차단 (aggressive)
blocked_types = ['image', 'stylesheet', 'font', 'media', 'other']

with StealthBrowser(headless=True) as browser:
    page = browser.fetch(
        'https://example.com',
        block_resource_types=blocked_types,
    )
    data = page.css('.content::text').getall()
```

---

## 6. HTML → Markdown 변환 (LLM 입력)

`crawl_utils.py` 의 변환 유틸리티를 사용합니다.

### html_to_markdown 사용법

```python
from crawl_utils import html_to_markdown
from scrapling.fetchers import Fetcher

page = Fetcher.get('https://example.com/article')

# 전체 페이지 HTML을 마크다운으로 변환
md = html_to_markdown(page.html)

# 특정 요소만 변환
article_html = page.css('article').first.html
md = html_to_markdown(article_html)

# LLM에 바로 전달
llm_input = f"다음 문서를 요약하세요:\n\n{md}"
```

### clean_html 사용법

```python
from crawl_utils import clean_html
from scrapling.fetchers import Fetcher

page = Fetcher.get('https://example.com/article')

# script/style/noscript/svg 태그 + onclick/style 속성 제거
clean = clean_html(page.html)

# href, src, class 등 유용한 속성은 유지됨
# 이후 파싱 또는 마크다운 변환에 활용
md = html_to_markdown(clean)
```

---

## 7. 주의사항

- **합법적 용도에만 사용** — 크롤링은 대상 사이트의 법적 허용 범위 내에서만 수행합니다.
- **사이트 ToS 확인 필수** — 서비스 이용약관에 크롤링 금지 조항이 있으면 진행하지 않습니다.
- **robots.txt 존중** — 크롤링 전 `robots.txt` 를 확인하고 `Disallow` 경로는 접근하지 않습니다.
- **과도한 요청 자제** — 짧은 시간에 대량 요청을 보내면 서버에 부하를 주고 IP 차단으로 이어집니다. `time.sleep` 또는 `retry_delay` 로 요청 간격을 조절하세요.
