@kood/claude-code 0.5.9 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/index.js +127 -135
  2. package/package.json +1 -1
  3. package/templates/.claude/agents/build-fixer.md +371 -0
  4. package/templates/.claude/agents/critic.md +223 -0
  5. package/templates/.claude/agents/deep-executor.md +320 -0
  6. package/templates/.claude/agents/dependency-manager.md +0 -1
  7. package/templates/.claude/agents/deployment-validator.md +0 -1
  8. package/templates/.claude/agents/designer.md +0 -1
  9. package/templates/.claude/agents/document-writer.md +0 -1
  10. package/templates/.claude/agents/git-operator.md +15 -0
  11. package/templates/.claude/agents/implementation-executor.md +0 -1
  12. package/templates/.claude/agents/ko-to-en-translator.md +0 -1
  13. package/templates/.claude/agents/lint-fixer.md +0 -1
  14. package/templates/.claude/agents/planner.md +11 -7
  15. package/templates/.claude/agents/qa-tester.md +488 -0
  16. package/templates/.claude/agents/researcher.md +189 -0
  17. package/templates/.claude/agents/scientist.md +544 -0
  18. package/templates/.claude/agents/security-reviewer.md +549 -0
  19. package/templates/.claude/agents/tdd-guide.md +413 -0
  20. package/templates/.claude/agents/vision.md +165 -0
  21. package/templates/.claude/commands/pre-deploy.md +79 -2
  22. package/templates/.claude/instructions/agent-patterns/model-routing.md +2 -2
  23. package/templates/.claude/skills/brainstorm/SKILL.md +889 -0
  24. package/templates/.claude/skills/bug-fix/SKILL.md +69 -0
  25. package/templates/.claude/skills/crawler/SKILL.md +156 -0
  26. package/templates/.claude/skills/crawler/references/anti-bot-checklist.md +162 -0
  27. package/templates/.claude/skills/crawler/references/code-templates.md +119 -0
  28. package/templates/.claude/skills/crawler/references/crawling-patterns.md +167 -0
  29. package/templates/.claude/skills/crawler/references/document-templates.md +147 -0
  30. package/templates/.claude/skills/crawler/references/network-crawling.md +141 -0
  31. package/templates/.claude/skills/crawler/references/playwriter-commands.md +172 -0
  32. package/templates/.claude/skills/crawler/references/pre-crawl-checklist.md +221 -0
  33. package/templates/.claude/skills/crawler/references/selector-strategies.md +140 -0
  34. package/templates/.claude/skills/execute/SKILL.md +5 -0
  35. package/templates/.claude/skills/feedback/SKILL.md +570 -0
  36. package/templates/.claude/skills/figma-to-code/SKILL.md +1 -0
  37. package/templates/.claude/skills/global-uiux-design/SKILL.md +1 -0
  38. package/templates/.claude/skills/korea-uiux-design/SKILL.md +1 -0
  39. package/templates/.claude/skills/nextjs-react-best-practices/SKILL.md +1 -0
  40. package/templates/.claude/skills/plan/SKILL.md +44 -0
  41. package/templates/.claude/skills/ralph/SKILL.md +16 -18
  42. package/templates/.claude/skills/refactor/SKILL.md +19 -0
  43. package/templates/.claude/skills/tanstack-start-react-best-practices/SKILL.md +1 -0
  44. package/templates/.claude/skills/stitch-design/README.md +0 -34
  45. package/templates/.claude/skills/stitch-design/SKILL.md +0 -213
  46. package/templates/.claude/skills/stitch-design/examples/DESIGN.md +0 -154
  47. package/templates/.claude/skills/stitch-loop/README.md +0 -54
  48. package/templates/.claude/skills/stitch-loop/SKILL.md +0 -316
  49. package/templates/.claude/skills/stitch-loop/examples/SITE.md +0 -73
  50. package/templates/.claude/skills/stitch-loop/examples/next-prompt.md +0 -25
  51. package/templates/.claude/skills/stitch-loop/resources/baton-schema.md +0 -61
  52. package/templates/.claude/skills/stitch-loop/resources/site-template.md +0 -104
  53. package/templates/.claude/skills/stitch-react/README.md +0 -36
  54. package/templates/.claude/skills/stitch-react/SKILL.md +0 -323
  55. package/templates/.claude/skills/stitch-react/examples/gold-standard-card.tsx +0 -88
  56. package/templates/.claude/skills/stitch-react/package-lock.json +0 -231
  57. package/templates/.claude/skills/stitch-react/package.json +0 -16
  58. package/templates/.claude/skills/stitch-react/resources/architecture-checklist.md +0 -15
  59. package/templates/.claude/skills/stitch-react/resources/component-template.tsx +0 -37
  60. package/templates/.claude/skills/stitch-react/resources/stitch-api-reference.md +0 -14
  61. package/templates/.claude/skills/stitch-react/resources/style-guide.json +0 -24
  62. package/templates/.claude/skills/stitch-react/scripts/fetch-stitch.sh +0 -30
  63. package/templates/.claude/skills/stitch-react/scripts/validate.js +0 -77
@@ -359,6 +359,10 @@ Task(subagent_type="architect", model="opus", ...)
359
359
  | 검증 | code-reviewer | opus | 수정 후 코드 리뷰, 회귀 검증 |
360
360
  | 린트 | lint-fixer | sonnet | tsc/eslint 오류 수정 |
361
361
  | 문서 | document-writer | haiku/sonnet | 버그 리포트, 수정 내역 문서화 |
362
+ | 보안 | security-reviewer | opus | 보안 취약점 버그, SQL Injection, XSS |
363
+ | 테스트 | qa-tester | sonnet | 수정 후 CLI/서비스 테스트 검증 |
364
+ | 조사 | researcher | sonnet | 외부 라이브러리 버그, API 문서 조사 |
365
+ | 시각 | vision | sonnet | UI 버그 스크린샷 분석, 레이아웃 검증 |
362
366
 
363
367
  ### Bug Severity별 병렬 처리
364
368
 
@@ -803,6 +807,71 @@ Task({
803
807
  // → 총 소요 시간: 15-18분
804
808
  ```
805
809
 
810
+ #### 예시 5: 보안 버그 수정 + 전체 스캔
811
+
812
+ **상황:** SQL Injection 취약점 발견 및 유사 취약점 스캔 필요
813
+
814
+ ```typescript
815
+ // ✅ 보안 버그 수정 + 검증 병렬
816
+ Task({
817
+ subagent_type: 'implementation-executor',
818
+ model: 'sonnet',
819
+ prompt: 'SQL Injection 취약점 수정'
820
+ })
821
+ Task({
822
+ subagent_type: 'security-reviewer',
823
+ model: 'opus',
824
+ prompt: '유사 보안 취약점 전체 스캔'
825
+ })
826
+
827
+ // → 수정과 동시에 다른 보안 취약점 발견
828
+ ```
829
+
830
+ #### 예시 6: 버그 수정 후 테스트 검증
831
+
832
+ **상황:** 인증 로직 수정 후 실제 서비스 테스트 필요
833
+
834
+ ```typescript
835
+ // ✅ 버그 수정 후 테스트 검증
836
+ Task({
837
+ subagent_type: 'implementation-executor',
838
+ model: 'sonnet',
839
+ prompt: '인증 버그 수정: 토큰 재발급 로직 개선'
840
+ })
841
+
842
+ // 수정 완료 후 테스트
843
+ Task({
844
+ subagent_type: 'qa-tester',
845
+ model: 'sonnet',
846
+ prompt: 'tmux 세션으로 수정된 기능 테스트: 로그인 → 토큰 만료 → 재발급 시나리오'
847
+ })
848
+ ```
849
+
850
+ #### 예시 7: 라이브러리 버그 조사
851
+
852
+ **상황:** 외부 라이브러리 버전 업그레이드 후 오류 발생
853
+
854
+ ```typescript
855
+ // ✅ 라이브러리 버그 조사 + 수정 병렬
856
+ Task({
857
+ subagent_type: 'researcher',
858
+ model: 'sonnet',
859
+ prompt: 'TanStack Query v5.60.0 breaking changes 조사 및 마이그레이션 가이드 탐색'
860
+ })
861
+ Task({
862
+ subagent_type: 'explore',
863
+ model: 'haiku',
864
+ prompt: '현재 코드베이스에서 TanStack Query 사용 위치 전체 탐색'
865
+ })
866
+
867
+ // → 조사 결과 기반으로 수정
868
+ Task({
869
+ subagent_type: 'implementation-executor',
870
+ model: 'sonnet',
871
+ prompt: '[조사 결과 기반] Breaking changes 대응 코드 수정'
872
+ })
873
+ ```
874
+
806
875
  ### Bug Fix Workflow with Agents
807
876
 
808
877
  | 단계 | 작업 | 에이전트 | 모델 | 병렬 실행 |
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: crawler
3
+ description: Playwriter로 웹사이트 직접 탐방하여 크롤링 설계. API/쿠키/토큰/헤더 분석 후 문서화.
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Crawler Skill
8
+
9
+ > Playwriter 탐방 → API/Network 분석 → 문서화 → 코드 생성
10
+
11
+ **Templates:** [document-templates.md](references/document-templates.md) · [code-templates.md](references/code-templates.md)
12
+ **Checklists:** [pre-crawl-checklist.md](references/pre-crawl-checklist.md) · [anti-bot-checklist.md](references/anti-bot-checklist.md)
13
+ **References:** [playwriter-commands.md](references/playwriter-commands.md) · [crawling-patterns.md](references/crawling-patterns.md) · [selector-strategies.md](references/selector-strategies.md) · [network-crawling.md](references/network-crawling.md)
14
+
15
+ ---
16
+
17
+ <trigger_conditions>
18
+
19
+ | 트리거 | 반응 |
20
+ |--------|------|
21
+ | 크롤링, 스크래핑, crawl, scrape | 즉시 실행 |
22
+ | 웹사이트 데이터 추출 | 즉시 실행 |
23
+ | API 리버스 엔지니어링 | API 인터셉트 |
24
+ | 봇 탐지 우회 | Anti-Detect 참고 |
25
+
26
+ </trigger_conditions>
27
+
28
+ ---
29
+
30
+ <workflow>
31
+
32
+ | Phase | 작업 | 명령어 |
33
+ |-------|------|--------|
34
+ | **1. 세션** | 생성 + 페이지 열기 | `playwriter session new` |
35
+ | **2. 탐색** | 구조 파악 | `accessibilitySnapshot`, `screenshotWithAccessibilityLabels` |
36
+ | **3. 분석** | API 인터셉트, Selector 추출 | `page.on('response')`, `getLocatorStringForElement` |
37
+ | **4. 문서화** | `.claude/crawler/[사이트]/` 저장 | Write |
38
+ | **5. 코드** | 크롤러 생성 | [code-templates.md](references/code-templates.md) |
39
+
40
+ </workflow>
41
+
42
+ ---
43
+
44
+ <quick_commands>
45
+
46
+ ```bash
47
+ # 세션 생성 + 페이지 열기
48
+ playwriter session new
49
+ playwriter -s 1 -e "state.page = await context.newPage(); await state.page.goto('https://target.com')"
50
+
51
+ # 구조 파악
52
+ playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page: state.page }))"
53
+
54
+ # API 인터셉트
55
+ playwriter -s 1 -e $'
56
+ state.responses = [];
57
+ state.page.on("response", async res => {
58
+ if (res.url().includes("/api/")) {
59
+ try { state.responses.push({ url: res.url(), body: await res.json() }); } catch {}
60
+ }
61
+ });
62
+ '
63
+
64
+ # 인증 추출
65
+ playwriter -s 1 -e "console.log(JSON.stringify(await context.cookies(), null, 2))"
66
+ playwriter -s 1 -e "console.log(await state.page.evaluate(() => localStorage.getItem('token')))"
67
+
68
+ # Selector 변환
69
+ playwriter -s 1 -e "console.log(await getLocatorStringForElement(state.page.locator('aria-ref=e14')))"
70
+ ```
71
+
72
+ </quick_commands>
73
+
74
+ ---
75
+
76
+ <method_selection>
77
+
78
+ | 조건 | 방식 | 비고 |
79
+ |------|------|------|
80
+ | API 발견 + 인증 단순 | **fetch** | 가장 빠름 |
81
+ | API + 쿠키/토큰 필요 | **fetch + Cookie** | 만료 관리 필요 |
82
+ | 봇 탐지 강함 | **Nstbrowser** | Anti-Detect |
83
+ | API 없음 (SSR) | **Playwright DOM** | 직접 파싱 |
84
+
85
+ </method_selection>
86
+
87
+ ---
88
+
89
+ <output_structure>
90
+
91
+ ```
92
+ .claude/crawler/[사이트명]/
93
+ ├── ANALYSIS.md # 사이트 구조
94
+ ├── SELECTORS.md # DOM selector
95
+ ├── API.md # API endpoint
96
+ ├── NETWORK.md # 인증 정보
97
+ └── CRAWLER.ts # 생성 코드
98
+ ```
99
+
100
+ **Templates:** [document-templates.md](references/document-templates.md)
101
+
102
+ </output_structure>
103
+
104
+ ---
105
+
106
+ <validation>
107
+
108
+ ```text
109
+ ✅ playwriter 세션 생성
110
+ ✅ accessibilitySnapshot 구조 파악
111
+ ✅ API 인터셉트 시도
112
+ ✅ selector 추출 검증
113
+ ✅ .claude/crawler/ 문서화
114
+ ✅ 크롤러 코드 생성
115
+ ```
116
+
117
+ </validation>
118
+
119
+ ---
120
+
121
+ <forbidden>
122
+
123
+ | 분류 | 금지 |
124
+ |------|------|
125
+ | **분석** | 구조 파악 없이 selector 추측 |
126
+ | **방식** | API 확인 없이 DOM만 시도 |
127
+ | **문서** | 분석 결과 문서화 생략 |
128
+ | **네트워크** | Rate limiting 미고려 |
129
+
130
+ </forbidden>
131
+
132
+ ---
133
+
134
+ <example>
135
+
136
+ ```bash
137
+ # 사용자: /crawler https://shop.example.com 상품 크롤링
138
+
139
+ # 1. 세션
140
+ playwriter session new # => 1
141
+ playwriter -s 1 -e "state.page = await context.newPage(); await state.page.goto('https://shop.example.com/products')"
142
+
143
+ # 2. 구조 파악
144
+ playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page: state.page }))"
145
+ # => list "Products" [ref=e5]: listitem [ref=e6]: link "Product A" [ref=e7]
146
+
147
+ # 3. API 확인 (스크롤 트리거)
148
+ playwriter -s 1 -e "await state.page.evaluate(() => window.scrollTo(0, 9999))"
149
+ playwriter -s 1 -e "console.log(state.responses.map(r => r.url))"
150
+ # => ["/api/products?page=2"]
151
+
152
+ # 4. 문서화 → .claude/crawler/shop-example-com/
153
+ # 5. API 기반 크롤러 생성
154
+ ```
155
+
156
+ </example>
@@ -0,0 +1,162 @@
1
+ # 봇 탐지 대응 체크리스트
2
+
3
+ > 크롤러 코드 작성 시 봇 탐지 회피 참고
4
+
5
+ ---
6
+
7
+ <fingerprint>
8
+
9
+ ## 브라우저 지문
10
+
11
+ ```bash
12
+ playwriter -s 1 -e $'
13
+ const fp = await state.page.evaluate(() => ({
14
+ webdriver: navigator.webdriver,
15
+ plugins: navigator.plugins.length,
16
+ languages: navigator.languages,
17
+ platform: navigator.platform,
18
+ }));
19
+ console.log(fp);
20
+ '
21
+ ```
22
+
23
+ | 지문 | 봇 특징 | 대응 |
24
+ |------|--------|------|
25
+ | `navigator.webdriver` | `true` | Anti-Detect |
26
+ | `plugins.length` | `0` | 스푸핑 |
27
+ | UA vs platform | 불일치 | 일관성 |
28
+ | Canvas/WebGL | 일정함 | 다양화 |
29
+ | TLS/JA3 | 비표준 | Anti-Detect |
30
+
31
+ </fingerprint>
32
+
33
+ ---
34
+
35
+ <behavior>
36
+
37
+ ## 행동 패턴
38
+
39
+ | 패턴 | 봇 | 인간 |
40
+ |------|-----|------|
41
+ | 요청 간격 | 일정 | 불규칙 |
42
+ | 클릭 | 즉시 | 호버 후 |
43
+ | 스크롤 | 점프 | 부드럽게 |
44
+ | 체류 시간 | 짧음 | 다양 |
45
+
46
+ ```bash
47
+ # 자연스러운 클릭
48
+ playwriter -s 1 -e $'
49
+ const btn = state.page.locator("button");
50
+ await btn.hover();
51
+ await state.page.waitForTimeout(100 + Math.random() * 200);
52
+ await btn.click();
53
+ '
54
+ ```
55
+
56
+ </behavior>
57
+
58
+ ---
59
+
60
+ <network>
61
+
62
+ ## 네트워크
63
+
64
+ | IP 유형 | 위험도 |
65
+ |---------|-------|
66
+ | 데이터센터 (AWS, GCP) | 높음 |
67
+ | VPN/프록시 | 중간 |
68
+ | 주거용 | 낮음 |
69
+
70
+ **헤더 체크:**
71
+ - `Accept-Language` 지역 일치
72
+ - `Referer` 적절히 설정
73
+ - 헤더 순서 브라우저와 일치
74
+
75
+ </network>
76
+
77
+ ---
78
+
79
+ <captcha>
80
+
81
+ ## CAPTCHA 대응
82
+
83
+ ```bash
84
+ playwriter -s 1 -e $'
85
+ const captcha = await state.page.evaluate(() => ({
86
+ recaptcha: !!document.querySelector(".g-recaptcha"),
87
+ hcaptcha: !!document.querySelector(".h-captcha"),
88
+ turnstile: !!document.querySelector(".cf-turnstile"),
89
+ }));
90
+ console.log(captcha);
91
+ '
92
+ ```
93
+
94
+ | CAPTCHA | 대응 |
95
+ |---------|------|
96
+ | reCAPTCHA v2 | 2captcha 서비스 |
97
+ | reCAPTCHA v3 | 행동 개선 |
98
+ | hCaptcha | 서비스 사용 |
99
+ | Turnstile | Anti-Detect |
100
+
101
+ </captcha>
102
+
103
+ ---
104
+
105
+ <test_sites>
106
+
107
+ ## 탐지 테스트
108
+
109
+ ```bash
110
+ playwriter -s 1 -e $'
111
+ await state.page.goto("https://bot.sannysoft.com/");
112
+ await state.page.screenshot({ path: "bot-test.png", scale: "css", fullPage: true });
113
+ '
114
+ ```
115
+
116
+ | 사이트 | 확인 |
117
+ |--------|------|
118
+ | bot.sannysoft.com | 종합 봇 탐지 |
119
+ | browserleaks.com | 브라우저 지문 |
120
+ | pixelscan.net | Anti-Detect 효과 |
121
+
122
+ </test_sites>
123
+
124
+ ---
125
+
126
+ <checklist>
127
+
128
+ ## 회피 체크리스트
129
+
130
+ **필수:**
131
+ ```text
132
+ ✅ User-Agent 실제 브라우저
133
+ ✅ webdriver = false
134
+ ✅ 플러그인/언어/플랫폼 일관성
135
+ ✅ 쿠키 활성화
136
+ ✅ 헤더 순서 일치
137
+ ```
138
+
139
+ **행동:**
140
+ ```text
141
+ ✅ 무작위 딜레이 (1-5초)
142
+ ✅ 클릭 전 호버
143
+ ✅ 자연스러운 스크롤
144
+ ✅ 체류 시간 다양화
145
+ ```
146
+
147
+ </checklist>
148
+
149
+ ---
150
+
151
+ <tool_selection>
152
+
153
+ ## 도구 선택
154
+
155
+ | 조건 | 도구 |
156
+ |------|------|
157
+ | 봇 탐지 없음 | Playwright |
158
+ | 기본 탐지 | Playwright + Stealth |
159
+ | 고급 탐지 | Nstbrowser |
160
+ | Cloudflare | Anti-Detect 필수 |
161
+
162
+ </tool_selection>
@@ -0,0 +1,119 @@
1
+ # 크롤러 코드 템플릿
2
+
3
+ > 분석 결과 기반 자동 생성용
4
+
5
+ ---
6
+
7
+ ## 방식 선택
8
+
9
+ | 조건 | 방식 | 템플릿 |
10
+ |------|------|--------|
11
+ | API 발견 + 인증 단순 | fetch | API 크롤러 |
12
+ | API + 쿠키/토큰 | fetch + Cookie | API 크롤러 (인증) |
13
+ | 봇 탐지 강함 | Nstbrowser | (별도 구현) |
14
+ | API 없음 (SSR) | Playwright | DOM 크롤러 |
15
+
16
+ ---
17
+
18
+ ## API 크롤러
19
+
20
+ ```typescript
21
+ // CRAWLER.ts - API 기반
22
+ interface ApiResponse {
23
+ data: Item[];
24
+ pagination: { page: number; total: number; hasNext: boolean };
25
+ }
26
+
27
+ interface Item {
28
+ id: string;
29
+ title: string;
30
+ }
31
+
32
+ export class ApiCrawler {
33
+ private baseUrl = 'https://example.com/api';
34
+ private headers: Record<string, string> = {
35
+ 'Content-Type': 'application/json',
36
+ // 'Authorization': 'Bearer ...',
37
+ // 'Cookie': '...',
38
+ };
39
+
40
+ async fetchPage(page: number, limit = 20): Promise<ApiResponse> {
41
+ const res = await fetch(
42
+ `${this.baseUrl}/items?page=${page}&limit=${limit}`,
43
+ { headers: this.headers }
44
+ );
45
+ if (!res.ok) throw new Error(`API error: ${res.status}`);
46
+ return res.json();
47
+ }
48
+
49
+ async crawlAll(): Promise<Item[]> {
50
+ const items: Item[] = [];
51
+ let page = 1;
52
+ let hasNext = true;
53
+
54
+ while (hasNext) {
55
+ const res = await this.fetchPage(page);
56
+ items.push(...res.data);
57
+ hasNext = res.pagination.hasNext;
58
+ page++;
59
+ await new Promise(r => setTimeout(r, 100)); // Rate limit
60
+ }
61
+ return items;
62
+ }
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## DOM 크롤러 (Playwright)
69
+
70
+ ```typescript
71
+ // CRAWLER.ts - DOM 기반
72
+ import { chromium, Browser, Page } from 'playwright';
73
+
74
+ interface Item {
75
+ id: string;
76
+ title: string;
77
+ url: string;
78
+ }
79
+
80
+ export class DomCrawler {
81
+ private browser: Browser | null = null;
82
+ private page: Page | null = null;
83
+
84
+ async init(): Promise<void> {
85
+ this.browser = await chromium.launch({ headless: true });
86
+ this.page = await this.browser.newPage();
87
+ }
88
+
89
+ async crawlList(url: string): Promise<Item[]> {
90
+ if (!this.page) throw new Error('Not initialized');
91
+ await this.page.goto(url, { waitUntil: 'domcontentloaded' });
92
+
93
+ return this.page.$$eval('.item-card', cards =>
94
+ cards.map(card => ({
95
+ id: card.getAttribute('data-id') || '',
96
+ title: card.querySelector('h2')?.textContent?.trim() || '',
97
+ url: card.querySelector('a')?.href || '',
98
+ }))
99
+ );
100
+ }
101
+
102
+ async crawlAllPages(baseUrl: string): Promise<Item[]> {
103
+ const items: Item[] = [];
104
+ let page = 1;
105
+
106
+ while (true) {
107
+ const result = await this.crawlList(`${baseUrl}?page=${page}`);
108
+ if (result.length === 0) break;
109
+ items.push(...result);
110
+ page++;
111
+ }
112
+ return items;
113
+ }
114
+
115
+ async close(): Promise<void> {
116
+ await this.browser?.close();
117
+ }
118
+ }
119
+ ```
@@ -0,0 +1,167 @@
1
+ # 크롤링 패턴
2
+
3
+ > 데이터 로딩, 페이지네이션, 인증 패턴
4
+
5
+ ---
6
+
7
+ <rendering>
8
+
9
+ ## 렌더링 방식
10
+
11
+ | 방식 | 특징 | 전략 |
12
+ |------|------|------|
13
+ | **SSR** | HTML에 데이터 포함 | DOM 파싱 |
14
+ | **CSR** | JS로 API 호출 | API 직접 호출 |
15
+
16
+ ```javascript
17
+ // SSR/CSR 판별
18
+ const html = await page.content();
19
+ console.log(html.length > 5000 ? 'SSR 가능' : 'CSR');
20
+ ```
21
+
22
+ </rendering>
23
+
24
+ ---
25
+
26
+ <pagination>
27
+
28
+ ## 페이지네이션
29
+
30
+ ### URL 기반
31
+
32
+ ```typescript
33
+ for (let page = 1; ; page++) {
34
+ const items = await fetch(`${baseUrl}?page=${page}`).then(r => r.json());
35
+ if (items.length === 0) break;
36
+ allItems.push(...items);
37
+ }
38
+ ```
39
+
40
+ ### 커서 기반
41
+
42
+ ```typescript
43
+ let cursor: string | null = null;
44
+ do {
45
+ const data = await fetch(cursor ? `${url}?cursor=${cursor}` : url).then(r => r.json());
46
+ allItems.push(...data.items);
47
+ cursor = data.nextCursor;
48
+ } while (cursor);
49
+ ```
50
+
51
+ ### 무한 스크롤
52
+
53
+ ```javascript
54
+ // API 인터셉트 후 스크롤
55
+ for (let i = 0; i < 10; i++) {
56
+ await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
57
+ await page.waitForTimeout(1000);
58
+ }
59
+ ```
60
+
61
+ ### 더보기 버튼
62
+
63
+ ```javascript
64
+ const btn = page.locator('button:has-text("더보기")');
65
+ while (await btn.isVisible()) {
66
+ await btn.click();
67
+ await page.waitForLoadState('networkidle');
68
+ }
69
+ ```
70
+
71
+ </pagination>
72
+
73
+ ---
74
+
75
+ <auth>
76
+
77
+ ## 인증 패턴
78
+
79
+ ### 쿠키/세션
80
+
81
+ ```javascript
82
+ const cookies = await context.cookies();
83
+ const session = cookies.find(c => c.name === 'session');
84
+
85
+ await fetch(url, {
86
+ headers: { 'Cookie': `session=${session.value}` }
87
+ });
88
+ ```
89
+
90
+ ### Bearer 토큰
91
+
92
+ ```javascript
93
+ const token = await page.evaluate(() => localStorage.getItem('token'));
94
+
95
+ await fetch(url, {
96
+ headers: { 'Authorization': `Bearer ${token}` }
97
+ });
98
+ ```
99
+
100
+ </auth>
101
+
102
+ ---
103
+
104
+ <dynamic>
105
+
106
+ ## 동적 콘텐츠
107
+
108
+ ### Lazy Loading
109
+
110
+ ```javascript
111
+ // 스크롤로 이미지 로드
112
+ await page.evaluate(async () => {
113
+ for (let y = 0; y < document.body.scrollHeight; y += 500) {
114
+ window.scrollTo(0, y);
115
+ await new Promise(r => setTimeout(r, 200));
116
+ }
117
+ });
118
+ ```
119
+
120
+ ### Shadow DOM
121
+
122
+ ```javascript
123
+ const content = await page.evaluate(() => {
124
+ return document.querySelector('custom-element').shadowRoot.innerHTML;
125
+ });
126
+ ```
127
+
128
+ ### iframe
129
+
130
+ ```javascript
131
+ const frame = page.frameLocator('#content-frame');
132
+ const items = await frame.locator('.item').allTextContents();
133
+ ```
134
+
135
+ </dynamic>
136
+
137
+ ---
138
+
139
+ <rate_limit>
140
+
141
+ ## Rate Limiting
142
+
143
+ ```typescript
144
+ // 딜레이 적용
145
+ async function rateLimitedFetch(urls: string[], delayMs = 100) {
146
+ const results = [];
147
+ for (const url of urls) {
148
+ results.push(await fetch(url).then(r => r.json()));
149
+ await new Promise(r => setTimeout(r, delayMs));
150
+ }
151
+ return results;
152
+ }
153
+
154
+ // 재시도
155
+ async function fetchWithRetry(url: string, retries = 3) {
156
+ for (let i = 0; i < retries; i++) {
157
+ const res = await fetch(url);
158
+ if (res.status === 429) {
159
+ await new Promise(r => setTimeout(r, (parseInt(res.headers.get('Retry-After') || '60')) * 1000));
160
+ continue;
161
+ }
162
+ return res.json();
163
+ }
164
+ }
165
+ ```
166
+
167
+ </rate_limit>