@d-zero/beholder 0.1.28 → 2.0.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 (49) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +172 -477
  3. package/dist/debug.d.ts +4 -1
  4. package/dist/debug.js +5 -2
  5. package/dist/dom-evaluation.d.ts +72 -14
  6. package/dist/dom-evaluation.js +169 -43
  7. package/dist/index.d.ts +20 -3
  8. package/dist/index.js +15 -3
  9. package/dist/is-error.d.ts +8 -0
  10. package/dist/is-error.js +10 -0
  11. package/dist/keyword-check.d.ts +5 -3
  12. package/dist/keyword-check.js +5 -3
  13. package/dist/parse-url.d.ts +14 -0
  14. package/dist/parse-url.js +23 -0
  15. package/dist/scraper.d.ts +39 -13
  16. package/dist/scraper.js +300 -263
  17. package/dist/types.d.ts +286 -214
  18. package/dist/types.js +6 -0
  19. package/package.json +7 -10
  20. package/src/debug.ts +5 -2
  21. package/src/dom-evaluation.ts +195 -65
  22. package/src/index.ts +27 -3
  23. package/src/is-error.spec.ts +33 -0
  24. package/src/is-error.ts +10 -0
  25. package/src/keyword-check.spec.ts +45 -4
  26. package/src/keyword-check.ts +5 -3
  27. package/src/parse-url.spec.ts +35 -0
  28. package/src/parse-url.ts +26 -0
  29. package/src/scraper.ts +338 -300
  30. package/src/types.ts +345 -258
  31. package/tsconfig.tsbuildinfo +1 -1
  32. package/dist/events.d.ts +0 -32
  33. package/dist/events.js +0 -15
  34. package/dist/fetch-destination.d.ts +0 -8
  35. package/dist/fetch-destination.js +0 -145
  36. package/dist/net-timeout-error.d.ts +0 -3
  37. package/dist/net-timeout-error.js +0 -3
  38. package/dist/sub-process-runner.d.ts +0 -12
  39. package/dist/sub-process-runner.js +0 -180
  40. package/dist/sub-process.d.ts +0 -1
  41. package/dist/sub-process.js +0 -67
  42. package/dist/utils.d.ts +0 -16
  43. package/dist/utils.js +0 -69
  44. package/src/events.ts +0 -21
  45. package/src/fetch-destination.ts +0 -173
  46. package/src/net-timeout-error.ts +0 -3
  47. package/src/sub-process-runner.ts +0 -220
  48. package/src/sub-process.ts +0 -86
  49. package/src/utils.ts +0 -89
package/README.md CHANGED
@@ -1,602 +1,297 @@
1
1
  # `@d-zero/beholder`
2
2
 
3
- Webページのスクレイピングと記録を行うツールです。
3
+ Puppeteer を使用してWebページをスクレイピングし、メタデータ・リンク・画像・ネットワークリソースを収集するライブラリです。
4
4
 
5
- **名前の由来**: **[Beholder](https://forgottenrealms.fandom.com/wiki/Beholder)** という名前は、多くの目を持つ神話上の生物に由来しています。この生物のように、このツールは周囲のすべてを観察する能力を象徴しており、Webページを正確かつ徹底的にスクレイピングして記録し、詳細を見逃しません。
5
+ **名前の由来**: **[Beholder](https://forgottenrealms.fandom.com/wiki/Beholder)** という名前は、多くの目を持つ神話上の生物に由来しています。この生物のように、このツールはWebページを正確かつ徹底的にスクレイピングして記録し、詳細を見逃しません。
6
6
 
7
7
  ## インストール
8
8
 
9
- ```bash
10
- npm install @d-zero/beholder
11
- ```
12
-
13
- または
14
-
15
9
  ```bash
16
10
  yarn add @d-zero/beholder
17
11
  ```
18
12
 
19
13
  ## 概要
20
14
 
21
- `@d-zero/beholder` は、Puppeteerを使用してWebページをスクレイピングし、ページのメタデータ、画像、リンク、およびHTML内容を収集するための強力なツールです。サブプロセスでスクレイピングを実行することで、メモリ効率的な運用が可能です。
22
-
23
- ## エクスポートされるAPI
15
+ `@d-zero/beholder` は、Puppeteer の `Page` オブジェクトを受け取り、単一ページのスクレイピングを行うインプロセス型のスクレイパーです。
24
16
 
25
- ### クラス
17
+ **主な特徴:**
26
18
 
27
- #### `Scraper` (デフォルトエクスポート)
28
-
29
- メインのスクレイパークラスです。Puppeteerを使用してWebページをスクレイピングし、イベントを通じて進捗状況を通知します。
30
-
31
- ```typescript
32
- import Scraper from '@d-zero/beholder';
33
- ```
19
+ - 結果は `ScrapeResult` として戻り値で返却(イベント経由ではない)
20
+ - ストリーミングイベント(`changePhase`, `resourceResponse`)で進捗を監視可能
21
+ - キーワード・パス除外によるページスキップ
22
+ - 複数デバイスプリセット対応のレスポンシブ画像キャプチャ
23
+ - ブラウザ管理は呼び出し側の責任(Scraperはページ操作のみ)
34
24
 
35
- ##### メソッド
36
-
37
- ###### `scrapeStart(url: ExURL, options?: Partial<ScraperOptions>, isSkip?: boolean): Promise<PageData | void>`
38
-
39
- 指定されたURLのスクレイピングを開始します。
40
-
41
- **パラメータ:**
42
-
43
- - `url` (`ExURL`): スクレイピング対象のURL
44
- - `options` (`Partial<ScraperOptions>`, オプション): スクレイピングオプション
45
- - `isExternal` (`boolean`): 外部URLかどうか (デフォルト: `false`)
46
- - `isGettingImages` (`boolean`): 画像情報を取得するかどうか (デフォルト: `true`)
47
- - `excludeKeywords` (`string[]`): 除外するキーワードのリスト (デフォルト: `[]`)
48
- - `executablePath` (`string | null`): Chromeの実行可能ファイルパス (デフォルト: `null`)
49
- - `isTitleOnly` (`boolean`): タイトルのみを取得するかどうか (デフォルト: `false`)
50
- - `screenshot` (`string | null`): スクリーンショットの保存パス (デフォルト: `null`)
51
- - `disableQueries` (`boolean`, オプション): クエリパラメータを無効にするかどうか
52
- - `isSkip` (`boolean`, オプション): スキップするかどうか (デフォルト: `false`)
53
-
54
- **戻り値:**
25
+ ## エクスポートされるAPI
55
26
 
56
- - `Promise<PageData | void>`: スクレイピング結果のページデータ、またはスキップされた場合は `void`
27
+ ### `Scraper`(デフォルトエクスポート)
57
28
 
58
- **使用例:**
29
+ ページレベルのスクレイパークラスです。`TypedAwaitEventEmitter` を継承しています。
59
30
 
60
31
  ```typescript
61
- const scraper = new Scraper();
62
-
63
- // イベントリスナーを登録
64
- scraper.on('scrapeEnd', async (event) => {
65
- console.log('スクレイピング完了:', event.result);
66
- });
67
-
68
- scraper.on('error', async (event) => {
69
- console.error('エラー発生:', event.error);
70
- });
71
-
72
- // スクレイピングを開始
73
- const url = parseUrl('https://example.com');
74
- await scraper.scrapeStart(url, {
75
- isGettingImages: true,
76
- excludeKeywords: ['広告', 'スポンサー'],
77
- });
78
-
79
- // 完了後にクリーンアップ
80
- await scraper.destroy(false);
32
+ import Scraper from '@d-zero/beholder';
81
33
  ```
82
34
 
83
- ###### `destroy(isExternal: boolean): Promise<void>`
35
+ #### `scrapeStart(page, url, options?, isSkip?)`
84
36
 
85
- スクレイパーインスタンスを破棄し、ブラウザを閉じます。
37
+ Puppeteer ページ上でスクレイピングを実行します。
86
38
 
87
39
  **パラメータ:**
88
40
 
89
- - `isExternal` (`boolean`): 外部URLのスクレイピングかどうか
41
+ - `page` (`Page`) — Puppeteer ページインスタンス
42
+ - `url` (`ExURL`) — スクレイピング対象のURL
43
+ - `options` (`Partial<ScraperOptions>`, 省略可) — スクレイピングオプション
44
+ - `isSkip` (`boolean`, 省略可) — `true` でネットワークリクエストなしにスキップ
90
45
 
91
- **戻り値:**
46
+ **戻り値:** `Promise<ScrapeResult>`
92
47
 
93
- - `Promise<void>`
48
+ - `type: "success"` — `pageData` にスクレイピング結果を格納
49
+ - `type: "skipped"` — `ignored` にスキップ理由を格納
50
+ - `type: "error"` — `error` にエラー詳細を格納
94
51
 
95
52
  **使用例:**
96
53
 
97
54
  ```typescript
98
- await scraper.destroy(false);
99
- ```
100
-
101
- ##### イベント
102
-
103
- `Scraper` クラスは `TypedAwaitEventEmitter` を継承しており、以下のイベントを発行します:
55
+ import Scraper from '@d-zero/beholder';
56
+ import { parseUrl } from '@d-zero/shared/parse-url';
57
+ import { launch } from 'puppeteer';
104
58
 
105
- - `ignoreAndSkip`: ページがキーワードマッチングによりスキップされた場合
106
- - `resourceResponse`: リソースが取得された場合
107
- - `scrapeEnd`: スクレイピングが完了した場合
108
- - `destroyed`: スクレイパーが破棄された場合
109
- - `error`: エラーが発生した場合
110
- - `changePhase`: スクレイピングフェーズが変更された場合
59
+ const browser = await launch();
60
+ const page = await browser.newPage();
111
61
 
112
- **イベントの使用例:**
62
+ const scraper = new Scraper();
113
63
 
114
- ```typescript
64
+ // 進捗イベントを監視
115
65
  scraper.on('changePhase', async (event) => {
116
- console.log(`フェーズ: ${event.name} - ${event.message}`);
66
+ console.log(`[${event.pid}] ${event.name}: ${event.message}`);
117
67
  });
118
68
 
119
- scraper.on('resourceResponse', async (event) => {
120
- console.log(`リソース取得: ${event.resource.url.href}`);
69
+ // スクレイピングを実行
70
+ const url = parseUrl('https://example.com');
71
+ const result = await scraper.scrapeStart(page, url, {
72
+ captureImages: true,
73
+ excludeKeywords: ['広告'],
74
+ isExternal: false,
121
75
  });
122
- ```
123
-
124
- #### `SubProcessRunner`
125
-
126
- サブプロセスでスクレイパーを実行するためのクラスです。メモリ効率的なスクレイピングを実現します。
127
-
128
- ```typescript
129
- import { SubProcessRunner } from '@d-zero/beholder';
130
- ```
131
-
132
- ##### コンストラクタ
133
-
134
- ```typescript
135
- new SubProcessRunner(resetTime: number)
136
- ```
137
-
138
- **パラメータ:**
139
-
140
- - `resetTime` (`number`): サブプロセスをリセットするまでのスクレイピング回数
141
-
142
- ##### プロパティ
143
-
144
- ###### `state: 'waiting' | 'running'` (読み取り専用)
145
-
146
- 現在のサブプロセスの状態を取得します。
147
-
148
- ##### メソッド
149
-
150
- ###### `start(url: ExURL, options: ScraperOptions, isSkip: boolean, interval: number): void`
151
-
152
- サブプロセスでスクレイピングを開始します。
153
-
154
- **パラメータ:**
155
-
156
- - `url` (`ExURL`): スクレイピング対象のURL
157
- - `options` (`ScraperOptions`): スクレイピングオプション
158
- - `isSkip` (`boolean`): スキップするかどうか
159
- - `interval` (`number`): スクレイピング間隔(ミリ秒)
160
-
161
- **例外:**
162
-
163
- - サブプロセスが既に実行中の場合はエラーをスローします
164
-
165
- **使用例:**
166
-
167
- ```typescript
168
- const runner = new SubProcessRunner(10); // 10回ごとにリセット
169
76
 
170
- runner.on('scrapeEvent', async (event) => {
171
- if (event.type === '@@scraper/scrapeEnd') {
172
- console.log('スクレイピング完了:', event.payload.result);
173
- }
174
- });
77
+ if (result.type === 'success') {
78
+ console.log('タイトル:', result.pageData?.meta.title);
79
+ console.log('リンク数:', result.pageData?.anchorList.length);
80
+ console.log('画像数:', result.pageData?.imageList.length);
81
+ console.log('サブリソース数:', result.resources.length);
82
+ }
175
83
 
176
- runner.start(
177
- url,
178
- {
179
- isExternal: false,
180
- isGettingImages: true,
181
- excludeKeywords: [],
182
- executablePath: null,
183
- isTitleOnly: false,
184
- screenshot: null,
185
- },
186
- false,
187
- 1000,
188
- );
84
+ // クリーンアップはブラウザレベルで行う
85
+ await page.close();
86
+ await browser.close();
189
87
  ```
190
88
 
191
- ###### `destory(): void`
89
+ #### イベント
192
90
 
193
- サブプロセスを破棄します。
91
+ | イベント名 | 説明 |
92
+ | ------------------ | ---------------------------------------------- |
93
+ | `changePhase` | スクレイピングフェーズが遷移した場合 |
94
+ | `resourceResponse` | サブリソースのレスポンスがキャプチャされた場合 |
194
95
 
195
- **注意:** メソッド名は `destory` (typo) ですが、パッケージの互換性のため維持されています。
96
+ ### `ScraperOptions`
196
97
 
197
- **使用例:**
98
+ | プロパティ | 型 | デフォルト | 説明 |
99
+ | ------------------- | ---------- | ---------- | ---------------------------------------------------------------- |
100
+ | `isExternal` | `boolean` | `false` | 外部URLかどうか |
101
+ | `captureImages` | `boolean` | `true` | 画像データを取得するかどうか |
102
+ | `excludeKeywords` | `string[]` | `[]` | HTML内にマッチしたらスキップするキーワード |
103
+ | `metadataOnly` | `boolean` | `false` | メタデータのみ取得(ブラウザスクレイピングなし) |
104
+ | `imageLoadTimeout` | `number` | `5000` | 画像読み込み待機のタイムアウト(ms) |
105
+ | `disableQueries` | `boolean` | - | URLパース時にクエリパラメータを除去するかどうか |
106
+ | `retries` | `number` | - | ネットワーク操作のリトライ回数 |
107
+ | `headCheckResult` | `PageData` | - | 事前取得したHEADチェック結果(省略時はHEADリクエストをスキップ) |
108
+ | `navigationTimeout` | `number` | `60000` | `page.goto()` のタイムアウト(ms) |
198
109
 
199
- ```typescript
200
- runner.destory();
201
- ```
110
+ ### ユーティリティ関数
202
111
 
203
- ###### `kill(): void`
112
+ #### `isError(status)`
204
113
 
205
- サブプロセスを強制終了します(SIGKILL)。
206
-
207
- **使用例:**
114
+ HTTPステータスコードがエラーかどうかを判定します。200-399 は成功、それ以外はエラーです。
208
115
 
209
116
  ```typescript
210
- runner.kill();
211
- ```
212
-
213
- ###### `getUndeadPid(): number[]`
214
-
215
- 終了できなかった(ゾンビ)プロセスのPIDリストを取得します。
216
-
217
- **戻り値:**
218
-
219
- - `number[]`: ゾンビプロセスのPIDリスト
117
+ import { isError } from '@d-zero/beholder';
220
118
 
221
- **使用例:**
222
-
223
- ```typescript
224
- const zombiePids = runner.getUndeadPid();
225
- console.log('ゾンビプロセス:', zombiePids);
119
+ isError(200); // false
120
+ isError(404); // true
226
121
  ```
227
122
 
228
- ##### イベント
123
+ #### `detectCompress(headers)` / `detectCDN(headers)`
229
124
 
230
- - `reset`: サブプロセスがリセットされた場合
231
- - `scrapeEvent`: スクレイピングイベントが発生した場合
232
- - `changePhase`: フェーズが変更された場合
233
- - `error`: エラーが発生した場合
125
+ レスポンスヘッダーから圧縮方式・CDNプロバイダを検出します(`@d-zero/shared` からの再エクスポート)。
234
126
 
235
127
  ### 型定義
236
128
 
237
- #### `ExURL`
129
+ #### `ScrapeResult`
238
130
 
239
- 拡張されたURL情報を含む型です。
131
+ スクレイピング操作の結果を表します。
240
132
 
241
133
  ```typescript
242
- type ExURL = {
243
- href: string; // 完全なURL
244
- _originUrlString: string; // パース前の元のURL文字列
245
- withoutHash: string; // ハッシュなしのURL
246
- withoutHashAndAuth: string; // ハッシュと認証情報なしのURL
247
- protocol: string; // プロトコル(例: "https:")
248
- isHTTP: boolean; // HTTPまたはHTTPSかどうか
249
- isSecure: boolean; // HTTPSかどうか
250
- username: string | null; // 認証ユーザー名
251
- password: string | null; // 認証パスワード
252
- hostname: string; // ホスト名
253
- port: string | null; // ポート番号
254
- pathname: string | null; // パス
255
- paths: string[]; // パスの配列
256
- depth: number; // パスの深さ
257
- dirname: string | null; // ディレクトリ名
258
- basename: string | null; // ベース名(拡張子なしのファイル名)
259
- isIndex: boolean; // インデックスページかどうか
260
- extname: string | null; // ファイル拡張子
261
- query: string | null; // クエリ文字列
262
- hash: string | null; // ハッシュ
134
+ type ScrapeResult = {
135
+ type: 'success' | 'skipped' | 'error';
136
+ pageData?: PageData;
137
+ resources: ResourceEntry[];
138
+ ignored?: { url: ExURL; matchedText: string; excludeKeywords: string[] };
139
+ error?: { name: string; message: string; stack?: string; shutdown: boolean };
263
140
  };
264
141
  ```
265
142
 
266
143
  #### `PageData`
267
144
 
268
- スクレイピング結果のページデータです。
145
+ スクレイピング成功時のページデータです。
269
146
 
270
147
  ```typescript
271
148
  type PageData = {
272
- url: ExURL; // ページのURL
273
- redirectPaths: string[]; // リダイレクトパス
274
- isTarget: boolean; // ターゲットページかどうか
275
- isExternal: boolean; // 外部ページかどうか
276
- status: number; // HTTPステータスコード
277
- statusText: string; // HTTPステータステキスト
278
- contentType: string | null; // コンテンツタイプ
279
- contentLength: number | null; // コンテンツ長
280
- responseHeaders: Record<string, string | string[] | undefined> | null; // レスポンスヘッダー
281
- meta: Meta; // メタ情報
282
- anchorList: AnchorData[]; // アンカーリスト
283
- imageList: ImageElement[]; // 画像リスト
284
- html: string; // HTML内容
285
- isSkipped: false; // スキップされたかどうか
149
+ url: ExURL;
150
+ redirectPaths: string[];
151
+ isTarget: boolean;
152
+ isExternal: boolean;
153
+ status: number;
154
+ statusText: string;
155
+ contentType: string | null;
156
+ contentLength: number | null;
157
+ responseHeaders: Record<string, string | string[] | undefined> | null;
158
+ meta: Meta;
159
+ anchorList: AnchorData[];
160
+ imageList: ImageElement[];
161
+ html: string;
162
+ isSkipped: false;
286
163
  };
287
164
  ```
288
165
 
289
166
  #### `Meta`
290
167
 
291
- ページのメタデータです。
168
+ ページの `<head>` から抽出されたメタデータです。
292
169
 
293
170
  ```typescript
294
171
  type Meta = {
295
- lang?: string; // 言語
296
- title: string; // タイトル
297
- description?: string; // 説明
298
- keywords?: string; // キーワード
299
- noindex?: boolean; // noindexタグの有無
300
- nofollow?: boolean; // nofollowタグの有無
301
- noarchive?: boolean; // noarchiveタグの有無
302
- canonical?: string; // 正規URL
303
- alternate?: string; // 代替URL
304
- 'og:type'?: string; // Open Graph: type
305
- 'og:title'?: string; // Open Graph: title
306
- 'og:site_name'?: string; // Open Graph: site_name
307
- 'og:description'?: string; // Open Graph: description
308
- 'og:url'?: string; // Open Graph: url
309
- 'og:image'?: string; // Open Graph: image
310
- 'twitter:card'?: string; // Twitter Card: card
172
+ lang?: string;
173
+ title: string;
174
+ description?: string;
175
+ keywords?: string;
176
+ noindex?: boolean;
177
+ nofollow?: boolean;
178
+ noarchive?: boolean;
179
+ canonical?: string;
180
+ alternate?: string;
181
+ 'og:type'?: string;
182
+ 'og:title'?: string;
183
+ 'og:site_name'?: string;
184
+ 'og:description'?: string;
185
+ 'og:url'?: string;
186
+ 'og:image'?: string;
187
+ 'twitter:card'?: string;
311
188
  };
312
189
  ```
313
190
 
314
191
  #### `AnchorData`
315
192
 
316
- アンカー要素のデータです。
193
+ アンカー要素(`<a>` / `<area>`)のデータです。
317
194
 
318
195
  ```typescript
319
196
  type AnchorData = {
320
- href: ExURL; // href属性の値
321
- textContent: string; // アクセシブルな名前
197
+ href: ExURL;
198
+ textContent: string;
199
+ isExternal?: boolean;
322
200
  };
323
201
  ```
324
202
 
325
203
  #### `ImageElement`
326
204
 
327
- 画像要素のデータです。
205
+ 画像要素のデータです。デバイスプリセットごとにキャプチャされます。
328
206
 
329
207
  ```typescript
330
208
  type ImageElement = {
331
- src: string; // src属性
332
- currentSrc: string; // 現在のsrc
333
- alt: string; // alt属性
334
- width: number; // 表示幅
335
- height: number; // 表示高さ
336
- naturalWidth: number; // 実際の幅
337
- naturalHeight: number; // 実際の高さ
338
- isLazy: boolean; // 遅延読み込みかどうか
339
- viewportWidth: number; // ビューポート幅
340
- sourceCode: string; // ソースコード
209
+ src: string;
210
+ currentSrc: string;
211
+ alt: string;
212
+ width: number;
213
+ height: number;
214
+ naturalWidth: number;
215
+ naturalHeight: number;
216
+ isLazy: boolean;
217
+ viewportWidth: number;
218
+ sourceCode: string;
341
219
  };
342
220
  ```
343
221
 
344
- #### `NetworkLog`
222
+ #### `ResourceEntry`
345
223
 
346
- ネットワークログの情報です。
224
+ ページ読み込み中にキャプチャされたサブリソースです。
347
225
 
348
226
  ```typescript
349
- type NetworkLog = {
350
- url: ExURL; // リクエストURL
351
- status: number | null; // ステータスコード
352
- contentLength: number; // コンテンツ長
353
- contentType: string; // コンテンツタイプ
354
- isError: boolean; // エラーかどうか
355
- request: {
356
- ts: number; // タイムスタンプ
357
- headers: Record<string, string>; // リクエストヘッダー
358
- method: string; // HTTPメソッド
359
- };
360
- response?: {
361
- ts: number; // タイムスタンプ
362
- status: number; // ステータスコード
363
- statusText: string; // ステータステキスト
364
- fromCache: boolean; // キャッシュから取得したかどうか
365
- headers: Record<string, string>; // レスポンスヘッダー
366
- };
227
+ type ResourceEntry = {
228
+ log: NetworkLog;
229
+ resource: Omit<Resource, 'uid'>;
230
+ pageUrl: string;
367
231
  };
368
232
  ```
369
233
 
370
- #### `Resource`
371
-
372
- リソースの情報です。
373
-
374
- ```typescript
375
- type Resource = {
376
- url: ExURL; // リソースURL
377
- isExternal: boolean; // 外部リソースかどうか
378
- isError: boolean; // エラーかどうか
379
- status: number | null; // ステータスコード
380
- statusText: string | null; // ステータステキスト
381
- contentType: string | null; // コンテンツタイプ
382
- contentLength: number | null; // コンテンツ長
383
- compress: false | CompressType; // 圧縮タイプ
384
- cdn: false | CDNType; // CDNタイプ
385
- headers: Record<string, string | string[] | undefined> | null; // ヘッダー
386
- };
387
- ```
388
-
389
- #### `SkippedPageData`
234
+ #### `NetworkLog`
390
235
 
391
- スキップされたページのデータです。
236
+ ネットワークリクエスト/レスポンスのログエントリです。
392
237
 
393
238
  ```typescript
394
- type SkippedPageData = {
395
- isSkipped: true; // スキップされたかどうか
396
- url: ExURL; // URL
397
- matched: {
398
- type: 'keyword' | 'path'; // マッチタイプ
399
- text?: string; // マッチしたテキスト(keywordの場合)
400
- excludeKeywords?: string[]; // 除外キーワード(keywordの場合)
401
- excludes?: string[]; // 除外パス(pathの場合)
239
+ type NetworkLog = {
240
+ url: ExURL;
241
+ status: number | null;
242
+ contentLength: number;
243
+ contentType: string;
244
+ isError: boolean;
245
+ request: { ts: number; headers: Record<string, string>; method: string };
246
+ response?: {
247
+ ts: number;
248
+ status: number;
249
+ statusText: string;
250
+ fromCache: boolean;
251
+ headers: Record<string, string>;
402
252
  };
403
253
  };
404
254
  ```
405
255
 
406
- #### イベント型
407
-
408
- ##### `ScrapeEventTypes`
409
-
410
- ```typescript
411
- type ScrapeEventTypes = {
412
- ignoreAndSkip: {
413
- pid: number | undefined;
414
- url: ExURL;
415
- reason: {
416
- matchedText: string;
417
- excludeKeywords: string[];
418
- };
419
- };
420
- resourceResponse: {
421
- pid: number | undefined;
422
- url: ExURL;
423
- log: NetworkLog;
424
- resource: Omit<Resource, 'uid'>;
425
- };
426
- scrapeEnd: {
427
- pid: number | undefined;
428
- url: ExURL;
429
- timestamp: number;
430
- result: PageData;
431
- };
432
- destroyed: {
433
- pid: number | undefined;
434
- };
435
- error: {
436
- pid: number | undefined;
437
- url: ExURL;
438
- shutdown: boolean;
439
- error: {
440
- name: string;
441
- message: string;
442
- stack?: string;
443
- };
444
- };
445
- changePhase: ChangePhaseEvent;
446
- };
447
- ```
256
+ #### `Resource`
448
257
 
449
- ##### `SubProcessRunnerEventTypes`
258
+ ネットワークリソースのメタデータです。
450
259
 
451
260
  ```typescript
452
- type SubProcessRunnerEventTypes = {
453
- reset: {
454
- pid: number | undefined;
455
- };
456
- scrapeEvent: Action<AnyScrapeEvent>;
457
- changePhase: SubProcessChangeEvent;
458
- error: {
459
- pid: number | undefined;
460
- url: ExURL;
461
- shutdown: boolean;
462
- error: {
463
- name: string;
464
- message: string;
465
- stack?: string;
466
- };
467
- };
261
+ type Resource = {
262
+ url: ExURL;
263
+ isExternal: boolean;
264
+ isError: boolean;
265
+ status: number | null;
266
+ statusText: string | null;
267
+ contentType: string | null;
268
+ contentLength: number | null;
269
+ compress: false | CompressType;
270
+ cdn: false | CDNType;
271
+ headers: Record<string, string | string[] | undefined> | null;
468
272
  };
469
273
  ```
470
274
 
471
- ### イベントアクションクリエーター
472
-
473
- #### `scraperEvent`
275
+ #### `ChangePhaseEvent`
474
276
 
475
- スクレイパーイベントを作成するためのアクションクリエーターです。
277
+ スクレイピングライフサイクルのフェーズ遷移イベントです。
476
278
 
477
- ```typescript
478
- import { scraperEvent } from '@d-zero/beholder';
479
-
480
- // 利用可能なイベント:
481
- // - scraperEvent.ignoreAndSkip
482
- // - scraperEvent.resourceResponse
483
- // - scraperEvent.scrapeEnd
484
- // - scraperEvent.destroyed
485
- // - scraperEvent.error
486
- // - scraperEvent.changePhase
487
- ```
488
-
489
- #### `subProcessEvent`
279
+ 主なフェーズ: `scrapeStart` → `openPage` → `loadDOMContent` → `waitNetworkIdle` → `getHTML` → `getAnchors` → `getMeta` → `extractImages` → `getImages` → `scrapeEnd`
490
280
 
491
- サブプロセスイベントを作成するためのアクションクリエーターです。
492
-
493
- ```typescript
494
- import { subProcessEvent } from '@d-zero/beholder';
495
-
496
- // 利用可能なイベント:
497
- // - subProcessEvent.start
498
- // - subProcessEvent.destroy
499
- ```
500
-
501
- ## 完全な使用例
502
-
503
- ### 基本的なスクレイピング
504
-
505
- ```typescript
506
- import Scraper from '@d-zero/beholder';
507
- import { parseUrl } from '@d-zero/shared/parse-url';
508
-
509
- const scraper = new Scraper();
510
-
511
- // イベントハンドラーを設定
512
- scraper.on('changePhase', async (event) => {
513
- console.log(`[${event.pid}] ${event.name}: ${event.message}`);
514
- });
515
-
516
- scraper.on('scrapeEnd', async (event) => {
517
- const { result } = event;
518
- console.log('タイトル:', result.meta.title);
519
- console.log('ステータス:', result.status);
520
- console.log('アンカー数:', result.anchorList.length);
521
- console.log('画像数:', result.imageList.length);
522
- });
523
-
524
- scraper.on('error', async (event) => {
525
- console.error('エラー:', event.error.message);
526
- });
527
-
528
- // スクレイピングを実行
529
- const url = parseUrl('https://example.com');
530
- const result = await scraper.scrapeStart(url, {
531
- isGettingImages: true,
532
- excludeKeywords: ['広告'],
533
- isExternal: false,
534
- });
535
-
536
- // クリーンアップ
537
- await scraper.destroy(false);
538
- ```
281
+ #### `SkippedPageData`
539
282
 
540
- ### サブプロセスでのスクレイピング
283
+ キーワードまたはパス除外によりスキップされたページのデータです。
541
284
 
542
285
  ```typescript
543
- import { SubProcessRunner } from '@d-zero/beholder';
544
- import { parseUrl } from '@d-zero/shared/parse-url';
545
-
546
- const runner = new SubProcessRunner(5); // 5回ごとにリセット
547
-
548
- // イベントハンドラーを設定
549
- runner.on('scrapeEvent', async (action) => {
550
- if (action.type === '@@scraper/scrapeEnd') {
551
- console.log('完了:', action.payload.result.url.href);
552
- }
553
- if (action.type === '@@scraper/error') {
554
- console.error('エラー:', action.payload.error.message);
555
- }
556
- });
557
-
558
- runner.on('changePhase', async (event) => {
559
- console.log(`[${event.pid}] ${event.name}`);
560
- });
561
-
562
- // URLリストをスクレイピング
563
- const urls = [
564
- 'https://example.com',
565
- 'https://example.com/about',
566
- 'https://example.com/contact',
567
- ];
568
-
569
- for (const urlString of urls) {
570
- const url = parseUrl(urlString);
571
-
572
- // サブプロセスが待機状態になるまで待つ
573
- while (runner.state === 'running') {
574
- await new Promise((resolve) => setTimeout(resolve, 100));
575
- }
576
-
577
- runner.start(
578
- url,
579
- {
580
- isExternal: false,
581
- isGettingImages: false,
582
- excludeKeywords: [],
583
- executablePath: null,
584
- isTitleOnly: true,
585
- screenshot: null,
586
- },
587
- false,
588
- 1000,
589
- );
590
- }
591
-
592
- // 完了後にクリーンアップ
593
- runner.destory();
286
+ type SkippedPageData = {
287
+ isSkipped: true;
288
+ url: ExURL;
289
+ matched:
290
+ | { type: 'keyword'; text: string; excludeKeywords: string[] }
291
+ | { type: 'path'; excludes: string[] };
292
+ };
594
293
  ```
595
294
 
596
295
  ## ライセンス
597
296
 
598
297
  MIT
599
-
600
- ## 作者
601
-
602
- D-ZERO