@isdk/web-fetcher 0.2.0 → 0.2.2

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/README.action.cn.md +32 -24
  2. package/README.action.md +14 -4
  3. package/README.cn.md +10 -2
  4. package/README.hackernews.md +52 -0
  5. package/README.md +10 -2
  6. package/README.reddit.md +248 -0
  7. package/README.v2ex.md +109 -0
  8. package/dist/index.d.mts +5 -3
  9. package/dist/index.d.ts +5 -3
  10. package/dist/index.js +1 -1
  11. package/dist/index.mjs +1 -1
  12. package/docs/README.md +10 -2
  13. package/docs/_media/README.action.md +14 -4
  14. package/docs/_media/README.cn.md +10 -2
  15. package/docs/classes/CheerioFetchEngine.md +91 -69
  16. package/docs/classes/ClickAction.md +23 -23
  17. package/docs/classes/ExtractAction.md +23 -23
  18. package/docs/classes/FetchAction.md +23 -23
  19. package/docs/classes/FetchEngine.md +87 -69
  20. package/docs/classes/FetchSession.md +8 -8
  21. package/docs/classes/FillAction.md +23 -23
  22. package/docs/classes/GetContentAction.md +23 -23
  23. package/docs/classes/GotoAction.md +23 -23
  24. package/docs/classes/PauseAction.md +23 -23
  25. package/docs/classes/PlaywrightFetchEngine.md +91 -69
  26. package/docs/classes/SubmitAction.md +23 -23
  27. package/docs/classes/WaitForAction.md +23 -23
  28. package/docs/classes/WebFetcher.md +5 -5
  29. package/docs/enumerations/FetchActionResultStatus.md +4 -4
  30. package/docs/functions/fetchWeb.md +2 -2
  31. package/docs/interfaces/BaseFetchActionProperties.md +9 -9
  32. package/docs/interfaces/BaseFetchCollectorActionProperties.md +13 -13
  33. package/docs/interfaces/BaseFetcherProperties.md +29 -21
  34. package/docs/interfaces/DispatchedEngineAction.md +4 -4
  35. package/docs/interfaces/ExtractActionProperties.md +9 -9
  36. package/docs/interfaces/FetchActionInContext.md +13 -13
  37. package/docs/interfaces/FetchActionProperties.md +10 -10
  38. package/docs/interfaces/FetchActionResult.md +6 -6
  39. package/docs/interfaces/FetchContext.md +43 -31
  40. package/docs/interfaces/FetchEngineContext.md +38 -26
  41. package/docs/interfaces/FetchMetadata.md +5 -5
  42. package/docs/interfaces/FetchResponse.md +13 -13
  43. package/docs/interfaces/FetchReturnTypeRegistry.md +7 -7
  44. package/docs/interfaces/FetchSite.md +36 -24
  45. package/docs/interfaces/FetcherOptions.md +35 -23
  46. package/docs/interfaces/GotoActionOptions.md +6 -6
  47. package/docs/interfaces/PendingEngineRequest.md +3 -3
  48. package/docs/interfaces/SubmitActionOptions.md +2 -2
  49. package/docs/interfaces/WaitForActionOptions.md +4 -4
  50. package/docs/type-aliases/BaseFetchActionOptions.md +1 -1
  51. package/docs/type-aliases/BaseFetchCollectorOptions.md +1 -1
  52. package/docs/type-aliases/BrowserEngine.md +1 -1
  53. package/docs/type-aliases/FetchActionCapabilities.md +1 -1
  54. package/docs/type-aliases/FetchActionCapabilityMode.md +1 -1
  55. package/docs/type-aliases/FetchActionOptions.md +1 -1
  56. package/docs/type-aliases/FetchEngineAction.md +1 -1
  57. package/docs/type-aliases/FetchEngineType.md +1 -1
  58. package/docs/type-aliases/FetchReturnType.md +1 -1
  59. package/docs/type-aliases/FetchReturnTypeFor.md +1 -1
  60. package/docs/type-aliases/OnFetchPauseCallback.md +1 -1
  61. package/docs/type-aliases/ResourceType.md +1 -1
  62. package/docs/variables/DefaultFetcherProperties.md +1 -1
  63. package/package.json +2 -1
@@ -80,21 +80,21 @@ export class FillAction extends FetchAction {
80
80
 
81
81
  #### `goto`
82
82
 
83
- 导航到新的 URL。
83
+ 将浏览器导航至指定 URL。
84
84
 
85
85
  * **`id`**: `goto`
86
86
  * **`params`**:
87
87
  * `url` (string): 要导航到的 URL。
88
- * ...其他导航选项,如 `waitUntil`, `timeout`,这些选项会传递给引擎。
88
+ * ...其他导航选项,如 **`waitUntil`**, **`timeout`**,这些选项会传递给引擎。
89
89
  * **`returns`**: `response`
90
90
 
91
91
  #### `click`
92
92
 
93
- 点击一个由选择器指定的元素。
93
+ 点击由 **`selector`** (CSS 选择器) 指定的元素。
94
94
 
95
95
  * **`id`**: `click`
96
96
  * **`params`**:
97
- * `selector` (string): 用于标识要点击元素的 CSS 选择器或 XPath。
97
+ * **`selector`** (string): 用于标识要点击元素的 CSS 选择器。
98
98
  * **`returns`**: `none`
99
99
 
100
100
  #### `fill`
@@ -103,9 +103,14 @@ export class FillAction extends FetchAction {
103
103
 
104
104
  * **`id`**: `fill`
105
105
  * **`params`**:
106
- * `selector` (string): 输入元素的选择器。
106
+ * **`selector`** (string): 输入元素的选择器。
107
107
  * `value` (string): 要填入元素中的文本。
108
- * **`returns`**: `none`
108
+ * **`returns`**: `response`
109
+
110
+ > **注意**:返回内容的具体行为在不同引擎之间存在差异。
111
+ >
112
+ > * **`cheerio`**:此引擎直接操作其内部的 HTML 表示,因此返回的内容会包含填充的值。
113
+ > * **`playwright`**:此引擎返回的是页面的渲染后 HTML (类似于 `document.documentElement.outerHTML`)。然而,当 `page.fill()` 更新输入框时,它修改的是该输入框元素的内部 `value` 属性。这个属性不总会被序列化为 HTML 源代码中的 `value` 特性。因此,调用 `page.content()` 所返回的 HTML 中将**不会**看到填充的值。
109
114
 
110
115
  #### `submit`
111
116
 
@@ -113,28 +118,31 @@ export class FillAction extends FetchAction {
113
118
 
114
119
  * **`id`**: `submit`
115
120
  * **`params`**:
116
- * `selector` (string, optional): 表单元素的选择器。
121
+ * **`selector`** (string, optional): 表单元素的选择器。
117
122
  * **`returns`**: `none`
118
123
 
119
124
  #### `waitFor`
120
125
 
121
- 暂停执行以等待特定条件满足。
126
+ 暂停执行,以等待一个或多个条件的满足。
127
+
128
+ 在 `browser` 模式下,如果提供了多个条件,它们将按顺序依次等待。例如,它会先等待选择器出现,然后等待网络空闲,最后再等待指定的毫秒数。
122
129
 
123
130
  * **`id`**: `waitFor`
124
- * **`params`**: 一个指定等待条件的对象 (例如 `ms`, `selector`, `networkIdle`)。
131
+ * **`params`**: 一个指定等待条件的对象,可包含以下一个或多个键:
132
+ * **`ms`** (number): 等待指定的毫秒数。两个引擎都支持。
133
+ * **`selector`** (string): 等待匹配的选择器出现在页面中。仅 `browser` 模式支持。
134
+ * **`networkIdle`** (boolean): 等待直到网络空闲(即,在一段时间内没有新的网络请求)。仅 `browser` 模式支持。
125
135
  * **`returns`**: `none`
126
136
 
127
137
  #### `pause`
128
138
 
129
- 暂停 Action 脚本的执行,以允许用户手动介入(例如,解决验证码)。
130
-
131
- 此 Action **必须**在 `fetchWeb` 的选项中提供一个 `onPause` 回调处理器。当此 Action 被触发时,它会调用 `onPause` 处理器并等待其执行完成。
139
+ 暂停 Action 脚本的执行,以允许用户手动介入(例如,解决验证码)。此 Action **必须**在 **`fetchWeb`** 的选项中提供一个 **`onPause`** 回调处理器。当此 Action 被触发时,它会调用 **`onPause`** 处理器并等待其执行完成。
132
140
 
133
141
  * **`id`**: `pause`
134
142
  * **`params`**:
135
- * `selector` (string, optional): 如果提供,仅当匹配此选择器的元素存在时,Action 才会暂停。
136
- * `attribute` (string, optional): 与 `selector` 配合使用。如果提供,仅当元素存在且拥有该指定属性时,Action 才会暂停。
137
- * `message` (string, optional): 一个将传递给 `onPause` 处理器的消息,可用于向用户显示提示信息。
143
+ * **`selector`** (string, optional): 如果提供,仅当匹配此选择器的元素存在时,Action 才会暂停。
144
+ * **`attribute`** (string, optional): 与 **`selector`** 配合使用。如果提供,仅当元素存在且拥有该指定属性时,Action 才会暂停。
145
+ * **`message`** (string, optional): 一个将传递给 **`onPause`** 处理器的消息,可用于向用户显示提示信息。
138
146
  * **`returns`**: `none`
139
147
 
140
148
  **示例:在 Google 搜索中处理 CAPTCHA**
@@ -193,10 +201,10 @@ await fetchWeb({
193
201
 
194
202
  #### `extract`
195
203
 
196
- 使用一个强大且声明式的 Schema 从当前页面中提取结构化数据。这是进行数据采集的核心 Action。
204
+ 使用一个强大且声明式的 **`ExtractSchema`** 从当前页面中提取结构化数据。这是进行数据采集的核心 Action。
197
205
 
198
206
  * **`id`**: `extract`
199
- * **`params`**: 一个 `ExtractSchema` 对象, 用于定义提取规则。
207
+ * **`params`**: 一个 **`ExtractSchema`** 对象, 用于定义提取规则。
200
208
  * **`returns`**: `any` (提取出的数据)
201
209
 
202
210
  ##### 提取 Schema 详解
@@ -205,7 +213,7 @@ await fetchWeb({
205
213
 
206
214
  ###### 1. 提取单个值
207
215
 
208
- 最基础的提取,可以指定 `selector` (CSS 选择器), `attribute` (要提取的属性名), 以及 `type` (string, number, boolean, html)。
216
+ 最基础的提取,可以指定 **`selector`** (CSS 选择器), **`attribute`** (要提取的属性名), 以及 **`type`** (string, number, boolean, html)。
209
217
 
210
218
  ```json
211
219
  {
@@ -221,7 +229,7 @@ await fetchWeb({
221
229
 
222
230
  ###### 2. 提取对象
223
231
 
224
- 通过 `type: 'object'``properties` 字段来定义一个结构化对象。
232
+ 通过 **`type: 'object'`****`properties`** 字段来定义一个结构化对象。
225
233
 
226
234
  ```json
227
235
  {
@@ -239,7 +247,7 @@ await fetchWeb({
239
247
 
240
248
  ###### 3. 提取数组 (便捷用法)
241
249
 
242
- 通过 `type: 'array'` 来提取一个列表。为了让最常见的操作更简单,我们提供了一些便捷用法。
250
+ 通过 **`type: 'array'`** 来提取一个列表。为了让最常见的操作更简单,我们提供了一些便捷用法。
243
251
 
244
252
  * **提取文本数组 (默认行为)**: 当您想提取一个文本列表时,只需提供选择器,省略 `items` 即可。这是最常见的用法。
245
253
 
@@ -263,7 +271,7 @@ await fetchWeb({
263
271
 
264
272
  > 上例将返回一个包含所有 `<li>` 标签文本的数组, 如 `["tech", "news"]`。
265
273
 
266
- * **提取属性数组 (快捷方式)**: 当您只想提取一个属性列表(例如所有链接的 `href`)时,也无需嵌套 `items`。直接在 `array` 定义中声明 `attribute` 即可。
274
+ * **提取属性数组 (快捷方式)**: 当您只想提取一个属性列表(例如所有链接的 **`href`**)时,也无需嵌套 `items`。直接在 `array` 定义中声明 **`attribute`** 即可。
267
275
 
268
276
  ```json
269
277
 
@@ -289,10 +297,10 @@ await fetchWeb({
289
297
 
290
298
  ###### 4. 精确筛选: `has` 和 `exclude`
291
299
 
292
- 您可以在任何包含 `selector` 的 Schema 中使用 `has``exclude` 字段来精确控制元素的选择。
300
+ 您可以在任何包含 **`selector`** 的 Schema 中使用 **`has`****`exclude`** 字段来精确控制元素的选择。
293
301
 
294
- * `has`: 一个 CSS 选择器,用于确保所选元素**必须包含**匹配此选择器的后代元素。
295
- * `exclude`: 一个 CSS 选择器,用于从结果中**排除**匹配此选择器的元素。
302
+ * **`has`**: 一个 CSS 选择器,用于确保所选元素**必须包含**匹配此选择器的后代元素。
303
+ * **`exclude`**: 一个 CSS 选择器,用于从结果中**排除**匹配此选择器的元素。
296
304
 
297
305
  **完整示例: 提取包含图片且未被标记为"草稿"的文章链接**
298
306
 
package/README.action.md CHANGED
@@ -94,7 +94,7 @@ Clicks on an element specified by a selector.
94
94
 
95
95
  * **`id`**: `click`
96
96
  * **`params`**:
97
- * `selector` (string): A CSS selector or XPath to identify the element to click.
97
+ * `selector` (string): A CSS selector to identify the element to click.
98
98
  * **`returns`**: `none`
99
99
 
100
100
  #### `fill`
@@ -105,7 +105,12 @@ Fills an input field with a specified value.
105
105
  * **`params`**:
106
106
  * `selector` (string): A selector for the input element.
107
107
  * `value` (string): The text to fill into the element.
108
- * **`returns`**: `none`
108
+ * **`returns`**: `response`
109
+
110
+ > **Note**: The behavior of the returned content differs between engines.
111
+ >
112
+ > * **`cheerio`**: This engine directly manipulates its internal HTML representation, so the returned content will include the filled value.
113
+ > * **`playwright`**: This engine returns the rendered HTML of the page (similar to `document.documentElement.outerHTML`). However, when `page.fill()` updates an input, it changes the input's internal `value` property. This property is not always serialized back to the `value` attribute in the HTML source. As a result, the filled value will **not** be visible in the HTML returned by `page.content()`.
109
114
 
110
115
  #### `submit`
111
116
 
@@ -118,10 +123,15 @@ Submits a form.
118
123
 
119
124
  #### `waitFor`
120
125
 
121
- Pauses execution to wait for a specific condition to be met.
126
+ Pauses execution to wait for one or more conditions to be met.
127
+
128
+ In `browser` mode, if multiple conditions are provided, they are awaited sequentially. For example, it will first wait for the selector to appear, then wait for the network to be idle, and finally wait for the specified duration.
122
129
 
123
130
  * **`id`**: `waitFor`
124
- * **`params`**: An object specifying the wait condition (e.g., `ms`, `selector`, `networkIdle`).
131
+ * **`params`**: An object specifying the wait condition, which can contain one or more of the following keys:
132
+ * **`ms`** (number): Waits for the specified number of milliseconds. Supported by both engines.
133
+ * **`selector`** (string): Waits for an element matching the selector to appear on the page. Supported only in `browser` mode.
134
+ * **`networkIdle`** (boolean): Waits until the network is idle (i.e., no new network requests for a period of time). Supported only in `browser` mode.
125
135
  * **`returns`**: `none`
126
136
 
127
137
  #### `pause`
package/README.cn.md CHANGED
@@ -1,9 +1,16 @@
1
1
  # 🕸️ @isdk/web-fetcher
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/%40isdk%2Fweb-fetcher)](https://www.npmjs.com/package/@isdk/web-fetcher)
4
+ [![npm downloads](https://img.shields.io/npm/dw/%40isdk%2Fweb-fetcher)](https://www.npmjs.com/package/@isdk/web-fetcher)
5
+ [![License](https://img.shields.io/github/license/isdk/web-fetcher.js)](https://github.com/isdk/web-fetcher.js/blob/main/LICENSE)
6
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js)](https://nodejs.org/)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Types%20included-3178C6?logo=typescript)](https://www.typescriptlang.org/)
8
+ [![GitHub Stars](https://img.shields.io/github/stars/isdk/web-fetcher.js?logo=github)](https://github.com/isdk/web-fetcher.js)
9
+ ![antibot](https://img.shields.io/badge/antibot-optional-orange)
10
+
3
11
  [English](./README.md) | 简体中文
4
12
 
5
- > 一个功能强大且灵活的 Web 抓取与浏览器自动化库。
6
- > 它采用双引擎架构(HTTP 和浏览器)和声明式动作系统,是 AI 代理和复杂数据抓取任务的理想选择。
13
+ > 一个面向AI的网页自动化库,它将复杂的网页交互简化为声明式JSON动作脚本。一次编写,你的脚本即可在快速的 **`http`** 模式(用于静态内容)或完整的 **`browser`** 模式(用于动态站点)下运行。可选的 **`antibot`** 标志有助于绕过检测机制。该库专为有针对性的、面向任务的数据提取而设计(例如,从页面Y获取数据X),而非用于构建全站爬虫。
7
14
 
8
15
  ---
9
16
 
@@ -137,6 +144,7 @@ searchGoogle('gemini');
137
144
  * `fill`: 用指定的值填充一个输入字段。
138
145
  * `submit`: 提交一个表单。
139
146
  * `waitFor`: 暂停执行以等待特定条件(例如,超时、选择器出现或网络空闲)。
147
+ * `pause`: 暂停执行以进行手动干预(例如,解决验证码)。
140
148
  * `getContent`: 获取当前页面状态的完整内容(HTML、文本等)。
141
149
  * `extract`: 使用富有表现力的声明式 Schema,可轻松提取页面中的任意结构化数据。
142
150
 
@@ -0,0 +1,52 @@
1
+ ### **Title:**
2
+
3
+ `Show HN: I built a declarative web automation library for AI agents`
4
+
5
+ ---
6
+
7
+ ### **First Comment (Final Masterpiece Version):**
8
+
9
+ GitHub: https://github.com/isdk/web-fetcher.js
10
+
11
+ Hey HN,
12
+
13
+ I’ve been building some AI agents lately and ran into a fundamental problem: LLMs are great at generating structured data like JSON, but they are terrible at writing the brittle, procedural JavaScript needed for web automation (`await page.click(...)`, `await page.waitFor(...)`, etc.).
14
+
15
+ This led me to build `@isdk/web-fetcher`, a library designed around a few core principles to solve this mismatch.
16
+
17
+ * **Declarative JSON Actions for AI:** Instead of code, you define tasks in a simple JSON "plan." This is a format an LLM can easily generate and reason about, making it a much more natural interface for an agent.
18
+
19
+ * **A Unified, Dual-Engine API:** It has a fast `http` engine (using Cheerio) and a full `browser` engine (using Playwright). The key was to design a single API (with actions like `extract`, `fill`, etc.) that works across both, allowing the library to execute your plan in the most efficient way.
20
+
21
+ * **Simple but Powerful Anti-Bot Evasion:** In `browser` mode, you just add `antibot: true`. Under the hood, this does more than just rotate user-agents; it meticulously equips the browser with a more convincing, human-like fingerprint—modifying TLS signatures, ordering headers correctly, and patching navigator properties to evade common commercial bot detectors. It's a complex problem solved with a single switch.
22
+
23
+ To avoid reinventing the core crawling infrastructure, the library is built on top of the excellent `crawlee` library. My work was to design and implement this declarative, AI-friendly layer on top.
24
+
25
+ Here’s an example of what a browser-based plan with the anti-bot flag looks like:
26
+
27
+ ```typescript
28
+ // Define a plan to access a site with strong bot detection
29
+ const plan = {
30
+ url: 'https://some-protected-site.com',
31
+ engine: 'browser',
32
+ antibot: true, // Just flip this switch for enhanced evasion
33
+ actions: [
34
+ { id: 'fill', params: { selector: '#search-input', value: 'Hacker News' } },
35
+ { id: 'click', params: { selector: '#search-button' } },
36
+ { id: 'waitFor', params: { selector: '#results' } },
37
+ {
38
+ id: 'extract',
39
+ params: {"type": "array", "selector": "#results a", "attribute": "href"},
40
+ storeAs: 'searchResults',
41
+ },
42
+ ]
43
+ };
44
+
45
+ const { result, outputs } = await fetchWeb(plan);
46
+ console.log('Search finalUrl:', result?.finalUrl);
47
+ console.log('Outputs searchResults:', outputs.searchResults);
48
+ ```
49
+
50
+ The project is new, and I believe this declarative approach is a more robust path forward for building capable AI agents that can interact with the web. I'd love to hear the community's thoughts and critiques on this design.
51
+
52
+ Thanks
package/README.md CHANGED
@@ -1,9 +1,16 @@
1
1
  # 🕸️ @isdk/web-fetcher
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/%40isdk%2Fweb-fetcher)](https://www.npmjs.com/package/@isdk/web-fetcher)
4
+ [![npm downloads](https://img.shields.io/npm/dw/%40isdk%2Fweb-fetcher)](https://www.npmjs.com/package/@isdk/web-fetcher)
5
+ [![License](https://img.shields.io/github/license/isdk/web-fetcher.js)](https://github.com/isdk/web-fetcher.js/blob/main/LICENSE)
6
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js)](https://nodejs.org/)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Types%20included-3178C6?logo=typescript)](https://www.typescriptlang.org/)
8
+ [![GitHub Stars](https://img.shields.io/github/stars/isdk/web-fetcher.js?logo=github)](https://github.com/isdk/web-fetcher.js)
9
+ ![antibot](https://img.shields.io/badge/antibot-optional-orange)
10
+
3
11
  English | [简体中文](./README.cn.md)
4
12
 
5
- > A powerful and flexible web fetching and browser automation library.
6
- > It features a dual-engine architecture (HTTP and Browser) and a declarative action system, making it perfect for AI agents and complex data scraping tasks.
13
+ > An AI-friendly web automation library that simplifies complex web interactions into a declarative JSON action script. Write your script once and run it in either a fast **`http`** mode for static content or a full **`browser`** mode for dynamic sites. An optional **`antibot`** flag helps bypass detection mechanisms. The library is designed for targeted, task-oriented data extraction (e.g., get X from page Y), not for building whole-site crawlers.
7
14
 
8
15
  ---
9
16
 
@@ -137,6 +144,7 @@ Here are the essential built-in actions:
137
144
  * `fill`: Fills an input field with a specified value.
138
145
  * `submit`: Submits a form.
139
146
  * `waitFor`: Pauses execution to wait for a specific condition (e.g., a timeout, a selector to appear, or network to be idle).
147
+ * `pause`: Pauses execution for manual intervention (e.g., solving a CAPTCHA).
140
148
  * `getContent`: Retrieves the full content (HTML, text, etc.) of the current page state.
141
149
  * `extract`: Extracts any structured data from the page with ease using an expressive, declarative schema.
142
150
 
@@ -0,0 +1,248 @@
1
+ ### 推荐的 Reddit 频道 (Subreddits)
2
+
3
+ 根据项目特点,最适合发布此帖子的社区是:
4
+
5
+ 1. **r/webscraping**: 这是最直接相关的社区。这里的用户会非常欣赏其双引擎架构和反机器人侦测功能。
6
+ 2. **r/javascript**: 这是一个庞大的 JavaScript 开发者社区。由于这是一个 npm 库,在这里发布可以获得广泛的关注。
7
+ 3. **r/ArtificialIntelligence**: 鉴于该库明确为 AI 代理设计,这个社区的成员会对“声明式 JSON 工作流”等特性特别感兴趣,因为这使得 AI 可以轻松生成和执行自己的自动化脚本。
8
+ 4. **r/programming**: 一个更广泛的编程社区,适合分享技术含量高的新项目。
9
+
10
+ ---
11
+
12
+ ### Reddit 帖子草稿
13
+
14
+ **标题:** 我为 AI 代理构建了一个 Web 自动化库,它拥有双引擎(快速 HTTP + 完整浏览器)和声明式操作!
15
+
16
+ **正文:**
17
+
18
+ 大家好,
19
+
20
+ 我非常激动地想和大家分享我最近一直在开发的一个项目: **`@isdk/web-fetcher`**。
21
+
22
+ 在构建需要与 web 交互的 AI 代理或复杂的 web scraper 时,我们常常需要编写脆弱、命令式的代码来处理导航、点击和数据提取。这个过程不仅繁琐,而且难以维护。
23
+
24
+ `@isdk/web-fetcher` 就是为了解决这个问题而生的。它是一个功能强大且灵活的 web 抓取和浏览器自动化库,其核心是为 AI 应用和高级数据抓取任务而设计的。
25
+
26
+ ---
27
+
28
+ #### 🤔 为什么要造这个轮子?
29
+
30
+ 你可能会想:“为什么不直接用 `fetch` 或者 Playwright 呢?”
31
+
32
+ * **`fetch` 的局限**: `fetch` API 非常适合请求 API 或获取静态 HTML,但对于现代的、由 JavaScript 动态渲染内容的网站(比如单页应用 SPA)就无能为力了。你拿到手的只是一堆不含实际内容的模板代码。
33
+ * **Playwright 不够“开箱即用”**: 虽然 Playwright 很强大,但直接使用它意味着你需要自己处理很多额外的工作。比如:
34
+ * **反爬虫措施**: 很多网站有 Cloudflare 等反机器人机制,需要复杂的策略来绕过。
35
+ * **登录与会话**: 获取某些内容前必须登录,你需要手动管理登录流程和会话状态。
36
+ * **代码冗余**: 大量的配置和重复的交互逻辑代码会迅速膨胀。
37
+
38
+ `@isdk/web-fetcher` 在这些工具之上构建了一个更高层次的抽象,旨在提供一个既强大又易于使用的统一接口,让你可以专注于业务逻辑,而不是底层的实现细节。
39
+
40
+ ---
41
+
42
+ #### ✨ 核心功能:
43
+
44
+ * **⚙️ 双引擎架构**: 你可以根据任务需求选择合适的引擎。使用 **`http` 模式** (基于 Cheerio) 在静态网站上实现闪电般的速度,或者切换到 **`browser` 模式** (基于 Playwright) 来处理需要完整 JavaScript 执行的动态网站。
45
+ * **📜 声明式操作脚本**: 你可以用简单、可读的 JSON 格式定义复杂的多步骤工作流(如登录、填写表单、点击按钮)。这使得 AI 代理可以动态生成自己的自动化脚本。
46
+ * **📊 强大的数据提取**: 通过一个直观且富有表现力的声明式 Schema,可以轻松提取从简单文本到复杂嵌套对象的各种结构化数据。
47
+ * **🛡️ 反机器人侦测**: 在 `browser` 模式下,一个可选的 `antibot` 标志可以帮助你绕过像 Cloudflare 这样的常见反机器人措施。
48
+ * **🧩 可扩展**: 你可以轻松创建自定义的、高级别的“复合”操作来封装可复用的业务逻辑(例如,一个 `login` 动作)。
49
+
50
+ ---
51
+
52
+ #### 🚀 快速上手
53
+
54
+ 下面是一个简单的例子,展示了如何抓取一个网页并提取其标题:
55
+
56
+ ```typescript
57
+ import { fetchWeb } from '@isdk/web-fetcher';
58
+
59
+ async function getTitle(url: string) {
60
+ const { outputs } = await fetchWeb({
61
+ url,
62
+ actions: [
63
+ {
64
+ id: 'extract',
65
+ params: {
66
+ // 提取 <title> 标签的文本内容
67
+ selector: 'title',
68
+ },
69
+ // 将结果存储在 outputs 对象的 'pageTitle' 键下
70
+ storeAs: 'pageTitle',
71
+ },
72
+ ],
73
+ });
74
+
75
+ console.log('Page Title:', outputs.pageTitle);
76
+ }
77
+
78
+ getTitle('https://www.google.com');
79
+ ```
80
+
81
+ ---
82
+
83
+ #### 🤖 高级用法: 多步骤表单提交
84
+
85
+ 这个例子演示了如何使用 `browser` 引擎在 Google 上执行搜索。
86
+
87
+ ```typescript
88
+ import { fetchWeb } from '@isdk/web-fetcher';
89
+
90
+ async function searchGoogle(query: string) {
91
+ const { result } = await fetchWeb({
92
+ url: 'https://www.google.com',
93
+ engine: 'browser', // 使用完整的浏览器引擎进行交互
94
+ actions: [
95
+ // 初始导航由 url 选项处理
96
+ { id: 'fill', params: { selector: 'textarea[name=q]', value: query } },
97
+ { id: 'submit', params: { selector: 'form' } },
98
+ { id: 'waitFor', params: { selector: '#search' } }, // 等待搜索结果容器出现
99
+ ]
100
+ });
101
+
102
+ console.log('搜索结果 URL:', result?.finalUrl);
103
+ }
104
+
105
+ searchGoogle('gemini');
106
+ ```
107
+
108
+ ---
109
+
110
+ #### 🌱 项目状态与未来展望
111
+
112
+ 这个项目刚刚出炉,我们已经搭建好了一个坚实且高度可扩展的核心架构,并提供了上述所有基本功能。
113
+
114
+ 当然,它还有一些待完善的地方。我们接下来的一个重要目标是实现更智能的抓取策略,比如 **能够根据目标网站的特点自动选择最佳的抓取方式** (例如,自动从 `http` 模式升级到 `browser` 模式)。
115
+
116
+ ---
117
+
118
+ 这个项目是开源的,你可以在 NPM 和 GitHub 上找到它。
119
+
120
+ * **NPM:** `npm install @isdk/web-fetcher`
121
+ * **GitHub:** [链接到你的 GitHub 仓库]
122
+
123
+ 欢迎大家试用并提出宝贵的反馈!我很想听听你们的想法。
124
+
125
+
126
+ ------------------------------
127
+
128
+
129
+ 好的,这是一个非常重要的信息!透露你使用了像 `crawlee` 这样成熟的库作为底层,可以极大地增强你项目的可信度。这表明你没有“重复造轮子”,而是在一个坚实的基础上构建了一个更高层次的抽象。这在技术社区里是一个非常积极的信号。
130
+
131
+ 我会将这一点巧妙地融入帖子中,让它成为一个加分项。
132
+
133
+ ---
134
+
135
+ 这是更新后的版本,它强调了你的库是如何站在巨人肩膀上的:
136
+
137
+ ### **Title: I built a web automation library for AI agents so they can browse the web like a human, not a bot.**
138
+
139
+ Hey everyone,
140
+
141
+ Ever tried to make an AI agent *actually use* a website? You quickly run into a wall of pain.
142
+
143
+ You're not trying to crawl an entire domain like a traditional scraper. You want your agent to perform a specific task: log in, find a price, fill out a form, and get the result. But this means writing brittle, imperative code (`page.waitForSelector()`, `page.click()`, `page.evaluate()`, repeat) that breaks the moment a UI element changes.
144
+
145
+ I've been building AI agents and got deeply frustrated by this. So, I created a solution: **`@isdk/web-fetcher`**.
146
+
147
+ It’s a library designed to give agents a "browser on a leash"—a way to perform targeted, human-like actions on the web without the messy implementation details.
148
+
149
+ ---
150
+
151
+ ### 🤔 "Why not just use Playwright or Crawlee?"
152
+
153
+ Great question, and the answer gets to the heart of this project. I'm a huge fan of not reinventing the wheel, which is why **this library uses the incredible `crawlee` library under the hood.**
154
+
155
+ * **The Low-Level Tools (`fetch`, Playwright):** `fetch` is for static content, and Playwright is a fantastic browser control tool. But using it directly is like being given a box of engine parts and told to build a car.
156
+ * **The Powerful Framework (`crawlee`):** `crawlee` is a massive step up. It solves huge problems like request queuing, proxy management, and browser pooling. It's the robust engine and chassis for our car.
157
+ * **The Missing Piece (My Library):** Even with `crawlee`, you often still need to write *imperative, procedural code* to define *what* happens on the page. Your agent's logic gets mixed up with `page.click()` and `page.fill()`.
158
+
159
+ `@isdk/web-fetcher` is the final layer: **the simple, declarative dashboard for the car.** It sits on top of `crawlee`'s power and provides a JSON-based instruction set. This allows an AI to easily generate a "plan" of what to do, without worrying about the implementation.
160
+
161
+ So, it's not a replacement; it's an abstraction layer specifically for **agent-driven automation**.
162
+
163
+ ---
164
+
165
+ ### ✨ Core Features: What Makes It Different?
166
+
167
+ * **⚙️ Dual-Engine Architecture (via Crawlee):** Choose your weapon. Use the blazing-fast **`http` mode** for simple sites, or the full-featured **`browser` mode** for complex, interactive web apps.
168
+ * **📜 Declarative Action Scripts:** This is the key for AI. Instead of code, you define multi-step tasks (log in, search, extract) in simple JSON. **This means an AI agent can dynamically generate its own automation plans.**
169
+ * **📊 Clean, Declarative Data Extraction:** Define the data you want with a simple schema. No more wrestling with DOM traversal in your application code.
170
+ * **🛡️ Built-in Anti-Bot Evasion:** By leveraging `crawlee`'s capabilities, a simple `antibot: true` flag helps navigate common bot detection hurdles like Cloudflare.
171
+ * **🧩 Extensible by Design:** Bundle complex sequences into your own high-level actions. For example, create a single, reusable `loginToGitHub` action that encapsulates the entire login flow.
172
+
173
+ ---
174
+
175
+ ### 🚀 Quick Start: Grab a Page Title
176
+
177
+ Here’s how simple it is. The library handles the engine choice and execution.
178
+
179
+ ```typescript
180
+ import { fetchWeb } from '@isdk/web-fetcher';
181
+
182
+ async function getTitle(url: string) {
183
+ const { outputs } = await fetchWeb({
184
+ url,
185
+ actions: [
186
+ {
187
+ id: 'extract',
188
+ params: {
189
+ // Tell it to grab the text from the <title> tag
190
+ selector: 'title',
191
+ },
192
+ // Store the result under the 'pageTitle' key
193
+ storeAs: 'pageTitle',
194
+ },
195
+ ],
196
+ });
197
+
198
+ console.log('Page Title:', outputs.pageTitle);
199
+ }
200
+
201
+ getTitle('https://news.ycombinator.com');
202
+ ```
203
+
204
+ ---
205
+
206
+ ### 🤖 Advanced Example: A Human-like Task (Google Search)
207
+
208
+ This shows how an agent could perform a search. Notice we're just *describing* the steps.
209
+
210
+ ```typescript
211
+ import { fetchWeb } from '@isdk/web-fetcher';
212
+
213
+ async function searchGoogle(query: string) {
214
+ const { result } = await fetchWeb({
215
+ url: 'https://www.google.com',
216
+ engine: 'browser', // We need a real browser for this
217
+ actions: [
218
+ // Step 1: Fill the search bar
219
+ { id: 'fill', params: { selector: 'textarea[name=q]', value: query } },
220
+ // Step 2: Submit the form (like pressing Enter)
221
+ { id: 'submit', params: { selector: 'form' } },
222
+ // Step 3: Wait for search results to appear
223
+ { id: 'waitFor', params: { selector: '#search' } },
224
+ ]
225
+ });
226
+
227
+ console.log('Search Results URL:', result?.finalUrl);
228
+ }
229
+
230
+ searchGoogle('Gemini vs. GPT-4');
231
+ ```
232
+
233
+ ---
234
+
235
+ ### 🌱 Project Status & The Road Ahead
236
+
237
+ This project is fresh out of the oven. The core architecture is solid, and the features above are ready to use.
238
+
239
+ My next big goal is to make it even smarter. I want to implement a strategy where it can **automatically upgrade from `http` to `browser` mode** if it detects that a simple request isn't enough to get the job done.
240
+
241
+ ---
242
+
243
+ The project is open source and I'd be thrilled for you to check it out, give it a spin, and share your feedback.
244
+
245
+ * **NPM:** `npm install @isdk/web-fetcher`
246
+ * **GitHub:** [https://github.com/isdk/web-fetcher.js](https://github.com/isdk/web-fetcher.js)
247
+
248
+ I’m really excited to hear what you think and what you might build with it. Thanks for reading
package/README.v2ex.md ADDED
@@ -0,0 +1,109 @@
1
+ 非常好!这个补充说明是项目的核心亮点之一,必须在帖子里清晰地强调出来。这正是区分你的库和简单封装的关键所在:**提供一个跨引擎的、统一的抽象层**。
2
+
3
+ 我会将这一点融入到 V2EX 帖子中,让技术用户一眼就能看出这个设计的精妙之处。
4
+
5
+ ---
6
+
7
+ 这是更新后的 V2EX 帖子最终版:
8
+
9
+ #### **标题 (推荐):**
10
+
11
+ `[自荐] 做了一个为 AI 代理设计的 Web 自动化库:统一 API 驱动双引擎,让 AI 像人一样浏览网页。`
12
+
13
+ *(这个标题直接点出了“统一 API”和“双引擎”两个核心技术点)*
14
+
15
+ ---
16
+
17
+ #### **正文:**
18
+
19
+ 大家好,
20
+
21
+ 最近一直在折腾 AI Agent,发现让 Agent 可靠地与 Web 交互是个大难题,现有工具要么太底层,要么不够灵活。所以动手撸了一个轮子: **`@isdk/web-fetcher`**,想和大家分享一下,也希望能得到一些反馈。
22
+
23
+ * **GitHub:** `https://github.com/isdk/web-fetcher.js`
24
+ * **NPM:** `npm install @isdk/web-fetcher`
25
+
26
+
27
+ #### 解决了什么痛点?
28
+
29
+ 你可能会问,为啥不用 `fetch` 或 Playwright/Crawlee?
30
+
31
+ * `fetch` 拿不到 JS 动态渲染的内容,对现代网页基本没用。
32
+ * Playwright 虽然强大,但需要写大量命令式的过程代码 (`await page.click(...)` 等),不仅繁琐,而且 AI (比如 LLM) 很难直接生成这种复杂的逻辑。
33
+
34
+ 我不想重复造轮子,所以**底层用了`Crawlee` 库**来处理。
35
+
36
+ 我的目标是在 `Crawlee` 之上构建一个跨引擎一致性:抽象/模拟 HTTP 与 Browser 的共有行为,**声明式的“意图层”**,让 AI 可以通过生成简单的 JSON 来“指挥”浏览器完成任务,而不是去写具体的执行代码。
37
+
38
+ #### 核心功能
39
+
40
+ * **⚙️ 双引擎架构**: 你可以选择 `http` 模式(基于 Cheerio)来极速抓取静态内容,也可以用 `browser` 模式(基于 Playwright)来处理复杂的动态网页。
41
+ * **✨ 统一的操作模型 (核心设计)**: 这是最关键的一点。**我抽象了 `http` 和 `browser` 模式下的共性行为**。无论底层用哪个引擎,你都使用同一套 `actions` API。比如 `extract` (提取数据) 这个操作,在 `http` 模式下它会通过 Cheerio 解析静态 HTML,在 `browser` 模式下它会操作浏览器渲染后的 DOM。**你只需要学习一套 API,库在内部完成了适配和翻译**。
42
+ * **📜 声明式操作脚本**: 基于统一的模型,你可以用 JSON 定义一个多步骤任务流(登录、填表、点击),AI 生成这个 JSON 的成本远低于生成 JS 代码。
43
+ * **📊 强大的数据提取**: 同样是声明式的 Schema,轻松从页面提取结构化数据。
44
+ * **🛡️ 内置反爬**: `browser` 模式下开启 `antibot: true`,能处理一些常见的 Cloudflare 挑战。
45
+ * **🧩 易于扩展**: 可以自己封装常用的操作,比如把“登录知乎”封装成一个 `loginToZhihu` 的自定义动作。
46
+
47
+ ---
48
+
49
+ #### 快速上手:提取个标题
50
+
51
+ 注意,下面的代码不关心目标 URL 是静态还是动态的,`extract` 操作在两种模式下都有效。
52
+
53
+ ```typescript
54
+ import { fetchWeb } from '@isdk/web-fetcher';
55
+
56
+ async function getTitle(url: string) {
57
+ const { outputs } = await fetchWeb({
58
+ url,
59
+ actions: [
60
+ {
61
+ id: 'extract',
62
+ params: {
63
+ selector: 'title', // 提取 <title> 标签内容
64
+ },
65
+ storeAs: 'pageTitle', // 结果存到 outputs.pageTitle
66
+ },
67
+ ],
68
+ });
69
+
70
+ console.log('页面标题:', outputs.pageTitle);
71
+ }
72
+
73
+ getTitle('https://www.v2ex.com');
74
+ ```
75
+
76
+ #### 进阶玩法:多步表单提交 (Google 搜索)
77
+
78
+ 这个例子展示了如何用 JSON 指挥浏览器执行一系列动作。
79
+
80
+ ```typescript
81
+ import { fetchWeb } from '@isdk/web-fetcher';
82
+
83
+ async function searchGoogle(query: string) {
84
+ const { result } = await fetchWeb({
85
+ url: 'https://www.google.com',
86
+ engine: 'browser', // 显式指定需要浏览器环境
87
+ actions: [
88
+ // 步骤 1: 找到输入框并填入内容
89
+ { id: 'fill', params: { selector: 'textarea[name=q]', value: query } },
90
+ // 步骤 2: 提交表单
91
+ { id: 'submit', params: { selector: 'form' } },
92
+ // 步骤 3: 等待搜索结果容器加载出来
93
+ { id: 'waitFor', params: { selector: '#search' } },
94
+ ]
95
+ });
96
+
97
+ console.log('搜索结果页 URL:', result?.finalUrl);
98
+ }
99
+
100
+ searchGoogle('V2EX');
101
+ ```
102
+
103
+ ---
104
+
105
+ #### 项目状态
106
+
107
+ 项目刚起步,核心架构已经搭好。下一步计划是实现更智能的抓取策略(比如发现 http 模式拿不到内容时,自动升级到 browser 模式)。
108
+
109
+ 项目是开源的,欢迎大家试用、Star、提 Issue,或者狠狠地拍砖!感谢。