@opencraw/mcp 0.1.44 → 0.1.47
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.md +27 -1
- package/dist/diff-tool/diff-tool.handler.d.ts +42 -0
- package/dist/diff-tool/index.d.ts +3 -0
- package/dist/docs-tool/docs-folder.repository.d.ts +24 -0
- package/dist/docs-tool/docs-root.client.d.ts +8 -0
- package/dist/docs-tool/docs-tool.handler.d.ts +21 -0
- package/dist/docs-tool/docs-topics.algorithm.d.ts +39 -0
- package/dist/docs-tool/index.d.ts +3 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.esm.js +449 -6
- package/dist/list-tool/index.d.ts +3 -0
- package/dist/list-tool/list-recipes-tool.handler.d.ts +32 -0
- package/dist/main.d.ts +7 -0
- package/dist/probe-tool/index.d.ts +2 -0
- package/dist/probe-tool/probe-tool.handler.d.ts +27 -0
- package/dist/recipe-source/index.d.ts +3 -0
- package/dist/recipe-source/recipe-source.mapper.d.ts +21 -0
- package/dist/run-tool/index.d.ts +3 -0
- package/dist/run-tool/run-tool.handler.d.ts +51 -0
- package/dist/server/create-server.use-case.d.ts +11 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/src/docs-tool/docs-folder.repository.d.ts +24 -0
- package/dist/src/docs-tool/docs-root.client.d.ts +8 -0
- package/dist/src/docs-tool/docs-tool.handler.d.ts +21 -0
- package/dist/src/docs-tool/docs-topics.algorithm.d.ts +39 -0
- package/dist/src/docs-tool/index.d.ts +3 -0
- package/dist/src/server/create-server.use-case.d.ts +1 -1
- package/dist/src/studio-tool/index.d.ts +2 -0
- package/dist/src/studio-tool/studio-command.client.d.ts +11 -0
- package/dist/src/studio-tool/studio-instance.repository.d.ts +22 -0
- package/dist/src/studio-tool/studio-tools.handler.d.ts +47 -0
- package/dist/studio-tool/index.d.ts +2 -0
- package/dist/studio-tool/studio-command.client.d.ts +11 -0
- package/dist/studio-tool/studio-instance.repository.d.ts +22 -0
- package/dist/studio-tool/studio-tools.handler.d.ts +47 -0
- package/dist/validate-tool/index.d.ts +3 -0
- package/dist/validate-tool/validate-tool.handler.d.ts +31 -0
- package/docs/how-it-works/01-recipes.md +698 -0
- package/docs/how-it-works/02-page-steps.md +672 -0
- package/docs/how-it-works/03-data-steps.md +813 -0
- package/docs/how-it-works/04-flow-steps.md +738 -0
- package/docs/how-it-works/05-templates-and-scope.md +428 -0
- package/docs/how-it-works/06-documents.md +1413 -0
- package/docs/how-it-works/07-mapping-and-transforms.md +699 -0
- package/docs/how-it-works/08-policies.md +405 -0
- package/docs/how-it-works/09-running.md +804 -0
- package/docs/how-it-works/README.md +225 -0
- package/docs/recipes/README.md +22 -0
- package/docs/recipes/access.md +440 -0
- package/docs/recipes/authoring.md +1503 -0
- package/docs/recipes/azure-durable.md +180 -0
- package/docs/recipes/captcha.md +204 -0
- package/docs/recipes/movies-example.md +12 -0
- package/docs/recipes/programmatic-authoring.md +101 -0
- package/docs/recipes/quick-guide.md +157 -0
- package/docs/recipes/vehicles-example.md +9 -0
- package/docs/recipes/worker-mode.md +221 -0
- package/docs/studio/manual.md +312 -0
- package/docs/studio/pdf-agreements.md +150 -0
- package/package.json +29 -4
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="../assets/opencraw-logo.svg" alt="OpenCraw" width="360">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# How OpenCraw works
|
|
6
|
+
|
|
7
|
+
This guide follows data through the engine: which page or document it sees, what each step keeps, how a
|
|
8
|
+
value changes transform by transform, and why the record ends up the way it does. Every example runs against a
|
|
9
|
+
real site, API or document, and every screenshot, scope, value table and trace here was produced by running it
|
|
10
|
+
([how](#how-this-guide-is-made)).
|
|
11
|
+
|
|
12
|
+
[authoring.md](../recipes/authoring.md) is the reference: every key, step and transform. This guide is the
|
|
13
|
+
explanation: read it to understand the engine, or when a recipe does something you didn't expect.
|
|
14
|
+
|
|
15
|
+
## The parts
|
|
16
|
+
|
|
17
|
+
| Part | What it explains |
|
|
18
|
+
|---|---|
|
|
19
|
+
| **[1. A run, end to end](#a-run-end-to-end)** (this page) | One recipe on a real site, from the page to the record. |
|
|
20
|
+
| [2. Recipes](01-recipes.md) | The output recipe (fields, types, coercion, keys) and the input recipe (web or api mode, sessions). |
|
|
21
|
+
| [3. Page steps](02-page-steps.md) | Driving a browser: `goto`, `click`, `fill`, `press`, `select`, `scroll`, `wait`, `screenshot`, `evaluate`, `captcha`. |
|
|
22
|
+
| [4. Data steps](03-data-steps.md) | Getting values: `extract` (CSS, XPath, JSONPath, regex, tables), `request`, `set`, `hook`. |
|
|
23
|
+
| [5. Flow steps](04-flow-steps.md) | Loops and decisions: `forEach`, `paginate`, `if`, `collect`, `emit`. |
|
|
24
|
+
| [6. Templates and scope](05-templates-and-scope.md) | What a step can see, and the `{{ }}` language. |
|
|
25
|
+
| [7. Documents, under the hood](06-documents.md) | How PDFs, spreadsheets, CSV, PowerPoint, Word, HTML tables, XML, YAML and Markdown are read, down to the algorithms. |
|
|
26
|
+
| [8. Mapping and transforms](07-mapping-and-transforms.md) | From scope to record: `from`, `each`, and every transform with real before and after values. |
|
|
27
|
+
| [9. Policies](08-policies.md) | Missing values, values that can't be converted, transforms that fail: what's dropped and what stops a run. |
|
|
28
|
+
| [10. Running](09-running.md) | Sessions, access and proxies, blocks, throttling, retries, captchas, concurrency, worker mode, change detection, events. |
|
|
29
|
+
|
|
30
|
+
## A run, end to end
|
|
31
|
+
|
|
32
|
+
The recipe: every book in the Mystery category of [books.toscrape.com](https://books.toscrape.com), a site
|
|
33
|
+
built for scraping practice, read in a browser. Two files:
|
|
34
|
+
|
|
35
|
+
- [`book.output.json`](recipes/books-list/book.output.json), the **output recipe**, says what a record is:
|
|
36
|
+
`url` (the key), `title`, `price` (in GBP), `stars`, `inStock`, `category`, `page`.
|
|
37
|
+
- [`mystery-web.input.json`](recipes/books-list/mystery-web.input.json), the **input recipe**, says where the
|
|
38
|
+
data is and how to turn it into records.
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
"steps": [
|
|
42
|
+
{ "type": "goto", "url": "{{start.url}}" },
|
|
43
|
+
{ "type": "set", "id": "starWords", "value": [{ "word": "One", "n": 1 }, "…", { "word": "Five", "n": 5 }] },
|
|
44
|
+
{ "type": "paginate", "next": { "selector": "li.next a" }, "maxPages": 2, "steps": [
|
|
45
|
+
{ "type": "extract", "id": "books", "selector": "article.product_pod", "kind": "css", "take": "html", "many": true },
|
|
46
|
+
{ "type": "forEach", "over": "books", "as": "book", "emit": true, "steps": [
|
|
47
|
+
{ "type": "extract", "id": "title", "from": "book", "selector": "h3 a", "kind": "css", "take": "attr:title" },
|
|
48
|
+
{ "type": "extract", "id": "link", "from": "book", "selector": "h3 a", "kind": "css", "take": "attr:href" },
|
|
49
|
+
{ "type": "extract", "id": "price", "from": "book", "selector": ".price_color", "kind": "css", "take": "text" },
|
|
50
|
+
{ "type": "extract", "id": "rating", "from": "book", "selector": "p.star-rating", "kind": "css", "take": "attr:class" },
|
|
51
|
+
{ "type": "extract", "id": "stock", "from": "book", "selector": ".availability", "kind": "css", "take": "text" }
|
|
52
|
+
]}
|
|
53
|
+
]}
|
|
54
|
+
]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 1. What the engine sees
|
|
58
|
+
|
|
59
|
+
`goto` opens the category page in Chromium. `extract` with `many: true` takes every element matching
|
|
60
|
+
`article.product_pod`, as HTML (`take: "html"`): one fragment per book. Inside `forEach`, each fragment is the
|
|
61
|
+
`book`, and the five `extract`s read from it (`from: "book"`), not from the page.
|
|
62
|
+
|
|
63
|
+
<!-- capture:books-list screenshot alt=The_Mystery_category,_with_what_each_extract_selects -->
|
|
64
|
+

|
|
65
|
+
<!-- /capture -->
|
|
66
|
+
|
|
67
|
+
### 2. What the steps keep: the scope
|
|
68
|
+
|
|
69
|
+
Every step with an `id` binds its value under that name. When `forEach` emits, the engine takes a snapshot of
|
|
70
|
+
everything in scope: the loop's own values, then its parents' (`page`, `starWords`, `vars`, `start`). That
|
|
71
|
+
snapshot is what the mapping reads. For the first book:
|
|
72
|
+
|
|
73
|
+
<!-- capture:books-list scope ids=title,link,price,rating,stock,page,vars -->
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"title": "Sharp Objects",
|
|
77
|
+
"link": "../../../sharp-objects_997/index.html",
|
|
78
|
+
"price": "£47.82",
|
|
79
|
+
"rating": "star-rating Four",
|
|
80
|
+
"stock": "In stock",
|
|
81
|
+
"page": {
|
|
82
|
+
"url": "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
|
|
83
|
+
"number": 1
|
|
84
|
+
},
|
|
85
|
+
"vars": {
|
|
86
|
+
"category": "Mystery"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
<!-- /capture -->
|
|
91
|
+
|
|
92
|
+
`book` (the fragment) and `books` (all of them) are in the snapshot too, left out here for length.
|
|
93
|
+
|
|
94
|
+
### 3. From scope to record: the mapping
|
|
95
|
+
|
|
96
|
+
Each output field has a rule: where to read (`from`) and what to do with it (`transform`). The table shows each
|
|
97
|
+
field's value as read, after each transform, and as it lands in the record, after coercion to the field's type:
|
|
98
|
+
|
|
99
|
+
<!-- capture:books-list mapping fields=url,price,stars,inStock,page -->
|
|
100
|
+
| Field | Step | Value |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `url` | read | `"../../../sharp-objects_997/index.html"` |
|
|
103
|
+
| | `absoluteUrl` | `"https://books.toscrape.com/catalogue/sharp-objects_997/index.html"` |
|
|
104
|
+
| | **field** | `"https://books.toscrape.com/catalogue/sharp-objects_997/index.html"` |
|
|
105
|
+
| `price` | read | `"£47.82"` |
|
|
106
|
+
| | `currency` | `{"amount":47.82,"currency":"GBP"}` |
|
|
107
|
+
| | **field** | `{"amount":47.82,"currency":"GBP"}` |
|
|
108
|
+
| `stars` | read | `"star-rating Four"` |
|
|
109
|
+
| | `regex` | `"Four"` |
|
|
110
|
+
| | `lookup` | `4` |
|
|
111
|
+
| | **field** | `4` |
|
|
112
|
+
| `inStock` | read | `"In stock"` |
|
|
113
|
+
| | `trim` | `"In stock"` |
|
|
114
|
+
| | `boolean` | `true` |
|
|
115
|
+
| | **field** | `true` |
|
|
116
|
+
| `page` | read | `1` |
|
|
117
|
+
| | **field** | `1` |
|
|
118
|
+
<!-- /capture -->
|
|
119
|
+
|
|
120
|
+
Three things happen here that aren't obvious from the recipe:
|
|
121
|
+
|
|
122
|
+
- `absoluteUrl` resolves the link against the page it came from, so `../../../sharp-objects_997/index.html`
|
|
123
|
+
becomes a full URL.
|
|
124
|
+
- `currency` reads the `£` sign and returns `GBP`. When a text has no sign, the field's `currency` fills it in
|
|
125
|
+
at coercion.
|
|
126
|
+
- `stars` takes two steps: `regex` pulls the word out of the class (`Four`), then `lookup` finds it in the
|
|
127
|
+
`starWords` table the recipe `set` at the start, and returns its `n`.
|
|
128
|
+
|
|
129
|
+
### 4. The record
|
|
130
|
+
|
|
131
|
+
<!-- capture:books-list record -->
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"url": "https://books.toscrape.com/catalogue/sharp-objects_997/index.html",
|
|
135
|
+
"title": "Sharp Objects",
|
|
136
|
+
"price": {
|
|
137
|
+
"amount": 47.82,
|
|
138
|
+
"currency": "GBP"
|
|
139
|
+
},
|
|
140
|
+
"stars": 4,
|
|
141
|
+
"inStock": true,
|
|
142
|
+
"category": "Mystery",
|
|
143
|
+
"page": 1
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
<!-- /capture -->
|
|
147
|
+
|
|
148
|
+
The output declares `url` as the key: a second record with the same URL would be dropped as a duplicate.
|
|
149
|
+
|
|
150
|
+
### 5. The route: the trace
|
|
151
|
+
|
|
152
|
+
`traceLine` turns the engine's events into this, one line per event, indented by step depth. `⇢` is a page,
|
|
153
|
+
`·` a finished step with its path in the recipe (`steps.2.steps.1.steps.0` is the first step inside the loop
|
|
154
|
+
inside `paginate`), `✚` a record with its key:
|
|
155
|
+
|
|
156
|
+
<!-- capture:books-list trace lines=16 -->
|
|
157
|
+
```text
|
|
158
|
+
▶ mystery-web (web)
|
|
159
|
+
⇄ access direct (direct)
|
|
160
|
+
⇢ page 1 https://books.toscrape.com/catalogue/category/books/mystery_3/index.html
|
|
161
|
+
· steps.0 goto … ms
|
|
162
|
+
· steps.1 set starWords … ms
|
|
163
|
+
· steps.2.steps.0 extract books … ms
|
|
164
|
+
· steps.2.steps.1.steps.0 extract title … ms
|
|
165
|
+
· steps.2.steps.1.steps.1 extract link … ms
|
|
166
|
+
· steps.2.steps.1.steps.2 extract price … ms
|
|
167
|
+
· steps.2.steps.1.steps.3 extract rating … ms
|
|
168
|
+
· steps.2.steps.1.steps.4 extract stock … ms
|
|
169
|
+
✚ record ["https://books.toscrape.com/catalogue/sharp-objects_997/index.html"]
|
|
170
|
+
· steps.2.steps.1.steps.0 extract title … ms
|
|
171
|
+
· steps.2.steps.1.steps.1 extract link … ms
|
|
172
|
+
· steps.2.steps.1.steps.2 extract price … ms
|
|
173
|
+
· steps.2.steps.1.steps.3 extract rating … ms
|
|
174
|
+
…
|
|
175
|
+
```
|
|
176
|
+
<!-- /capture -->
|
|
177
|
+
|
|
178
|
+
<!-- capture:books-list summary -->
|
|
179
|
+
```text
|
|
180
|
+
mystery-web: 32 emitted, 0 rejected, 0 duplicates, 2 pages
|
|
181
|
+
```
|
|
182
|
+
<!-- /capture -->
|
|
183
|
+
|
|
184
|
+
32 books over two pages: `paginate` clicked "next" once, then found no "next" on page 2 and stopped.
|
|
185
|
+
|
|
186
|
+
## How the pipeline fits together
|
|
187
|
+
|
|
188
|
+
```mermaid
|
|
189
|
+
flowchart LR
|
|
190
|
+
A[recipe files] -->|load + validate| B[recipe set]
|
|
191
|
+
B -->|bind: every output field fed| C[runs: one per input recipe and start]
|
|
192
|
+
C --> D{mode}
|
|
193
|
+
D -->|web| E[browser page]
|
|
194
|
+
D -->|api| F[HTTP session]
|
|
195
|
+
E & F --> G[steps: goto, extract, forEach, paginate…]
|
|
196
|
+
G -->|emit| H[scope snapshot]
|
|
197
|
+
H -->|mapping rules + transforms| I[raw values]
|
|
198
|
+
I -->|coerce + validate| J[record]
|
|
199
|
+
J -->|missing / failure policy| K{kept?}
|
|
200
|
+
K -->|key seen| L[duplicate]
|
|
201
|
+
K -->|new| M[sink]
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Each box is a part of this guide. The same pipeline runs whether the source is a web page, an API, or a PDF
|
|
205
|
+
fetched by URL: the document readers (part 7) turn every format into something `extract` can select from.
|
|
206
|
+
|
|
207
|
+
## How this guide is made
|
|
208
|
+
|
|
209
|
+
Each example's recipes live in [`recipes/`](recipes), one folder per scene. The capture script,
|
|
210
|
+
[`capture/capture.mjs`](capture/capture.mjs), does the following for each scene:
|
|
211
|
+
|
|
212
|
+
- runs its recipes with `CrawlOptions.debug`, so every `record:emit` event carries the scope snapshot and each
|
|
213
|
+
field's mapping trace;
|
|
214
|
+
- saves the records, the trace and the summary to [`captures/`](captures);
|
|
215
|
+
- takes the screenshots, outlining what each selector matches;
|
|
216
|
+
- rewrites the regions of these pages marked `<!-- capture:… -->`.
|
|
217
|
+
|
|
218
|
+
```sh
|
|
219
|
+
npm run docs:capture # every scene, against the live sites (a few minutes)
|
|
220
|
+
npm run docs:capture -- --only books-list
|
|
221
|
+
npm run docs:capture -- --render # rewrite the pages from the saved captures, offline
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
CI loads and binds every scene's recipes on each pull request, so a change to the engine that breaks one fails
|
|
225
|
+
there. The capture itself runs on demand, since the sites are live.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="../assets/opencraw-logo.svg" alt="OpenCraw" width="360">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Recipe documentation
|
|
6
|
+
|
|
7
|
+
| Document | Read it when |
|
|
8
|
+
|---|---|
|
|
9
|
+
| [authoring.md](./authoring.md) | You are writing a recipe and need every field, step, transform and rule, with the reasoning behind each. The reference. |
|
|
10
|
+
| [quick-guide.md](./quick-guide.md) | You want the short version: one page, the common shapes. |
|
|
11
|
+
| [programmatic-authoring.md](./programmatic-authoring.md) | You generate many similar recipes (per shop, region or login) and want to build them as objects in code instead of typing JSON. |
|
|
12
|
+
| [How OpenCraw works](../how-it-works/README.md) | You want to see what the engine does with each part of a recipe: every step, template, transform and policy run on real sites, APIs and documents, with screenshots, traces and the records they gave, and the engineering of the PDF, spreadsheet, PowerPoint and Word readers. |
|
|
13
|
+
| [examples/](../../examples/README.md) | You want recipes that run: every example, what it shows and how to start it. |
|
|
14
|
+
| [examples/movies](../../examples/movies/README.md), [examples/filmography](../../examples/filmography/README.md) | You want to see real recipes end to end: five movies from Netflix and IMDb, then movie → lead actor → filmography on TMDB and Letterboxd, with the decisions, the traces and the bugs met on the way. |
|
|
15
|
+
| [examples/vehicles](../../examples/vehicles/README.md) | You want the harder case: configurator data behind an inline `carPath`, three pages per model, nested loops emitting one record per priced combination, and the two engine features it forced (`regex` extracts, templated selectors). |
|
|
16
|
+
| [access.md](./access.md) | A site blocks the network you crawl from: proxies, provider presets, where credentials go, and the plugin seam. |
|
|
17
|
+
| [worker-mode.md](./worker-mode.md) | You crawl thousands of items (one report per date, region and filter set) from a queue: windows that stay busy, keep their page between items, and grow or shrink with the site's health. |
|
|
18
|
+
| [azure-durable.md](./azure-durable.md) | You want one OpenCraw service every crawler calls over HTTP: the Azure Durable Functions host, its container, `host.json`, scaling, security, and a caller that keeps a warm pool busy. |
|
|
19
|
+
| [captcha.md](./captcha.md) | A site shows a captcha: whether to solve it, the four places a challenge appears, the solve loop, the budget, and writing a solver. |
|
|
20
|
+
| [../requirements.md](../requirements.md) | You need the specification the engine is checked against. |
|
|
21
|
+
|
|
22
|
+
The JSON Schemas for editor validation are in `packages/core/schemas/`; point a recipe's (or an access config's) `$schema` at them.
|