@opencraw/cli 0.1.2 → 0.1.3

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 CHANGED
@@ -43,6 +43,10 @@ input recipes) and reports what happened.
43
43
  | `--dry-run` | One record per input recipe, printed with the scope it was mapped from — for checking a recipe under construction without a full run. |
44
44
  | `--trace` | Print the crawl trace to stderr. |
45
45
  | `--headed` | Show the browser instead of running headless. |
46
+ | `--parallel <n>` | Run this many input recipes at once (default 1). Iterations inside a recipe follow its `limits.concurrency`. See [§3.9 of the authoring guide](../../docs/recipes/authoring.md#39-concurrency). |
47
+ | `--retries <n>` | Tries per request that fails in passing (a dropped connection, a timeout, a 429, a 5xx), for recipes whose `limits.retry` says nothing. Default 3; `1` turns retrying off. See [§7 of the authoring guide](../../docs/recipes/authoring.md#7-policies-what-happens-when-something-is-missing-or-fails). |
48
+ | `--profiles <dir>` | Where the persistent browser profiles of `session.browserProfile` live (or `OPENCRAW_PROFILES`; default `.opencraw/profiles`). See [access.md](../../docs/recipes/access.md#persistent-browser-profiles). |
49
+ | `--host-delay <ms>`, `--host-concurrency <n>` | Per-site politeness across every recipe: at least `ms` between two requests to one site, at most `n` in flight. They override the access config's `throttle` defaults. See [access.md](../../docs/recipes/access.md#throttling-per-site). |
46
50
  | `--plugins <file>` (or `--hooks`) | A plugins module: named exports `hooks` (the recipes' hook steps and transforms), `accessPlugins` (for `{ kind: "plugin" }` access profiles) and `captchaSolvers`. A module whose default export is `{ name: function }` is read as hooks alone. `OPENCRAW_PLUGINS` / `OPENCRAW_HOOKS` when not given; `probe` reads it too. It runs as your code; load only files you trust. See [§6 of the authoring guide](../../docs/recipes/authoring.md#6-hooks). |
47
51
 
48
52
  ```sh
package/dist/index.esm.js CHANGED
@@ -19,6 +19,16 @@ Options for run
19
19
  --dry-run One record per input, printed with the scope it was mapped from.
20
20
  --trace Print the crawl trace to stderr.
21
21
  --headed Show the browser.
22
+ --parallel <n> Run this many input recipes at once (default 1). Iterations inside a recipe
23
+ follow its limits.concurrency.
24
+ --retries <n> Tries per request that fails in passing (connection, timeout, 429, 5xx), for
25
+ recipes whose limits.retry says nothing. Default 3; 1 turns retrying off.
26
+ --profiles <dir> Where recipes' session.browserProfile profiles live (or OPENCRAW_PROFILES;
27
+ default .opencraw/profiles).
28
+ --host-delay <ms> At least this long between two requests to one site, across every recipe.
29
+ --host-concurrency <n>
30
+ At most this many requests to one site in flight, across every recipe.
31
+ Both override the access config's "throttle" defaults.
22
32
 
23
33
  Options for probe
24
34
  --browser Also render the page in a browser and list the JSON it fetches.
@@ -58,6 +68,21 @@ const OPTIONS = {
58
68
  'headed': {
59
69
  type: 'boolean'
60
70
  },
71
+ 'profiles': {
72
+ type: 'string'
73
+ },
74
+ 'retries': {
75
+ type: 'string'
76
+ },
77
+ 'parallel': {
78
+ type: 'string'
79
+ },
80
+ 'host-delay': {
81
+ type: 'string'
82
+ },
83
+ 'host-concurrency': {
84
+ type: 'string'
85
+ },
61
86
  'hooks': {
62
87
  type: 'string'
63
88
  },
@@ -154,6 +179,13 @@ function parseArguments(argv, env = {}) {
154
179
  dryRun: values['dry-run'] === true,
155
180
  only: values.only ?? [],
156
181
  headed: values.headed === true,
182
+ profiles: values.profiles ?? nonEmpty(env.OPENCRAW_PROFILES),
183
+ retries: integer(values.retries, '--retries', 1),
184
+ parallel: integer(values.parallel, '--parallel', 1),
185
+ throttle: {
186
+ delayMs: integer(values['host-delay'], '--host-delay', 0),
187
+ concurrency: integer(values['host-concurrency'], '--host-concurrency', 1)
188
+ },
157
189
  options
158
190
  };
159
191
  }
@@ -173,6 +205,12 @@ function parseArguments(argv, env = {}) {
173
205
  }
174
206
  }
175
207
  }
208
+ function integer(value, name, minimum) {
209
+ if (value === undefined) return undefined;
210
+ const number = Number(value);
211
+ if (!Number.isSafeInteger(number) || number < minimum) throw new Error(`${name} needs a whole number of at least ${minimum}, not "${value}"`);
212
+ return number;
213
+ }
176
214
  function nonEmpty(value) {
177
215
  return value === '' ? undefined : value;
178
216
  }
@@ -1018,6 +1056,12 @@ async function runRecipes(command, terminal) {
1018
1056
  const crawler = createCrawler({
1019
1057
  sink,
1020
1058
  access,
1059
+ throttle: throttleFor(command, access?.throttle),
1060
+ profilesDir: command.profiles,
1061
+ parallel: command.parallel,
1062
+ retry: command.retries === undefined ? undefined : {
1063
+ attempts: command.retries
1064
+ },
1021
1065
  hooks: plugins?.hooks,
1022
1066
  accessPlugins: plugins?.accessPlugins,
1023
1067
  captchaSolvers: plugins?.captchaSolvers,
@@ -1049,6 +1093,23 @@ async function runRecipes(command, terminal) {
1049
1093
  await crawler.close();
1050
1094
  }
1051
1095
  }
1096
+ /** The access config's throttle, with `--host-delay` / `--host-concurrency` over its defaults. */
1097
+ function throttleFor(command, fromConfig) {
1098
+ const {
1099
+ delayMs,
1100
+ concurrency
1101
+ } = command.throttle;
1102
+ if (delayMs === undefined && concurrency === undefined) return fromConfig;
1103
+ return {
1104
+ ...fromConfig,
1105
+ ...(delayMs !== undefined && {
1106
+ delayMs
1107
+ }),
1108
+ ...(concurrency !== undefined && {
1109
+ concurrency
1110
+ })
1111
+ };
1112
+ }
1052
1113
  function onEvent(event, command, terminal) {
1053
1114
  if (command.trace) {
1054
1115
  const line = traceLine(event);
@@ -33,6 +33,17 @@ export type Command = {
33
33
  /** Run only the input recipes with these ids. */
34
34
  only: string[];
35
35
  headed: boolean;
36
+ /** How many input recipes run at once. */
37
+ parallel?: number;
38
+ /** Tries per request for recipes whose `limits.retry` says nothing (1 turns retrying off). */
39
+ retries?: number;
40
+ /** Where `session.browserProfile` profiles live; `OPENCRAW_PROFILES` when not given. */
41
+ profiles?: string;
42
+ /** Per-site politeness across every recipe: overrides the access config's `throttle` defaults. */
43
+ throttle: {
44
+ delayMs?: number;
45
+ concurrency?: number;
46
+ };
36
47
  options: CommonOptions;
37
48
  } | {
38
49
  name: 'probe';
@@ -1,5 +1,5 @@
1
1
  import type { Command } from './command.contract.js';
2
- export declare const USAGE = "opencraw <command> [options]\n\nCommands\n validate <recipe files or directories...> Parse and bind the recipes; list every problem with its path.\n run <recipe files or directories...> Crawl. Records go to --out as JSON Lines, or to stdout.\n probe <url> Fetch a page and report where its data lives.\n\nOptions for run\n --out <file> Write records to this JSON Lines file (default: stdout).\n --append Keep what --out holds and add to it (each line carries _key).\n --resume Skip records --out already has (needs --append).\n --only <id> Run only this input recipe (repeatable).\n --dry-run One record per input, printed with the scope it was mapped from.\n --trace Print the crawl trace to stderr.\n --headed Show the browser.\n\nOptions for probe\n --browser Also render the page in a browser and list the JSON it fetches.\n\nOptions for both\n --browser-path <p> A browser binary other than the one Playwright installed (or OPENCRAW_CHROMIUM).\n --insecure-tls Accept an intercepting proxy's certificate (or OPENCRAW_INSECURE_TLS=1).\n --user-agent <ua> The user agent to send.\n --access <file> An access config: proxy profiles, credentials as {{env.NAME}} (or OPENCRAW_ACCESS).\n --access-profile <name>\n The access profile to use when a recipe names none.\n --plugins <file> A JavaScript module exporting hooks (the recipes' hook steps and transforms),\n accessPlugins (for { kind: \"plugin\" } access profiles) and captchaSolvers\n (for session.captcha and captcha steps); --hooks, or OPENCRAW_PLUGINS /\n OPENCRAW_HOOKS. It runs as your code: trust it.\n --help, --version";
2
+ export declare const USAGE = "opencraw <command> [options]\n\nCommands\n validate <recipe files or directories...> Parse and bind the recipes; list every problem with its path.\n run <recipe files or directories...> Crawl. Records go to --out as JSON Lines, or to stdout.\n probe <url> Fetch a page and report where its data lives.\n\nOptions for run\n --out <file> Write records to this JSON Lines file (default: stdout).\n --append Keep what --out holds and add to it (each line carries _key).\n --resume Skip records --out already has (needs --append).\n --only <id> Run only this input recipe (repeatable).\n --dry-run One record per input, printed with the scope it was mapped from.\n --trace Print the crawl trace to stderr.\n --headed Show the browser.\n --parallel <n> Run this many input recipes at once (default 1). Iterations inside a recipe\n follow its limits.concurrency.\n --retries <n> Tries per request that fails in passing (connection, timeout, 429, 5xx), for\n recipes whose limits.retry says nothing. Default 3; 1 turns retrying off.\n --profiles <dir> Where recipes' session.browserProfile profiles live (or OPENCRAW_PROFILES;\n default .opencraw/profiles).\n --host-delay <ms> At least this long between two requests to one site, across every recipe.\n --host-concurrency <n>\n At most this many requests to one site in flight, across every recipe.\n Both override the access config's \"throttle\" defaults.\n\nOptions for probe\n --browser Also render the page in a browser and list the JSON it fetches.\n\nOptions for both\n --browser-path <p> A browser binary other than the one Playwright installed (or OPENCRAW_CHROMIUM).\n --insecure-tls Accept an intercepting proxy's certificate (or OPENCRAW_INSECURE_TLS=1).\n --user-agent <ua> The user agent to send.\n --access <file> An access config: proxy profiles, credentials as {{env.NAME}} (or OPENCRAW_ACCESS).\n --access-profile <name>\n The access profile to use when a recipe names none.\n --plugins <file> A JavaScript module exporting hooks (the recipes' hook steps and transforms),\n accessPlugins (for { kind: \"plugin\" } access profiles) and captchaSolvers\n (for session.captcha and captcha steps); --hooks, or OPENCRAW_PLUGINS /\n OPENCRAW_HOOKS. It runs as your code: trust it.\n --help, --version";
3
3
  /**
4
4
  * Turns argv into a command.
5
5
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencraw/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.esm.js",
6
6
  "module": "./dist/index.esm.js",
@@ -24,7 +24,7 @@
24
24
  "!**/*.js.map"
25
25
  ],
26
26
  "dependencies": {
27
- "@opencraw/core": "^0.1.2",
27
+ "@opencraw/core": "^0.1.3",
28
28
  "playwright": "^1.63.0"
29
29
  },
30
30
  "publishConfig": {