@zenrows/cli 1.3.1 → 1.4.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/agent-plugin/skills/extract/SKILL.md +10 -4
- package/dist/src/adapters/batch.js +21 -1
- package/dist/src/cli/commands/batch.js +24 -14
- package/dist/src/cli/commands/extract.js +3 -3
- package/dist/src/core/config.js +45 -6
- package/dist/src/core/http.js +12 -0
- package/package.json +1 -1
- package/skills/batch-jobs/SKILL.md +15 -6
- package/templates/batch-jsonl-pipeline/README.md +4 -2
|
@@ -17,12 +17,18 @@ Use **`extract`** when you need structured JSON fields rather than a full page:
|
|
|
17
17
|
extract({ url }) # mode=auto (default)
|
|
18
18
|
extract({ url, mode: "autoparse" }) # general Autoparse (any domain)
|
|
19
19
|
extract({ url, mode: "css", css_extractor: '{"title":"h1","price":".price"}' })
|
|
20
|
-
extract({ url,
|
|
20
|
+
extract({ url, proxy_country: "US" }) # geo-targeted
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
`mode=auto` (`extract=auto`) is **open beta**: richest on enabled domains,
|
|
24
|
-
currently free; billing may apply later. If the domain is not enabled (AUTH010)
|
|
25
|
-
the tool retries once with Autoparse by default
|
|
24
|
+
currently free; billing may apply later. If the domain is not enabled (AUTH010)
|
|
25
|
+
or not prepared yet (REQS007), the tool retries once with Autoparse by default
|
|
26
|
+
(`fallback_autoparse`).
|
|
27
|
+
|
|
28
|
+
Every mode uses Adaptive Stealth Mode by default: pass just the URL, even for
|
|
29
|
+
protected or dynamic sites. Set `js_render` or `premium_proxy` only to force a
|
|
30
|
+
fixed configuration; that turns Adaptive Stealth Mode off. Check `empty` in the
|
|
31
|
+
result: true means no field came back with a value.
|
|
26
32
|
|
|
27
33
|
## Fallback: `scrape` extraction params
|
|
28
34
|
|
|
@@ -40,7 +46,7 @@ scrape({ url, outputs: "emails,links" }) # or "*" for all built-ins
|
|
|
40
46
|
## When to use which
|
|
41
47
|
|
|
42
48
|
- **`mode=auto`**: site-tailored fields on beta-enabled domains.
|
|
43
|
-
- **`mode=autoparse`**: any website; also the automatic AUTH010 fallback.
|
|
49
|
+
- **`mode=autoparse`**: any website; also the automatic AUTH010 / REQS007 fallback.
|
|
44
50
|
- **`mode=css`**: known fields/selectors; deterministic.
|
|
45
51
|
- **Markdown via `scrape`**: feed clean content to an LLM yourself.
|
|
46
52
|
|
|
@@ -69,6 +69,25 @@ export function estimateCredits(jobs) {
|
|
|
69
69
|
});
|
|
70
70
|
return { credits: perJob.reduce((a, b) => a + b, 0), perJob };
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Adaptive Stealth Mode (mode=auto) by default, like `fetch` and `extract`: returns
|
|
74
|
+
* the jobs with `mode: "auto"` on every line that doesn't force `js_render` /
|
|
75
|
+
* `premium_proxy` or set `mode` itself, and none at all when the job-level params
|
|
76
|
+
* force either flag. Applied per task rather than job-level because the API rejects
|
|
77
|
+
* a task that combines a job-level mode=auto with its own js_render or
|
|
78
|
+
* premium_proxy (REQS004).
|
|
79
|
+
*/
|
|
80
|
+
export function withAdaptiveStealth(jobs, jobParams = {}) {
|
|
81
|
+
const forced = (p) => isTruthyFlag(p.js_render) || isTruthyFlag(p.premium_proxy);
|
|
82
|
+
if (forced(jobParams) || jobParams.mode != null)
|
|
83
|
+
return jobs;
|
|
84
|
+
return jobs.map((job) => {
|
|
85
|
+
const own = { ...(job.zenrows_params ?? {}), ...job };
|
|
86
|
+
if (forced(own) || own.mode != null)
|
|
87
|
+
return job;
|
|
88
|
+
return { ...job, mode: "auto" };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
72
91
|
/**
|
|
73
92
|
* Map a validated JSONL spec + job-level flags to the Batch API request body.
|
|
74
93
|
* The flat per-line options (`js_render`, `premium_proxy`, `proxy_country`,
|
|
@@ -82,7 +101,8 @@ export function estimateCredits(jobs) {
|
|
|
82
101
|
* `protected-fetch.ts::validateAutoManual`.
|
|
83
102
|
*/
|
|
84
103
|
export function toJobBody(jobs, jobParams = {}) {
|
|
85
|
-
|
|
104
|
+
// No separate job-level check: a job-level proxy_country is valid for each task
|
|
105
|
+
// in Adaptive Stealth Mode, and the per-task check below sees job ⊕ task params.
|
|
86
106
|
const tasks = jobs.map((job, idx) => {
|
|
87
107
|
const { url, external_id, metadata, zenrows_params } = job;
|
|
88
108
|
const flat = {};
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import { statSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { log, ANSI, c } from "../../core/logger.js";
|
|
12
|
-
import { estimateCredits, toJobBody, validateJsonl } from "../../adapters/batch.js";
|
|
12
|
+
import { estimateCredits, toJobBody, validateJsonl, withAdaptiveStealth } from "../../adapters/batch.js";
|
|
13
|
+
import { loadConfig } from "../../core/config.js";
|
|
13
14
|
import { requireApiKey } from "../../core/auth.js";
|
|
14
15
|
import { ensureApiKey } from "../../core/ensure-key.js";
|
|
15
16
|
import { assertUsable } from "../../core/capabilities.js";
|
|
@@ -25,12 +26,15 @@ export const batch = {
|
|
|
25
26
|
usage: "zenrows batch <estimate|create <file.jsonl>|status <id>|results <id>|cancel <id>|wait <id>|retry-failed <id>>",
|
|
26
27
|
help: [
|
|
27
28
|
"Local (no key):",
|
|
28
|
-
" estimate <file.jsonl>
|
|
29
|
+
" estimate <file.jsonl> [--manual] validate the spec + estimate credits (upper bound)",
|
|
29
30
|
"Cloud (needs a key + Batch beta access):",
|
|
30
31
|
" create <file.jsonl> [flags] validate, then submit the job",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
" --
|
|
32
|
+
" (default) Adaptive Stealth Mode (mode=auto) on every task that",
|
|
33
|
+
" doesn't force js_render / premium_proxy itself",
|
|
34
|
+
" --manual no Adaptive Stealth Mode; plain requests unless flags below",
|
|
35
|
+
" --js-render job-level: force JavaScript rendering (turns auto off)",
|
|
36
|
+
" --premium-proxy job-level: force residential IPs (turns auto off)",
|
|
37
|
+
" --proxy-country <cc> job-level: geo-target (auto mode, or with --premium-proxy)",
|
|
34
38
|
" --output <fmt> job-level response_type (markdown|plaintext|pdf|html)",
|
|
35
39
|
" --follow poll until the run finishes (alias: --wait)",
|
|
36
40
|
" --no-signup do not auto-create a Free plan account if no key exists",
|
|
@@ -76,20 +80,24 @@ export const batch = {
|
|
|
76
80
|
}
|
|
77
81
|
},
|
|
78
82
|
};
|
|
83
|
+
/** Jobs as they will be sent: Adaptive Stealth Mode applied unless manual or disabled in config. */
|
|
84
|
+
function effectiveJobs(jobs, jobParams, manual) {
|
|
85
|
+
return manual || loadConfig().defaultMode !== "auto" ? jobs : withAdaptiveStealth(jobs, jobParams);
|
|
86
|
+
}
|
|
79
87
|
function estimateCmd(rest, ctx) {
|
|
80
|
-
const { positionals } = parse(rest, {});
|
|
88
|
+
const { values, positionals } = parse(rest, { manual: { type: "boolean" } });
|
|
81
89
|
const file = positionals[0];
|
|
82
90
|
if (!file)
|
|
83
91
|
throw needFile();
|
|
84
92
|
const v = validateJsonl(file);
|
|
85
|
-
const est = estimateCredits(v.jobs);
|
|
93
|
+
const est = estimateCredits(effectiveJobs(v.jobs, {}, values.manual === true));
|
|
86
94
|
if (ctx.json) {
|
|
87
95
|
log.out(JSON.stringify({ ok: v.errors.length === 0, ...v, estimatedCredits: est.credits }, null, 2));
|
|
88
96
|
}
|
|
89
97
|
else {
|
|
90
98
|
log.info(c(ANSI.bold, `Job spec: ${file}`));
|
|
91
99
|
log.info(`valid jobs: ${v.validJobs}/${v.totalLines}`);
|
|
92
|
-
log.info(`estimated credits (1x basic / 5x js / 10x proxy / 25x both): ${est.credits}`);
|
|
100
|
+
log.info(`estimated credits, upper bound (1x basic / 5x js / 10x proxy / 25x both or auto): ${est.credits}`);
|
|
93
101
|
if (v.errors.length) {
|
|
94
102
|
log.warn(`${v.errors.length} invalid line(s):`);
|
|
95
103
|
v.errors.slice(0, 10).forEach((e) => log.dim(` line ${e.line}: ${e.reason}`));
|
|
@@ -105,6 +113,7 @@ async function createCmd(rest, ctx) {
|
|
|
105
113
|
"js-render": { type: "boolean" },
|
|
106
114
|
"premium-proxy": { type: "boolean" },
|
|
107
115
|
"proxy-country": { type: "string" },
|
|
116
|
+
manual: { type: "boolean" },
|
|
108
117
|
output: { type: "string" },
|
|
109
118
|
follow: { type: "boolean" },
|
|
110
119
|
wait: { type: "boolean" }, // back-compat alias for --follow
|
|
@@ -136,11 +145,6 @@ async function createCmd(rest, ctx) {
|
|
|
136
145
|
// 1. every task URL must pass the allow/deny domain policy,
|
|
137
146
|
// 2. the run must fit the batch-only page/credit caps (a batch is the one
|
|
138
147
|
// primitive that fans out into many requests, so the caps bind here).
|
|
139
|
-
const policy = loadPolicy();
|
|
140
|
-
for (const job of v.jobs)
|
|
141
|
-
assertDomainAllowed(job.url, policy);
|
|
142
|
-
const est = estimateCredits(v.jobs);
|
|
143
|
-
assertWithinLimits({ pages: v.validJobs, credits: est.credits }, policy, "batch");
|
|
144
148
|
const jobParams = {};
|
|
145
149
|
if (values["js-render"] === true)
|
|
146
150
|
jobParams.js_render = true;
|
|
@@ -152,8 +156,14 @@ async function createCmd(rest, ctx) {
|
|
|
152
156
|
const responseType = normalizeOutput(asString(values.output));
|
|
153
157
|
if (responseType)
|
|
154
158
|
jobParams.response_type = responseType;
|
|
159
|
+
const jobs = effectiveJobs(v.jobs, jobParams, values.manual === true);
|
|
160
|
+
const policy = loadPolicy();
|
|
161
|
+
for (const job of jobs)
|
|
162
|
+
assertDomainAllowed(job.url, policy);
|
|
163
|
+
const est = estimateCredits(jobs);
|
|
164
|
+
assertWithinLimits({ pages: v.validJobs, credits: est.credits }, policy, "batch");
|
|
155
165
|
// toJobBody validates proxy_country/premium_proxy BEFORE any HTTP call.
|
|
156
|
-
const body = toJobBody(
|
|
166
|
+
const body = toJobBody(jobs, jobParams);
|
|
157
167
|
const apiKey = await ensureApiKey(values["no-signup"] ? { ...policy, auto_signup: false } : policy, {
|
|
158
168
|
onProvision: (a) => {
|
|
159
169
|
log.info("No API key found — created a Zenrows Free plan account for you.");
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `zenrows extract <url> [flags]` → Extract (structured extraction on `/v1/`).
|
|
3
3
|
*
|
|
4
4
|
* Default: `extract=auto` (site-tailored Extract, open beta). If the domain is
|
|
5
|
-
* not enabled (AUTH010), falls back once to Autoparse. Opt into Autoparse
|
|
5
|
+
* not enabled or prepared (AUTH010 / REQS007), falls back once to Autoparse. Opt into Autoparse
|
|
6
6
|
* directly with `--autoparse`. Other methods: `--css`, `--outputs`, `--output`.
|
|
7
7
|
*/
|
|
8
8
|
import { ensureApiKey } from "../../core/ensure-key.js";
|
|
@@ -23,7 +23,7 @@ export const extract = {
|
|
|
23
23
|
usage: "zenrows extract <url> [--autoparse | --css <json> | --outputs <filters> | --output md|text] [flags]",
|
|
24
24
|
help: [
|
|
25
25
|
"Methods:",
|
|
26
|
-
" (default) extract=auto — site-tailored Extract (open beta); falls back to Autoparse if the domain is not enabled",
|
|
26
|
+
" (default) extract=auto — site-tailored Extract (open beta); falls back to Autoparse if the domain is not enabled or prepared",
|
|
27
27
|
" --autoparse general-purpose Autoparse (any domain, no Extract fallback)",
|
|
28
28
|
" --css <json> CSS selector map, e.g. '{\"title\":\"h1\",\"price\":\".price\"}'",
|
|
29
29
|
" --outputs <list> built-in output filters → JSON: emails, phone_numbers, headings,",
|
|
@@ -119,7 +119,7 @@ export const extract = {
|
|
|
119
119
|
const safeParams = { ...outcome.params };
|
|
120
120
|
delete safeParams.apikey;
|
|
121
121
|
if (outcome.fellBackToAutoparse) {
|
|
122
|
-
log.info("Extract not
|
|
122
|
+
log.info("Extract not available for this domain yet — fell back to Autoparse.");
|
|
123
123
|
}
|
|
124
124
|
const runDir = writeRun({
|
|
125
125
|
runId,
|
package/dist/src/core/config.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Toolkit config (`.zenrows/config.json`). Non-secret, safe defaults.
|
|
3
3
|
*/
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
5
8
|
import { findWorkspace, readJson, workspacePaths, writeJson } from "./workspace.js";
|
|
6
9
|
/** Confirmed Zenrows Fetch and Extract API base. */
|
|
7
10
|
export const DEFAULT_API_BASE = "https://api.zenrows.com/v1/";
|
|
@@ -11,7 +14,7 @@ export const CONFIG_VERSION = "0.1.0";
|
|
|
11
14
|
* entrypoint) so core modules — telemetry, signup provenance — can read it
|
|
12
15
|
* without an import cycle. `VERSION` in `cli/index.ts` re-exports this.
|
|
13
16
|
*/
|
|
14
|
-
export const CLI_VERSION = "1.
|
|
17
|
+
export const CLI_VERSION = "1.4.1";
|
|
15
18
|
/** The User-Agent on every request to a Zenrows API. The gateway reads the version from it. */
|
|
16
19
|
export const CLI_USER_AGENT = `zenrows-cli/${CLI_VERSION}`;
|
|
17
20
|
/** Env var to override the Fetch and Extract API base (local/staging testing). */
|
|
@@ -63,21 +66,57 @@ export function saveConfig(config, projectRoot) {
|
|
|
63
66
|
const ws = projectRoot ? workspacePaths(projectRoot) : (findWorkspace() ?? workspacePaths());
|
|
64
67
|
writeJson(ws.config, config);
|
|
65
68
|
}
|
|
69
|
+
/** Env var to relocate the per-machine config directory (tests, sandboxes). */
|
|
70
|
+
export const CONFIG_HOME_ENV = "ZENROWS_CONFIG_HOME";
|
|
71
|
+
/** Per-machine config directory, shared by every workspace on this machine. */
|
|
72
|
+
export function machineConfigDir() {
|
|
73
|
+
const override = process.env[CONFIG_HOME_ENV];
|
|
74
|
+
if (override && override.trim())
|
|
75
|
+
return override.trim();
|
|
76
|
+
if (process.platform === "win32" && process.env.APPDATA)
|
|
77
|
+
return join(process.env.APPDATA, "zenrows");
|
|
78
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
79
|
+
return join(xdg && xdg.trim() ? xdg.trim() : join(homedir(), ".config"), "zenrows");
|
|
80
|
+
}
|
|
81
|
+
function readMachineTelemetryId(file) {
|
|
82
|
+
try {
|
|
83
|
+
const id = readFileSync(file, "utf8").trim();
|
|
84
|
+
return id || undefined;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
66
90
|
/**
|
|
67
91
|
* Return the stable anonymous agent id, generating + persisting one on first
|
|
68
92
|
* use. Sent as the `X-ZR-Agent-Id` header on signup so the backend can correlate
|
|
69
93
|
* the (anonymous) device with the account it later merges on claim. Contains no
|
|
70
94
|
* PII — a random uuid only.
|
|
95
|
+
*
|
|
96
|
+
* Stored per machine, not per workspace: a per-workspace id made every new
|
|
97
|
+
* project folder look like a new device, so the backend could not tell a
|
|
98
|
+
* rate-limited retry from the machine that had just signed up. An id already in
|
|
99
|
+
* the workspace is adopted on first run so existing devices keep their identity.
|
|
100
|
+
* If the machine directory is not writable, falls back to the workspace config.
|
|
71
101
|
*/
|
|
72
102
|
export function getOrCreateTelemetryId(projectRoot) {
|
|
103
|
+
const file = join(machineConfigDir(), "telemetry-id");
|
|
104
|
+
const machineId = readMachineTelemetryId(file);
|
|
105
|
+
if (machineId)
|
|
106
|
+
return machineId;
|
|
73
107
|
const ws = projectRoot ? workspacePaths(projectRoot) : (findWorkspace() ?? workspacePaths());
|
|
74
108
|
// Merge onto the raw stored config (not loadConfig) so we never persist the
|
|
75
109
|
// ZENROWS_API_BASE env override back into config.json.
|
|
76
110
|
const stored = readJson(ws.config) ?? {};
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
111
|
+
const workspaceId = stored.telemetryId?.trim() ? stored.telemetryId.trim() : undefined;
|
|
112
|
+
const id = workspaceId ?? randomUUID();
|
|
113
|
+
try {
|
|
114
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
115
|
+
writeFileSync(file, `${id}\n`, { mode: 0o600 });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
if (!workspaceId)
|
|
119
|
+
saveConfig({ ...defaultConfig(), ...stored, telemetryId: id }, projectRoot);
|
|
120
|
+
}
|
|
82
121
|
return id;
|
|
83
122
|
}
|
package/dist/src/core/http.js
CHANGED
|
@@ -98,6 +98,18 @@ export async function scrape(apiBase, apiKey, params, opts = {}) {
|
|
|
98
98
|
concurrencyRemaining: numOrNull(res.headers.get("concurrency-remaining")),
|
|
99
99
|
redactedUrl: redacted,
|
|
100
100
|
};
|
|
101
|
+
// REQS007 on extract=auto: the domain hasn't been prepared for Extract yet.
|
|
102
|
+
// Same recovery as AUTH010 (the extract adapter falls back to autoparse), so it
|
|
103
|
+
// maps to the same code. Checked before the 403 auth branch below.
|
|
104
|
+
if (res.status === 403 && params.extract !== undefined && zrErrorCode(body) === "REQS007") {
|
|
105
|
+
throw new ToolkitError({
|
|
106
|
+
code: "EXTRACT_DOMAIN_NOT_ENABLED",
|
|
107
|
+
message: "Extract has not been prepared for this domain yet.",
|
|
108
|
+
likely_cause: zrErrorDetail(body) ?? "This domain is not prepared for Extract.",
|
|
109
|
+
next_action: "Retry with --autoparse for general-purpose extraction on any site.",
|
|
110
|
+
suggested_commands: [`zenrows extract ${params.url} --autoparse`],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
101
113
|
if (res.status === 401 || res.status === 403) {
|
|
102
114
|
// Distinguish Zenrows auth errors from target-site 403s. Zenrows auth
|
|
103
115
|
// failures surface AUTH00x codes in the body.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenrows/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Open-source CLI, MCP, skills, templates, workflows, recipes, and evals layer for giving AI agents reliable access to protected web data through Zenrows cloud infrastructure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,14 +35,15 @@ structured data instead of raw HTML.
|
|
|
35
35
|
{"url": "https://example.com/products", "extract": "auto", "external_id": "p1"}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
An Extract task costs the same as a regular one
|
|
39
|
-
|
|
38
|
+
An Extract task costs the same as a regular one, so `batch estimate` prices it
|
|
39
|
+
correctly. The result carries two keys: `html` (the
|
|
40
40
|
raw page) and `parsed` (the structured data). Validate on a single URL with
|
|
41
41
|
`zenrows extract <url>` before running the full batch.
|
|
42
42
|
|
|
43
43
|
## Cloud (needs a key + beta access)
|
|
44
44
|
```
|
|
45
|
-
zenrows batch create jobs.jsonl [--
|
|
45
|
+
zenrows batch create jobs.jsonl [--proxy-country cc] [--wait]
|
|
46
|
+
zenrows batch create jobs.jsonl --manual [--js-render] [--premium-proxy]
|
|
46
47
|
zenrows batch status <id> # run status + stats
|
|
47
48
|
zenrows batch results <id> [--status successful|failed|all] [--out results.jsonl]
|
|
48
49
|
zenrows batch wait <id> [--timeout ms]
|
|
@@ -50,9 +51,17 @@ zenrows batch cancel <id> # stop an in-flight run
|
|
|
50
51
|
zenrows batch retry-failed <id> # rerun only the failed tasks (new run)
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
Every task runs in Adaptive Stealth Mode (`mode=auto`) by default, like `fetch`
|
|
55
|
+
and `extract`: Zenrows enables JS rendering and premium proxies only on the pages
|
|
56
|
+
that need them, and bills only the configuration that succeeds. A task that sets
|
|
57
|
+
`js_render`, `premium_proxy` or `mode` itself keeps its own settings, and
|
|
58
|
+
`--js-render` / `--premium-proxy` force that configuration on every task instead.
|
|
59
|
+
`--manual` turns Adaptive Stealth Mode off. `batch estimate` counts auto tasks at
|
|
60
|
+
the 25x upper bound, so it's the most the run can cost, not the expected cost.
|
|
61
|
+
|
|
62
|
+
`create` validates the spec first, then submits. Outside Adaptive Stealth Mode,
|
|
63
|
+
`--proxy-country` needs `--premium-proxy` and is rejected before any request.
|
|
64
|
+
Job-level flags apply to every task; per-task keys in the JSONL override them.
|
|
56
65
|
|
|
57
66
|
A run ends in one of `completed`, `failed`, `stopped`, or `deleted`. `status`,
|
|
58
67
|
`wait`, and `create --wait` exit non-zero (`ok: false` under `--json`) only for
|
|
@@ -29,7 +29,9 @@ zenrows batch retry-failed <job-id> # rerun only the failed
|
|
|
29
29
|
zenrows batch cancel <job-id> # stop an in-flight run
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
`--
|
|
32
|
+
Tasks use Adaptive Stealth Mode (`mode=auto`) by default. Job-level scrape
|
|
33
|
+
options come from flags (`--js-render`, `--premium-proxy`, `--proxy-country`,
|
|
34
|
+
`--output`, or `--manual` to turn Adaptive Stealth Mode off); per-line keys
|
|
35
|
+
override them for that URL.
|
|
34
36
|
Without beta access yet, validate/estimate locally or fan out with
|
|
35
37
|
`zenrows fetch` per URL on a small sample first.
|