@isdk/web-fetcher 0.3.0 → 0.3.1
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.
- package/README.action.cn.md +53 -312
- package/README.action.extract.cn.md +263 -0
- package/README.action.extract.md +263 -0
- package/README.action.md +53 -311
- package/README.cn.md +10 -2
- package/README.engine.cn.md +22 -1
- package/README.engine.md +22 -1
- package/README.md +8 -1
- package/dist/index.d.mts +147 -1
- package/dist/index.d.ts +147 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/docs/README.md +8 -1
- package/docs/_media/README.action.md +53 -311
- package/docs/_media/README.cn.md +10 -2
- package/docs/_media/README.engine.md +22 -1
- package/docs/classes/CheerioFetchEngine.md +236 -88
- package/docs/classes/ClickAction.md +23 -23
- package/docs/classes/EvaluateAction.md +23 -23
- package/docs/classes/ExtractAction.md +23 -23
- package/docs/classes/FetchAction.md +27 -23
- package/docs/classes/FetchEngine.md +218 -86
- package/docs/classes/FetchSession.md +13 -13
- package/docs/classes/FillAction.md +23 -23
- package/docs/classes/GetContentAction.md +23 -23
- package/docs/classes/GotoAction.md +23 -23
- package/docs/classes/KeyboardPressAction.md +533 -0
- package/docs/classes/KeyboardTypeAction.md +533 -0
- package/docs/classes/MouseClickAction.md +533 -0
- package/docs/classes/MouseMoveAction.md +533 -0
- package/docs/classes/PauseAction.md +23 -23
- package/docs/classes/PlaywrightFetchEngine.md +337 -87
- package/docs/classes/SubmitAction.md +23 -23
- package/docs/classes/TrimAction.md +23 -23
- package/docs/classes/WaitForAction.md +23 -23
- package/docs/classes/WebFetcher.md +5 -5
- package/docs/enumerations/FetchActionResultStatus.md +4 -4
- package/docs/functions/fetchWeb.md +2 -2
- package/docs/globals.md +8 -0
- package/docs/interfaces/BaseFetchActionProperties.md +12 -12
- package/docs/interfaces/BaseFetchCollectorActionProperties.md +16 -16
- package/docs/interfaces/BaseFetcherProperties.md +31 -27
- package/docs/interfaces/Cookie.md +14 -14
- package/docs/interfaces/DispatchedEngineAction.md +4 -4
- package/docs/interfaces/EvaluateActionOptions.md +3 -3
- package/docs/interfaces/ExtractActionProperties.md +12 -12
- package/docs/interfaces/FetchActionInContext.md +15 -15
- package/docs/interfaces/FetchActionProperties.md +13 -13
- package/docs/interfaces/FetchActionResult.md +6 -6
- package/docs/interfaces/FetchContext.md +41 -37
- package/docs/interfaces/FetchEngineContext.md +36 -32
- package/docs/interfaces/FetchMetadata.md +5 -5
- package/docs/interfaces/FetchResponse.md +14 -14
- package/docs/interfaces/FetchReturnTypeRegistry.md +7 -7
- package/docs/interfaces/FetchSite.md +34 -30
- package/docs/interfaces/FetcherOptions.md +33 -29
- package/docs/interfaces/GotoActionOptions.md +14 -6
- package/docs/interfaces/KeyboardPressParams.md +25 -0
- package/docs/interfaces/KeyboardTypeParams.md +25 -0
- package/docs/interfaces/MouseClickParams.md +49 -0
- package/docs/interfaces/MouseMoveParams.md +41 -0
- package/docs/interfaces/PendingEngineRequest.md +3 -3
- package/docs/interfaces/StorageOptions.md +5 -5
- package/docs/interfaces/SubmitActionOptions.md +2 -2
- package/docs/interfaces/TrimActionOptions.md +3 -3
- package/docs/interfaces/WaitForActionOptions.md +5 -5
- package/docs/type-aliases/BaseFetchActionOptions.md +1 -1
- package/docs/type-aliases/BaseFetchCollectorOptions.md +1 -1
- package/docs/type-aliases/BrowserEngine.md +1 -1
- package/docs/type-aliases/FetchActionCapabilities.md +1 -1
- package/docs/type-aliases/FetchActionCapabilityMode.md +1 -1
- package/docs/type-aliases/FetchActionOptions.md +1 -1
- package/docs/type-aliases/FetchEngineAction.md +2 -2
- package/docs/type-aliases/FetchEngineType.md +1 -1
- package/docs/type-aliases/FetchReturnType.md +1 -1
- package/docs/type-aliases/FetchReturnTypeFor.md +1 -1
- package/docs/type-aliases/OnFetchPauseCallback.md +1 -1
- package/docs/type-aliases/ResourceType.md +1 -1
- package/docs/type-aliases/TrimPreset.md +1 -1
- package/docs/variables/DefaultFetcherProperties.md +1 -1
- package/docs/variables/FetcherOptionKeys.md +1 -1
- package/docs/variables/TRIM_PRESETS.md +1 -1
- package/package.json +10 -10
package/README.action.cn.md
CHANGED
|
@@ -245,6 +245,52 @@ await fetchWeb({
|
|
|
245
245
|
* **`params`**: (无)
|
|
246
246
|
* **`returns`**: `response`
|
|
247
247
|
|
|
248
|
+
#### `mouseMove`
|
|
249
|
+
|
|
250
|
+
将鼠标指针移动到指定的坐标或元素。在 `browser` 模式下,它使用 **贝塞尔曲线 (Bézier curve)** 来模拟真实人类的非线性移动轨迹,并带有轻微的抖动以增加真实感。
|
|
251
|
+
|
|
252
|
+
* **`id`**: `mouseMove`
|
|
253
|
+
* **`params`**:
|
|
254
|
+
* `x` (number, 可选): 绝对 X 坐标。
|
|
255
|
+
* `y` (number, 可选): 绝对 Y 坐标。
|
|
256
|
+
* `selector` (string, 可选): CSS 选择器。如果提供,鼠标将移动到该元素的中心。
|
|
257
|
+
* `steps` (number, 可选): 轨迹的中间步数(默认:`-1`)。设置为 `-1` 可根据距离动态计算步数(模拟自然移动速度)。
|
|
258
|
+
* **`returns`**: `none`
|
|
259
|
+
|
|
260
|
+
#### `mouseClick`
|
|
261
|
+
|
|
262
|
+
在当前位置或指定坐标触发鼠标点击。如果提供了 `selector`,光标会先平滑地移动到目标元素(使用动态步数),然后再执行点击。
|
|
263
|
+
|
|
264
|
+
* **`id`**: `mouseClick`
|
|
265
|
+
* **`params`**:
|
|
266
|
+
* `x` (number, 可选): 点击的绝对 X 坐标。
|
|
267
|
+
* `y` (number, 可选): 点击的绝对 Y 坐标。
|
|
268
|
+
* `selector` (string, 可选): CSS 选择器。如果提供,鼠标会先移动到该元素。
|
|
269
|
+
* `button` (string, 可选): 使用的鼠标按键 (`left`, `right`, 或 `middle`)。默认为 `left`。
|
|
270
|
+
* `clickCount` (number, 可选): 点击次数(例如:2 表示双击)。默认为 1。
|
|
271
|
+
* `delay` (number, 可选): mousedown 和 mouseup 之间的延迟(毫秒)。
|
|
272
|
+
* **`returns`**: `none`
|
|
273
|
+
|
|
274
|
+
#### `keyboardType`
|
|
275
|
+
|
|
276
|
+
模拟真人在当前获得焦点的元素中输入文本。
|
|
277
|
+
|
|
278
|
+
* **`id`**: `keyboardType`
|
|
279
|
+
* **`params`**:
|
|
280
|
+
* `text` (string): 要输入的文本。
|
|
281
|
+
* `delay` (number, 可选): 按键之间的延迟(毫秒,默认:100)。
|
|
282
|
+
* **`returns`**: `none`
|
|
283
|
+
|
|
284
|
+
#### `keyboardPress`
|
|
285
|
+
|
|
286
|
+
模拟按下单个按键或组合键(例如:`Enter`, `Control+A`)。
|
|
287
|
+
|
|
288
|
+
* **`id`**: `keyboardPress`
|
|
289
|
+
* **`params`**:
|
|
290
|
+
* `key` (string): 按键名称(例如:`Enter`, `Tab`, `Backspace`, `ArrowUp`)。
|
|
291
|
+
* `delay` (number, 可选): 按键后的延迟(毫秒)。
|
|
292
|
+
* **`returns`**: `none`
|
|
293
|
+
|
|
248
294
|
#### `evaluate`
|
|
249
295
|
|
|
250
296
|
在页面上下文中执行 JavaScript 函数或表达式。这是一个强大的 Action,用于执行内置 Action 未涵盖的自定义逻辑。
|
|
@@ -292,322 +338,17 @@ await fetchWeb({
|
|
|
292
338
|
|
|
293
339
|
#### `extract`
|
|
294
340
|
|
|
295
|
-
|
|
341
|
+
使用强大且声明式的 **`ExtractSchema`** 从页面中提取结构化数据。
|
|
296
342
|
|
|
297
343
|
* **`id`**: `extract`
|
|
298
|
-
* **`params`**: 一个 **`ExtractSchema`**
|
|
299
|
-
* **`returns`**:
|
|
300
|
-
|
|
301
|
-
##### 提取 Schema 详解
|
|
344
|
+
* **`params`**: 一个 **`ExtractSchema`** 对象。
|
|
345
|
+
* **`returns`**: 提取出的结构化数据。
|
|
302
346
|
|
|
303
|
-
`
|
|
304
|
-
|
|
305
|
-
###### 1. 提取单个值
|
|
306
|
-
|
|
307
|
-
最基础的提取,可以指定 **`selector`** (CSS 选择器), **`attribute`** (要提取的属性名), **`type`** (string, number, boolean, html), 以及 **`mode`** (text, innerText)。
|
|
308
|
-
|
|
309
|
-
* **`depth`** (number, 可选): 在通过 `selector` 匹配到元素后, 向上回溯指定的 DOM 层级。最终回溯到的祖先元素将作为实际的值提取目标 (例如, 用于从父级包装容器中提取属性)。
|
|
310
|
-
|
|
311
|
-
```json
|
|
312
|
-
{
|
|
313
|
-
"id": "extract",
|
|
314
|
-
"params": {
|
|
315
|
-
"selector": "h1.main-title",
|
|
316
|
-
"type": "string",
|
|
317
|
-
"mode": "innerText"
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
> **提取模式 (Extraction Modes):**
|
|
347
|
+
> **📚 详细手册**: 由于 `extract` 功能非常丰富(包含数组模式、作用域控制、锚点跳转等),我们准备了专门的详细文档:
|
|
323
348
|
>
|
|
324
|
-
>
|
|
325
|
-
> * **`innerText`**: 提取渲染后的文本,尊重 CSS 样式并处理换行。
|
|
326
|
-
> * **`html`**: 返回元素的 `innerHTML`。
|
|
327
|
-
> * **`outerHTML`**: 返回包含标签自身的完整 HTML。对于保留元素结构非常有用。
|
|
328
|
-
> 上例将使用 `innerText` 模式提取 class 为 `main-title` 的 `<h1>` 标签的文本内容。
|
|
329
|
-
|
|
330
|
-
###### 2. 提取对象
|
|
331
|
-
|
|
332
|
-
通过 **`type: 'object'`** 和 **`properties`** 字段来定义一个结构化对象。
|
|
333
|
-
|
|
334
|
-
```json
|
|
335
|
-
{
|
|
336
|
-
"id": "extract",
|
|
337
|
-
"params": {
|
|
338
|
-
"type": "object",
|
|
339
|
-
"selector": ".author-bio",
|
|
340
|
-
"properties": {
|
|
341
|
-
"name": { "selector": ".author-name" },
|
|
342
|
-
"email": { "selector": "a.email", "attribute": "href" }
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
* **`depth`** (number, 可选): 启用“按需回溯 (Try-And-Bubble)”策略。如果在当前匹配的元素中缺少 `required` (必填) 字段,引擎会尝试向上回溯 DOM 树(最多 `depth` 层),寻找包含该必填字段的祖先元素。当选择器匹配的是后代元素(如内部 `span`),但数据位于父级容器时,此功能非常有用。
|
|
349
|
-
|
|
350
|
-
**Object 进阶功能:**
|
|
351
|
-
|
|
352
|
-
* **锚点跳转 (`anchor`)**: 指定该字段提取的起始参考点。
|
|
353
|
-
* **字段引用**: 引用同一对象中先前已提取的字段(使用其 DOM 元素)。
|
|
354
|
-
* **CSS 选择器**: 在当前对象作用域内即时查找锚点。
|
|
355
|
-
* **`depth`** (number, 可选): 使用锚点时,定义向上回溯多少层父节点来收集后续兄弟节点。
|
|
356
|
-
* **注意**: 如果省略,引擎默认会回溯到最大深度(直至对象根节点)以保持向后兼容。若要严格限制在锚点自身的兄弟节点内搜索,请设置 `depth: 0`。
|
|
357
|
-
* **效果**: 锚点生效后,该字段的搜索范围将变为锚点**之后**的兄弟节点(及其祖先节点,取决于 `depth`)。这允许在平铺结构中实现非线性的“跳转”提取。
|
|
358
|
-
* **顺序消费 (`relativeTo: "previous"`)**:
|
|
359
|
-
* 配合 `order` 属性,使每个字段的搜索范围都从前一个字段的匹配位置之后开始。
|
|
360
|
-
* 这对于提取由多个相同标签组成的列表(如多个连续的 `<p>` 标签分别代表不同含义)至关重要。
|
|
361
|
-
|
|
362
|
-
###### 3. 提取数组 (便捷用法)
|
|
363
|
-
|
|
364
|
-
通过 **`type: 'array'`** 来提取一个列表。为了让最常见的操作更简单,我们提供了一些便捷用法。
|
|
365
|
-
|
|
366
|
-
* **提取文本数组 (默认行为)**: 当您想提取一个文本列表时,只需提供选择器,省略 `items` 即可。这是最常见的用法。
|
|
367
|
-
|
|
368
|
-
```json
|
|
369
|
-
{
|
|
370
|
-
"id": "extract",
|
|
371
|
-
"params": {
|
|
372
|
-
"type": "array",
|
|
373
|
-
"selector": ".tags li"
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
> 上例将返回一个包含所有 `<li>` 标签文本的数组, 如 `["tech", "news"]`。
|
|
379
|
-
|
|
380
|
-
* **提取属性数组 (快捷方式)**: 当您只想提取一个属性列表(例如所有链接的 **`href`**)时,也无需嵌套 `items`。直接在 `array` 定义中声明 **`attribute`** 即可。
|
|
381
|
-
|
|
382
|
-
```json
|
|
383
|
-
|
|
384
|
-
{
|
|
385
|
-
"id": "extract",
|
|
386
|
-
"params": {
|
|
387
|
-
"type": "array",
|
|
388
|
-
"selector": ".gallery img",
|
|
389
|
-
"attribute": "src"
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
> 上例将返回一个包含所有 `<img>` 标签 `src` 属性的数组。
|
|
395
|
-
|
|
396
|
-
* **数组提取模式**: 在提取数组时,引擎支持不同的模式来处理各种 DOM 结构。
|
|
397
|
-
|
|
398
|
-
* **`nested`** (默认): `selector` 匹配每个项目的包裹元素。
|
|
399
|
-
* **`columnar`** (原 Zip 策略): `selector` 指向**容器**,`items` 中的字段是平行的列,按索引缝合在一起。
|
|
400
|
-
* **`segmented`**: `selector` 指向**容器**,项目通过“锚点”字段进行分段提取。
|
|
401
|
-
|
|
402
|
-
###### 4. 列对齐模式 (Columnar Mode,原 Zip 策略)
|
|
403
|
-
|
|
404
|
-
当 `selector` 指向一个**容器**(如结果列表)且项目数据以平行的独立列散落在其中时使用。此模式针对性能进行了深度优化,特别是在浏览器模式下,通过最小化 DOM 查询和 RPC 调用来提升速度。
|
|
405
|
-
|
|
406
|
-
> **💡 广播与性能 (Broadcasting & Performance)**:如果某个属性匹配容器元素本身(例如省略 selector 或匹配容器自身的属性),其值将被**广播**到每一行。这不仅是一个功能,更是一项重大性能优化:该值仅被提取**一次**并在所有行中复用,避免了成千上万次冗余的引擎调用。
|
|
407
|
-
|
|
408
|
-
```json
|
|
409
|
-
{
|
|
410
|
-
"id": "extract",
|
|
411
|
-
"params": {
|
|
412
|
-
"type": "array",
|
|
413
|
-
"selector": "#search-results",
|
|
414
|
-
"mode": "columnar",
|
|
415
|
-
"items": {
|
|
416
|
-
"title": { "selector": ".item-title" },
|
|
417
|
-
"link": { "selector": "a.item-link", "attribute": "href" }
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
> **启发式自动检测:** 如果省略了 `mode`,且 `selector` 恰好只匹配到一个元素,同时 `items` 中包含选择器,引擎会自动使用 **columnar** 模式。
|
|
424
|
-
|
|
425
|
-
**示例:列模式的广播行为 (Broadcasting)**
|
|
426
|
-
|
|
427
|
-
当列表中的某些数据(如分类)位于容器上,而具体项目在容器内部时。
|
|
349
|
+
> 👉 **[点击查看 Extract 操作详解](./README.action.extract.cn.md)**
|
|
428
350
|
|
|
429
|
-
|
|
430
|
-
{
|
|
431
|
-
"id": "extract",
|
|
432
|
-
"params": {
|
|
433
|
-
"type": "array",
|
|
434
|
-
"selector": "#book-category",
|
|
435
|
-
"mode": "columnar",
|
|
436
|
-
"items": {
|
|
437
|
-
"category": { "attribute": "data-category" },
|
|
438
|
-
"title": { "selector": ".book-title" }
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
> 如果 `#book-category` 拥有 `data-category="Sci-Fi"` 属性并包含 3 本书,结果将返回 3 个项目,且每个项目都带有 `"category": "Sci-Fi"`。
|
|
445
|
-
|
|
446
|
-
**Columnar 配置参数:**
|
|
447
|
-
|
|
448
|
-
* **`strict`** (boolean, 默认: `true`): 如果为 `true`,当不同字段匹配到的数量不一致时将抛出错误。
|
|
449
|
-
* **`inference`** (boolean, 默认: `false`): 如果为 `true`,当字段数量不匹配时,尝试通过 DOM 树自动寻找“包裹元素”来修复错位的列表。它使用经过优化的**祖先搜索算法**,在浏览器模式下比手动遍历快得多。
|
|
450
|
-
* **性能备注**: 引擎会自动检测共享结构并预计算对齐方式,确保即使在复杂的 DOM 树中也能保持 O(N) 的性能。在浏览器模式下,通过预计算“广播”标志来最小化 IPC 往返开销。
|
|
451
|
-
|
|
452
|
-
###### 5. 分段扫描模式 (Segmented Mode)
|
|
453
|
-
|
|
454
|
-
适用于完全“平铺”且没有包裹元素的结构。它使用指定的 `anchor` 锚点来对容器内容进行分段。
|
|
455
|
-
|
|
456
|
-
**核心特性:自动容器检测 (Bubble Up)**
|
|
457
|
-
|
|
458
|
-
为了处理“看似平铺实则有细微容器”的结构,引擎引入了冒泡定位策略:
|
|
459
|
-
|
|
460
|
-
- **智能冒泡**:当锚点深埋于嵌套结构中(如 `div.card > h3.title`),引擎会自动向上回溯,寻找在不包含相邻锚点的前提下最大的“安全容器”(如 `div.card`)。
|
|
461
|
-
- **逻辑隔离**:如果找到安全容器,该容器将成为分段的作用域。这使您能够通过简单的相对选择器提取容器内的任何内容,即使它位于锚点的“上方”或深层。
|
|
462
|
-
- **平铺回退**:如果锚点之间完全没有容器隔离,引擎将自动回退到传统的兄弟节点扫描模式。
|
|
463
|
-
|
|
464
|
-
```json
|
|
465
|
-
{
|
|
466
|
-
"id": "extract",
|
|
467
|
-
"params": {
|
|
468
|
-
"type": "array",
|
|
469
|
-
"selector": "#flat-container",
|
|
470
|
-
"mode": { "type": "segmented", "anchor": "h3.item-title" },
|
|
471
|
-
"items": {
|
|
472
|
-
"title": { "selector": "h3" },
|
|
473
|
-
"desc": { "selector": "p" }
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
**Segmented 配置参数:**
|
|
480
|
-
|
|
481
|
-
* **`anchor`** (string):
|
|
482
|
-
* 可以是 `items` 中定义的**字段名**(如 `"title"`)。
|
|
483
|
-
* 也可以是直接的 **CSS 选择器**(如 `"h3.item-title"`)。
|
|
484
|
-
* 默认使用 `items` 中第一个字段的选择器。
|
|
485
|
-
* **`depth`** (number, 可选): 从锚点向上寻找分段容器时的最大回溯层级。如果省略,它将尽可能向上冒泡,只要不与相邻分段冲突。
|
|
486
|
-
* **`strict`** (boolean, 默认: `false`): 如果为 `true`,若未找到任何锚点元素,或任何项目违反了自身的 `required` 约束,将抛出错误。
|
|
487
|
-
|
|
488
|
-
###### 5.1 进阶:处理重复标签 (relativeTo)
|
|
489
|
-
|
|
490
|
-
当一个分段中包含多个完全相同的标签(例如连续多个 `<p>` 标签)分别代表不同字段时,可以使用 `relativeTo: "previous"` 按顺序“消耗”它们。
|
|
491
|
-
|
|
492
|
-
```json
|
|
493
|
-
{
|
|
494
|
-
"id": "extract",
|
|
495
|
-
"params": {
|
|
496
|
-
"type": "array",
|
|
497
|
-
"selector": "#container",
|
|
498
|
-
"mode": {
|
|
499
|
-
"type": "segmented",
|
|
500
|
-
"anchor": ".item-start",
|
|
501
|
-
"relativeTo": "previous"
|
|
502
|
-
},
|
|
503
|
-
"items": {
|
|
504
|
-
"type": "object",
|
|
505
|
-
"order": ["id", "desc", "extra"],
|
|
506
|
-
"properties": {
|
|
507
|
-
"id": "h1",
|
|
508
|
-
"desc": "p",
|
|
509
|
-
"extra": "p"
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
* **`relativeTo: "previous"`**: 找到 `id` (h1) 后,查找 `desc` 会从该 h1 之后开始。找到 `desc` (第一个 p) 后,查找 `extra` 会从该 p 之后开始,从而成功匹配到第二个 `<p>`。
|
|
517
|
-
* **`order`**: 定义了消耗 DOM 的确切顺序。在使用 `relativeTo: "previous"` 时强烈建议显式指定。
|
|
518
|
-
|
|
519
|
-
###### 6. 数据质量控制: `required` 和 `strict`
|
|
520
|
-
|
|
521
|
-
- **`required`**: 将字段标记为必填。
|
|
522
|
-
- **Object 中**: 如果任何必填字段为 `null`,整个对象返回 `null`。
|
|
523
|
-
- **Array 中**: 缺失必填字段的项目会被自动跳过。
|
|
524
|
-
- **空值传播**: 对于没有 `selector` 的隐式对象,如果其所有子字段均为 `null`,则该对象本身被视为 `null`,从而触发父级的必填或跳过逻辑。
|
|
525
|
-
- **`strict`**:
|
|
526
|
-
- `false` (默认): 静默跳过不完整数据。
|
|
527
|
-
- `true`: 任何必填项缺失或列模式对齐失败都将抛出错误。
|
|
528
|
-
- **继承性**: 在数组模式中设置 `strict` 会自动向下传递给所有嵌套子项。
|
|
529
|
-
|
|
530
|
-
**示例:忽略缺少关键信息的项目**
|
|
531
|
-
|
|
532
|
-
```json
|
|
533
|
-
{
|
|
534
|
-
"id": "extract",
|
|
535
|
-
"params": {
|
|
536
|
-
"type": "array",
|
|
537
|
-
"selector": ".product-list",
|
|
538
|
-
"mode": "columnar",
|
|
539
|
-
"items": {
|
|
540
|
-
"name": { "selector": ".title", "required": true },
|
|
541
|
-
"price": { "selector": ".price", "required": true },
|
|
542
|
-
"discount": ".promo"
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
> 在此示例中,如果某个产品缺少 `name` 或 `price`,它将从结果数组中完全剔除。可选的 `discount` 字段缺失不会影响该项的保留。
|
|
549
|
-
|
|
550
|
-
###### 7. 隐式对象提取 (最简语法)
|
|
551
|
-
|
|
552
|
-
为了让对象提取更简单,你可以省略 `type: 'object'` 和 `properties`。如果 schema 对象包含非上下文定义关键字(如 `selector`, `has`, `exclude`, `required`, `strict`, `depth`)的键,它将被视为对象 schema,其中的键作为属性名。
|
|
553
|
-
|
|
554
|
-
> **关键字冲突处理:** 您可以安全地抓取名为 `type` 的数据字段,只要它的值不是保留的 Schema 类型(如 `"string"`, `"object"`, `"array"` 等)。
|
|
555
|
-
|
|
556
|
-
```json
|
|
557
|
-
{
|
|
558
|
-
"id": "extract",
|
|
559
|
-
"params": {
|
|
560
|
-
"selector": ".author-bio",
|
|
561
|
-
"name": ".author-name",
|
|
562
|
-
"type": ".author-rank",
|
|
563
|
-
"items": { "type": "array", "selector": "li" }
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
> **隐式对象的核心特性:**
|
|
569
|
-
>
|
|
570
|
-
> 1. **关键字处理**:常用的配置关键字如 `items`、`attribute` 或 `mode` **可以作为属性名**在隐式对象中使用。只有当显式存在 `type`(如 `array`)时,它们才会被视为配置项。同时,`required`、`strict` 和 `depth` 也会被当作上下文定义关键字处理。
|
|
571
|
-
> 2. **字符串简写**:你可以直接使用字符串作为属性值(例如 `"email": "a.email"`),它会自动扩展为 `{ "selector": "a.email" }`。
|
|
572
|
-
> 3. **上下文分离**:只有 `selector`、`has`、`exclude`、`required`、`strict` 和 `depth` 用于定义隐式对象的上下文及校验逻辑;所有其他键都被视为要提取的数据。
|
|
573
|
-
> 4. **空值传递 (Null Propagation)**: 如果一个隐式对象没有 `selector`,且其所有的子属性提取结果均为 `null`,则该对象本身返回 `null`。这对于父对象的 `required` 校验或数组中的跳过逻辑至关重要。
|
|
574
|
-
|
|
575
|
-
###### 8. 精确筛选: `has` 和 `exclude`
|
|
576
|
-
|
|
577
|
-
您可以在任何包含 **`selector`** 的 Schema 中使用 **`has`** 和 **`exclude`** 字段来精确控制元素的选择。
|
|
578
|
-
|
|
579
|
-
* **`has`**: 一个 CSS 选择器,用于确保所选元素**必须包含**匹配此选择器的后代元素。
|
|
580
|
-
* **`exclude`**: 一个 CSS 选择器,用于从结果中**排除**匹配此选择器的元素。
|
|
581
|
-
|
|
582
|
-
**完整示例: 提取包含图片且未被标记为"草稿"的文章链接**
|
|
583
|
-
|
|
584
|
-
```json
|
|
585
|
-
{
|
|
586
|
-
"actions": [
|
|
587
|
-
{ "id": "goto", "params": { "url": "https://example.com/articles" } },
|
|
588
|
-
{
|
|
589
|
-
"id": "extract",
|
|
590
|
-
"params": {
|
|
591
|
-
"type": "array",
|
|
592
|
-
"selector": "div.article-card",
|
|
593
|
-
"has": "img.cover-image",
|
|
594
|
-
"exclude": ".draft",
|
|
595
|
-
"items": {
|
|
596
|
-
"selector": "a.title-link",
|
|
597
|
-
"attribute": "href"
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
]
|
|
602
|
-
}
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
> 上述 `extract` Action 会:
|
|
606
|
-
>
|
|
607
|
-
> 1. 找到所有 `div.article-card` 元素。
|
|
608
|
-
> 2. 筛选出其中必须包含 `<img class="cover-image">` 的元素。
|
|
609
|
-
> 3. 再从结果中排除掉自身带有 `.draft` 类的元素。
|
|
610
|
-
> 4. 对于最终剩下的每个 `div.article-card`, 找到其后代 `a.title-link` 并提取 `href` 属性。
|
|
351
|
+
---
|
|
611
352
|
|
|
612
353
|
### 通过“组合”构建高级语义 Action
|
|
613
354
|
|
|
@@ -787,7 +528,7 @@ await fetchWeb({
|
|
|
787
528
|
* **定义**: 任何可序列化的数据结构(对象、数组、字符串等)。
|
|
788
529
|
* **用途**: 业务数据提取的主要机制。
|
|
789
530
|
* **用法**: 当你的动作产生处理后的业务数据(而不是代表整个页面或系统状态)时使用。
|
|
790
|
-
* **系统行为**: 如果 Action 配置包含 `storeAs: "key"`,框架会自动将 `result` 保存到 `context.outputs["key"]`
|
|
531
|
+
* **系统行为**: 如果 Action 配置包含 `storeAs: "key"`,框架会自动将 `result` 保存到 `context.outputs["key"]` 中。如果目标键已包含一个对象且新结果也是一个对象,它们将被合并(浅合并)而不是覆盖。这允许通过多个 `extract` 动作将数据累积到同一个输出键中。
|
|
791
532
|
* **典型 Action**: `extract`。
|
|
792
533
|
* **示例**:
|
|
793
534
|
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# 🔍 Extract 操作深度指南 (The Data Surgeon's Manual)
|
|
2
|
+
|
|
3
|
+
[English](./README.action.extract.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
`extract` 是 `@isdk/web-fetcher` 的灵魂。它不只是一个“抓取器”,更像是一个**智能转换器**,能把杂乱无章的 HTML 变成你想要的精美 JSON。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ⚡ 1. 快速开始:简写魔法 (Shorthand Magic)
|
|
10
|
+
|
|
11
|
+
**场景**:结构简单的标准网页。当你只想快点看到结果,不想写长长的 JSON 配置时。
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"action": "extract",
|
|
16
|
+
"params": {
|
|
17
|
+
"title": "h1", // 把 h1 里的文字抓出来存为 'title'
|
|
18
|
+
"link": { "selector": "a.main", "attribute": "href" }, // 精确提取链接属性
|
|
19
|
+
"tags": { "type": "array", "selector": ".tag-item" } // 一口气抓下一组标签
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 📄 2. 提取基本值:手术镊子 (The Tweezers)
|
|
27
|
+
|
|
28
|
+
**场景**:处理网页上具体的某一个数据点,如价格、标题或 ID。
|
|
29
|
+
|
|
30
|
+
### 镊子参数解析 (Value Properties)
|
|
31
|
+
|
|
32
|
+
* **`selector`**: CSS 选择器,告诉镊子要去哪里夹东西。
|
|
33
|
+
* **`type`**: 自动类型转换。支持 `string`, `number` (自动剔除货币符号、单位,只留数字), `boolean`, `html`。
|
|
34
|
+
* **`mode`**: 提取的模式。
|
|
35
|
+
* `innerText`: **(强烈推荐使用)** 就像人眼观察一样,它只取网页上可见的文字,并自动处理换行,过滤掉隐藏的 HTML 噪音。
|
|
36
|
+
* `text`: 原始文本内容,对应源码里的 `textContent`,包含隐藏字符。
|
|
37
|
+
* `outerHTML`: 抓取包含标签自身的完整 HTML 代码。
|
|
38
|
+
* **`attribute`**: 如果你要抓的不是文字,而是属性(如 `href`, `src`),就在这里指定属性名。
|
|
39
|
+
* **`depth`**: 向上回溯。匹配到元素后,先向上跳 N 层父节点再提取(比如你想抓一个按钮的父容器上的 ID)。
|
|
40
|
+
|
|
41
|
+
**示例**:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"selector": ".price-tag",
|
|
46
|
+
"type": "number", // 自动把 "¥99.00" 转换成数字 99
|
|
47
|
+
"mode": "innerText", // 确保拿到的是干净的文字
|
|
48
|
+
"attribute": "data-v", // 提取 data-v 属性
|
|
49
|
+
"depth": 1 // 抓取该元素父节点的属性
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 📦 3. 提取对象:便当盒 (The Bento Box)
|
|
56
|
+
|
|
57
|
+
**场景**:当你需要把多个相关的字段(如用户的“姓名”、“头像”、“简介”)打包在一起,形成一个完整的结构时。
|
|
58
|
+
|
|
59
|
+
### 便当盒参数解析 (Object Properties)
|
|
60
|
+
|
|
61
|
+
* **`type: "object"`**: 明确告诉引擎,这是一个需要打包的对象。
|
|
62
|
+
* **`selector`**: **对象的根容器**。这非常重要!内部所有属性的 `selector` 都会相对于这个容器查找。如果你把容器定在 `.user-card`,内部写 `img` 就会只抓这张卡片里的图。
|
|
63
|
+
* **`properties`**: **核心配置**。在这里定义你想要的 JSON 键名(Key)和对应的提取规则。
|
|
64
|
+
* **`required`**: 如果你认为某个字段是“灵魂”,抓不到它这个对象就没意义,请设为 `true`。此时若提取失败,整个对象会返回 `null`。
|
|
65
|
+
* **`depth`**: 用于触发“向上探索”逻辑的深度(详见进阶章节)。
|
|
66
|
+
|
|
67
|
+
**示例:打包用户信息**
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"type": "object",
|
|
72
|
+
"selector": ".user-card",
|
|
73
|
+
"properties": {
|
|
74
|
+
"name": { "selector": ".username", "required": true },
|
|
75
|
+
"bio": ".user-description",
|
|
76
|
+
"avatar": { "selector": "img.avatar", "attribute": "src" }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **💡 透明盒子 (隐式对象)**:如果你省略 `type: "object"` 且不写 `selector`,这个盒子就成了“透明的”。如果内部所有字段都抓不到结果,整个盒子会自动消失。在过滤列表中的广告或空项时非常神奇。
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 📑 4. 提取数组:分拣模式 (The Sorting Machines)
|
|
86
|
+
|
|
87
|
+
**场景**:处理成排出现的数据,如搜索结果列表、新闻瀑布流、或一组分类标签。
|
|
88
|
+
|
|
89
|
+
### 分拣机参数解析 (Array Properties)
|
|
90
|
+
|
|
91
|
+
* **`type: "array"`**: 明确声明你要提取的是一个列表。
|
|
92
|
+
* **`selector`**: **扫描范围**。在默认模式下,它匹配每一个项目的“外壳”;在平铺模式下,它匹配整个大的列表容器。
|
|
93
|
+
* **`items`**: **每一项的图纸**。注意,数组里用的是 `items` 而不是 `properties`。它定义了每一行数据长什么样。
|
|
94
|
+
* **`mode`**: 决定分拣机的工作模式。
|
|
95
|
+
* `"nested"`: (默认) 每一个项目都有自己的“格子”(容器)。
|
|
96
|
+
* `"columnar"`: 数据按列排布,没有行容器(像 Excel 表格)。
|
|
97
|
+
* `"segmented"`: 数据完全平铺,通过“锚点”来切分段落。
|
|
98
|
+
* **`limit`**: 限制抓取的最大条数,防止数据量过大。
|
|
99
|
+
* **`inference`**: 启发式推断。当列表结构不规范时,开启它可以让引擎自动尝试修正错位。
|
|
100
|
+
|
|
101
|
+
### 4.1 Nested (蛋托模式):最稳定、首选
|
|
102
|
+
|
|
103
|
+
**场景**:每个项目都被包裹在 `<li>` 或 `.item` 这种容器里。这是最稳、性能最好的选择。
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"type": "array",
|
|
108
|
+
"selector": ".product-list .item", // 匹配每一个产品的外壳
|
|
109
|
+
"items": {
|
|
110
|
+
"name": "h3",
|
|
111
|
+
"price": ".price"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 4.2 Columnar (列对齐模式):处理表格类平铺
|
|
117
|
+
|
|
118
|
+
**场景**:数据像表格一样。左边一列是标题,右边一列是价格,但它们没有共同的父节点包裹。
|
|
119
|
+
**广播功能 (Broadcasting)**:如果某个字段在每一项里都一样(如列表的分类),你只需在总容器提取一次,它会自动“广播”给每一行。
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"type": "array",
|
|
124
|
+
"selector": "#table-container",
|
|
125
|
+
"mode": "columnar",
|
|
126
|
+
"items": {
|
|
127
|
+
"name": ".title-cols",
|
|
128
|
+
"date": "" // selector 为空,自动广播总容器里的日期信息
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 4.3 Segmented (切割模式):处理无序平铺
|
|
134
|
+
|
|
135
|
+
**场景**:页面结构极其糟糕,所有内容都是兄弟节点并排,只能靠某个标志(如 `h2` 标题)来区分段落。
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"type": "array",
|
|
140
|
+
"selector": ".content-body",
|
|
141
|
+
"mode": { "type": "segmented", "anchor": "h2" }, // 看到 h2 就切一刀
|
|
142
|
+
"items": {
|
|
143
|
+
"section_title": "h2",
|
|
144
|
+
"text": "p"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 🛡️ 5. 质量控制:过滤器与筛子 (Filters & Sieves)
|
|
152
|
+
|
|
153
|
+
**场景**:抓取时混入了广告、缺货提示或者不需要的垃圾项。
|
|
154
|
+
|
|
155
|
+
* **`has`**:**精华过滤器**。只有包含特定子元素时才抓取。例如:`"has": ".promo-tag"` 表示只有带优惠标签的商品我才要。
|
|
156
|
+
* **`exclude`**:**垃圾剔除器**。排除匹配的元素。例如:`"exclude": ".is-advertisement"`。
|
|
157
|
+
* **`strict`**:**强迫症模式**。一旦发现必填项缺失,直接报错停机,而不是默默返回 `null`。
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🧠 6. 核心进阶:边界与作用域控制 (Boundary Mastery)
|
|
162
|
+
|
|
163
|
+
这是处理那些没有 ID、没有类名、结构平铺的烂网页的“手术刀”。
|
|
164
|
+
|
|
165
|
+
### 6.1 顺序切片:解决“双胞胎标签”定位难题
|
|
166
|
+
|
|
167
|
+
* **痛点**:网页上一排 `<span>` 或 `p`,长得一模一样。你用同一个选择器去抓,每次都只能抓到第一个。
|
|
168
|
+
* **解决方案**:`relativeTo: "previous"` (相对于上一个)。
|
|
169
|
+
* **原理解析**:引擎会像“读书”一样记住上次读到了哪。当第一个字段提取完后,引擎会夹个“书签”。提取下一个字段时,引擎会**从书签之后**开始找。
|
|
170
|
+
* **关键点**:由于这是按顺序寻找,建议显式设置 `order: ["name", "job"]` 来确保引擎不会因为 JSON 键名的乱序而导致书签位置错乱,虽然大多数js引擎是按照对象keys的声明顺序来排序的。
|
|
171
|
+
* **示例场景**:
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<p>姓名</p> <span>张三</span>
|
|
175
|
+
<p>职位</p> <span>经理</span>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
* **配置示例**:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"relativeTo": "previous",
|
|
183
|
+
"order": ["k1", "v1", "k2", "v2"], // 强制顺序,防止书签错位
|
|
184
|
+
"properties": {
|
|
185
|
+
"k1": "p", // 抓到第一个 p (姓名)
|
|
186
|
+
"v1": "span", // 从 k1 之后找,抓到张三
|
|
187
|
+
"k2": "p", // 从 v1 之后找,抓到第二个 p (职位)
|
|
188
|
+
"v2": "span" // 从 k2 之后找,抓到经理
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 6.2 分段隔离与 LCA:筑起“逻辑围墙”
|
|
194
|
+
|
|
195
|
+
* **痛点**:平铺结构中提取列表,如果没有墙,Item 1 的搜索可能会越界抓到 Item 2 的内容。
|
|
196
|
+
* **解决方案**:使用 `mode: "segmented"` 配合 `anchor`。
|
|
197
|
+
* **原理解析**:引擎计算两个锚点之间的“最小公共祖先 (LCA)”,并逻辑上筑起围墙,确保搜索范围死死锁在 Item 内部。
|
|
198
|
+
* **示例场景**:
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<h2 class="title">文章1</h2>
|
|
202
|
+
<p class="summary">摘要1</p>
|
|
203
|
+
<h2 class="title">文章2</h2>
|
|
204
|
+
<p class="summary">摘要2</p>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
* **配置示例**:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"type": "array",
|
|
212
|
+
"mode": { "type": "segmented", "anchor": "h2.title" }, // 以 h2 为墙
|
|
213
|
+
"items": {
|
|
214
|
+
"title": "h2",
|
|
215
|
+
"desc": "p"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 6.3 向上探索 (Bubble-up):给提取一次“后悔药”
|
|
221
|
+
|
|
222
|
+
* **痛点**:作用域定在卡片内部,但关键数据(如卡片 ID)在卡片外面的父容器上。
|
|
223
|
+
* **解决方案**:设置 `depth` (向上回退层数) 并配合 `required`。
|
|
224
|
+
* **原理解析**:
|
|
225
|
+
1. 引擎在当前范围内找必填项,没找到。
|
|
226
|
+
2. 它会根据 `depth: 1` 自动把视野“向后退一步”,退到父节点。
|
|
227
|
+
3. 在更大的视野里重新扫描,从而找齐那些“由于视野太窄而漏掉”的数据。
|
|
228
|
+
* **示例场景**:
|
|
229
|
+
|
|
230
|
+
```html
|
|
231
|
+
<div class="wrapper" data-id="ID_001">
|
|
232
|
+
<div class="content-box">
|
|
233
|
+
<h3 class="title">产品名称</h3>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
* **配置示例**:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"selector": ".content-box", // 初始视角太窄
|
|
243
|
+
"depth": 1, // 允许退后一级
|
|
244
|
+
"properties": {
|
|
245
|
+
"product_id": {
|
|
246
|
+
"selector": "..", // ".." 代表向上找父节点
|
|
247
|
+
"attribute": "data-id",
|
|
248
|
+
"required": true // 没找到 ID 就触发向上回溯
|
|
249
|
+
},
|
|
250
|
+
"name": "h3"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 💡 7. 最佳实践提示
|
|
258
|
+
|
|
259
|
+
1. **容器优先**:只要 HTML 里有 `.item` 这种包裹容器,永远优先选择默认的 `Nested` 模式,这是性能和稳定性最好的选择。
|
|
260
|
+
2. **innerText 推荐作为首选**:在 90% 的场景下,它都比 `text` 好用,因为它拿到的数据最干净,且能自动处理换行。
|
|
261
|
+
3. **遇到平铺找锚点**:当你看到一堆兄弟节点并排且没有外壳包裹时,第一时间就该想到 `segmented` + `anchor` 的组合。
|
|
262
|
+
4. **善用 required 过滤空数据**:如果你抓到一个列表,有些项是空的(比如预留位),给核心字段加上 `required: true`,那些空项就会自动变成 `null`,方便你在代码里过滤。
|
|
263
|
+
5. **调试必备:先看 Scope**:如果抓不到数据,先检查是不是 `selector` 写得太死,或者尝试把 `depth` 调大一点。
|