@nitpicker/crawler 0.11.0 → 0.12.0
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/lib/archive/archive.d.ts +117 -2
- package/lib/archive/archive.js +147 -2
- package/lib/archive/cache/compute-archive-cache-key.d.ts +39 -0
- package/lib/archive/cache/compute-archive-cache-key.js +95 -0
- package/lib/archive/cache/extract-archive-to-cache.d.ts +43 -0
- package/lib/archive/cache/extract-archive-to-cache.js +309 -0
- package/lib/archive/cache/get-archive-cache-root.d.ts +20 -0
- package/lib/archive/cache/get-archive-cache-root.js +53 -0
- package/lib/archive/cache/is-archive-cache-disabled.d.ts +24 -0
- package/lib/archive/cache/is-archive-cache-disabled.js +34 -0
- package/lib/archive/cache/resolve-archive-cache-dir.d.ts +26 -0
- package/lib/archive/cache/resolve-archive-cache-dir.js +32 -0
- package/lib/archive/database.d.ts +216 -15
- package/lib/archive/database.js +1459 -938
- package/lib/archive/derive-lineage-from-parent.d.ts +37 -0
- package/lib/archive/derive-lineage-from-parent.js +42 -0
- package/lib/archive/get-failed-page-messages.d.ts +43 -0
- package/lib/archive/get-failed-page-messages.js +131 -0
- package/lib/archive/init-schema.js +153 -1
- package/lib/archive/is-inventory-source.d.ts +21 -0
- package/lib/archive/is-inventory-source.js +22 -0
- package/lib/archive/migrate-inventory-runs.d.ts +29 -0
- package/lib/archive/migrate-inventory-runs.js +52 -0
- package/lib/archive/types.d.ts +33 -0
- package/lib/classify-error-kind.d.ts +19 -0
- package/lib/classify-error-kind.js +122 -0
- package/lib/crawler/build-js-redirect-edge.d.ts +68 -0
- package/lib/crawler/build-js-redirect-edge.js +57 -0
- package/lib/crawler/build-redirect-event.d.ts +24 -0
- package/lib/crawler/build-redirect-event.js +28 -0
- package/lib/crawler/clear-dns-burned-host-cache.d.ts +6 -0
- package/lib/crawler/clear-dns-burned-host-cache.js +11 -0
- package/lib/crawler/crawler.d.ts +3 -1
- package/lib/crawler/crawler.js +655 -107
- package/lib/crawler/derive-js-redirect-target.d.ts +68 -0
- package/lib/crawler/derive-js-redirect-target.js +129 -0
- package/lib/crawler/derive-resource-source.d.ts +25 -15
- package/lib/crawler/derive-resource-source.js +28 -17
- package/lib/crawler/dns-burned-host-cache.d.ts +26 -0
- package/lib/crawler/dns-burned-host-cache.js +25 -0
- package/lib/crawler/dns-burned-host-short-circuit-counter.d.ts +13 -0
- package/lib/crawler/dns-burned-host-short-circuit-counter.js +11 -0
- package/lib/crawler/fetch-destination.d.ts +12 -4
- package/lib/crawler/fetch-destination.js +94 -16
- package/lib/crawler/is-js-redirect-error-shape.d.ts +40 -0
- package/lib/crawler/is-js-redirect-error-shape.js +53 -0
- package/lib/crawler/is-puppeteer-fallback-candidate.d.ts +16 -0
- package/lib/crawler/is-puppeteer-fallback-candidate.js +63 -0
- package/lib/crawler/link-list.d.ts +21 -1
- package/lib/crawler/link-list.js +23 -3
- package/lib/crawler/plan-sub-resource-emits.d.ts +63 -0
- package/lib/crawler/plan-sub-resource-emits.js +44 -0
- package/lib/crawler/preload-short-circuit-error.d.ts +22 -0
- package/lib/crawler/preload-short-circuit-error.js +25 -0
- package/lib/crawler/should-burn-host.d.ts +78 -0
- package/lib/crawler/should-burn-host.js +61 -0
- package/lib/crawler/should-get-fallback-on-head-failure.d.ts +38 -0
- package/lib/crawler/should-get-fallback-on-head-failure.js +46 -0
- package/lib/crawler/types.d.ts +107 -0
- package/lib/crawler-orchestrator.d.ts +13 -3
- package/lib/crawler-orchestrator.js +292 -69
- package/lib/crawler.d.ts +3 -2
- package/lib/crawler.js +3 -1
- package/lib/permanent-error-kinds.d.ts +43 -0
- package/lib/permanent-error-kinds.js +48 -0
- package/lib/types.d.ts +84 -0
- package/lib/utils/compute-file-sha256.d.ts +23 -0
- package/lib/utils/compute-file-sha256.js +55 -0
- package/lib/utils/error/emit-error-with-retry.d.ts +40 -0
- package/lib/utils/error/emit-error-with-retry.js +44 -0
- package/lib/utils/error/emit-error.d.ts +39 -0
- package/lib/utils/error/emit-error.js +41 -0
- package/package.json +11 -11
- package/lib/utils/error/error-emitter.d.ts +0 -18
- package/lib/utils/error/error-emitter.js +0 -29
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsonLdRow, TagRow } from './meta/types.js';
|
|
2
|
-
import type { Config, DatabaseOption, DB_Anchor, DB_Page, DB_Redirect, DB_Referrer, DB_Resource, DatabaseEvent, PageFilter, PageSource } from './types.js';
|
|
2
|
+
import type { Config, DatabaseOption, DB_Anchor, DB_Page, DB_Redirect, DB_Referrer, DB_Resource, DatabaseEvent, InventoryRunMeta, PageFilter, PageSource } from './types.js';
|
|
3
3
|
import type { PageData, Resource } from '../utils/types/types.js';
|
|
4
4
|
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
5
5
|
import type { Knex } from 'knex';
|
|
@@ -7,12 +7,20 @@ import { TypedAwaitEventEmitter as EventEmitter } from '@d-zero/shared/typed-awa
|
|
|
7
7
|
/**
|
|
8
8
|
* Low-level database abstraction layer for the archive's SQLite database.
|
|
9
9
|
*
|
|
10
|
-
* Public methods that perform database queries use the
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Public methods that perform database queries use the `emitErrorAndRetry`
|
|
11
|
+
* HOF for automatic retry on transient failures combined with error-event
|
|
12
|
+
* propagation, or `emitError` when retry is not appropriate. The set of
|
|
13
|
+
* tables this layer manages is
|
|
13
14
|
* defined by `init-schema.ts` (the source of truth — query that file for
|
|
14
15
|
* the canonical list).
|
|
15
16
|
*
|
|
17
|
+
* **Label sync caveat**: each `emitError` / `emitErrorAndRetry` call passes
|
|
18
|
+
* the method name as a string literal (e.g. `'Database.getAnchorsOnPage'`).
|
|
19
|
+
* TypeScript cannot check that the string matches the enclosing method's
|
|
20
|
+
* real name — the two-way sync is manual. Renaming a method here **must**
|
|
21
|
+
* update the literal string too, otherwise debug logs and `RetryTimeoutError`
|
|
22
|
+
* messages will silently report the old name.
|
|
23
|
+
*
|
|
16
24
|
* Use the static {@link Database.connect} factory method to create instances.
|
|
17
25
|
* The constructor is private.
|
|
18
26
|
*/
|
|
@@ -58,7 +66,67 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
58
66
|
getConfig(): Promise<Config>;
|
|
59
67
|
/**
|
|
60
68
|
* Retrieves the current crawling state by listing scraped and pending URLs.
|
|
61
|
-
*
|
|
69
|
+
*
|
|
70
|
+
* `scraped` is straightforward: every page row whose `scraped` flag is `1`
|
|
71
|
+
* — that is, every URL the crawl reached a terminal state on, including
|
|
72
|
+
* setSkippedPage / setExternalPage / outright setPage success or failure.
|
|
73
|
+
*
|
|
74
|
+
* `pending` is intentionally STRICT — not "every `scraped = 0` row".
|
|
75
|
+
* Three filters apply:
|
|
76
|
+
*
|
|
77
|
+
* 1. `scraped = 0` — work still incomplete.
|
|
78
|
+
* 2. `isExternal = 0` — only in-scope work. External URLs go through a
|
|
79
|
+
* HEAD-only path that always lands on `scraped = 1` (either setPage or
|
|
80
|
+
* setExternalPage). A row with `isExternal = 1 AND scraped = 0` is
|
|
81
|
+
* therefore a data anomaly, and resume / inventory / append have no
|
|
82
|
+
* business retrying it on the next session.
|
|
83
|
+
* 3. `EXISTS (anchor with hrefId = pages.id) OR source != 'crawled'` —
|
|
84
|
+
* the row was either discovered as an anchor destination during a
|
|
85
|
+
* previous scrape OR was explicitly tagged with a non-default
|
|
86
|
+
* source label (`'inventory-seed'`, `'inventory-discovered'`, …).
|
|
87
|
+
* Both halves of the OR represent "deliberately enqueued, expected
|
|
88
|
+
* to be processed", which is exactly what `resume` should pick up.
|
|
89
|
+
*
|
|
90
|
+
* The orphan filter targets the **predicted-discard leak** in
|
|
91
|
+
* `crawler.ts` where `shouldDiscardPredicted` returns true but no
|
|
92
|
+
* `emit('skip')` follows. Such placeholders are inserted with the
|
|
93
|
+
* DB DEFAULT `source = 'crawled'` (no caller explicitly labels
|
|
94
|
+
* them) AND have no anchor referrer (predicted URLs are
|
|
95
|
+
* synthesised from pagination patterns, never anchored from a
|
|
96
|
+
* rendered page) — both halves of the OR are therefore false and
|
|
97
|
+
* the leak is excluded.
|
|
98
|
+
*
|
|
99
|
+
* The `source != 'crawled'` clause specifically saves the
|
|
100
|
+
* `--inventory` × `--retry-failed` interaction: an inventory-seed
|
|
101
|
+
* URL came from the operator's URL list (no anchor referrer) and
|
|
102
|
+
* `resetFailedPages` puts it back at `scraped = 0`. Without this
|
|
103
|
+
* clause those legitimate retries would be dropped on resume.
|
|
104
|
+
*
|
|
105
|
+
* The defensive shape is on purpose: the data source can drift into
|
|
106
|
+
* anomalous states under interruption, but the reader must never throw
|
|
107
|
+
* or feed garbage back into the dealer. A real in-scope URL that was
|
|
108
|
+
* truly interrupted mid-crawl will always have at least one anchor
|
|
109
|
+
* referrer (otherwise the dealer would not have queued it), so the
|
|
110
|
+
* strict filter loses no legitimate pending work.
|
|
111
|
+
*
|
|
112
|
+
* Seeds passed directly to `Crawler.start()` are NOT in the strict
|
|
113
|
+
* pending set when they were never picked by the dealer — they have no
|
|
114
|
+
* DB row at all in that case (`linkList.add` is purely in-memory until
|
|
115
|
+
* `setPage` runs). A Ctrl-C between dealer pick and `setPage` likewise
|
|
116
|
+
* leaves no row to recover. Recovery of un-picked seeds is the
|
|
117
|
+
* responsibility of the caller (e.g. re-running `--inventory ./list.txt`
|
|
118
|
+
* with the same URL list).
|
|
119
|
+
*
|
|
120
|
+
* The query uses an explicit `p` alias on the `pages` table so the
|
|
121
|
+
* correlated `EXISTS` subquery can join via `whereRaw('anchors.hrefId =
|
|
122
|
+
* p.id')`. A future refactor that renames the alias must update both
|
|
123
|
+
* sites — the raw string in the subquery cannot be grep-resolved
|
|
124
|
+
* automatically. Read-only / stub viewer connections never call this
|
|
125
|
+
* method (they do not need to know about pending state), so the EXISTS
|
|
126
|
+
* shape is safe to use without the `migrate*` guards that other writer
|
|
127
|
+
* methods carry.
|
|
128
|
+
* @returns An object with `scraped` (completed URLs) and `pending` (the
|
|
129
|
+
* strict set of in-scope, anchor-referenced, unfinished URLs).
|
|
62
130
|
*/
|
|
63
131
|
getCrawlingState(): Promise<{
|
|
64
132
|
scraped: string[];
|
|
@@ -137,6 +205,27 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
137
205
|
* @returns An array of raw {@link DB_Page} rows.
|
|
138
206
|
*/
|
|
139
207
|
getPages(filter?: PageFilter, offset?: number, limit?: number): Promise<DB_Page[]>;
|
|
208
|
+
/**
|
|
209
|
+
* Look up the `source` column of a single page by its URL key. Used by
|
|
210
|
+
* the orchestrator's `PageSourceLookup` injection so the Crawler can
|
|
211
|
+
* resolve a parent page's lineage on `--resume` / `--retry-failed`
|
|
212
|
+
* sessions, where the in-memory `inventoryMode` is no longer
|
|
213
|
+
* available but the DB still remembers what label was last persisted.
|
|
214
|
+
*
|
|
215
|
+
* Returns `undefined` when the URL has no `pages` row (e.g. a brand-new
|
|
216
|
+
* URL that has not been seen yet) so the caller can fall through to
|
|
217
|
+
* its default behaviour without distinguishing "row absent" from "row
|
|
218
|
+
* present with NULL source" — the schema's `NOT NULL DEFAULT 'crawled'`
|
|
219
|
+
* makes a NULL value impossible in practice.
|
|
220
|
+
*
|
|
221
|
+
* Read-only — no transaction, single PK-equivalent lookup on
|
|
222
|
+
* `pages.url` (a UNIQUE column), so the cost is constant per call. The
|
|
223
|
+
* Crawler calls this at most once per page render, NOT per
|
|
224
|
+
* sub-resource, so the N+1 risk does not apply.
|
|
225
|
+
* @param url - URL key in `url.withoutHashAndAuth` form.
|
|
226
|
+
* @returns The recorded `source`, or `undefined` when no row exists.
|
|
227
|
+
*/
|
|
228
|
+
getPageSourceByUrl(url: string): Promise<PageSource | undefined>;
|
|
140
229
|
/**
|
|
141
230
|
* Retrieves pages along with their related redirect, anchor, and referrer data.
|
|
142
231
|
* Results are ordered by the natural URL sort order. Only non-redirected pages are returned.
|
|
@@ -183,7 +272,7 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
183
272
|
* `href` while callers may only know the hash-stripped form; the first match
|
|
184
273
|
* wins.
|
|
185
274
|
*
|
|
186
|
-
* Deliberately NOT
|
|
275
|
+
* Deliberately NOT wrapped with `emitError`/`emitErrorAndRetry`: the only caller (the
|
|
187
276
|
* crawler's resource-reuse hook) has a full fallback (the HEAD pre-flight),
|
|
188
277
|
* so a read failure here must not surface as a database `error` event —
|
|
189
278
|
* the orchestrator aborts the whole crawl on that event, which is the
|
|
@@ -240,6 +329,58 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
240
329
|
* @param isExternal - Whether the URL is external to the crawl scope.
|
|
241
330
|
*/
|
|
242
331
|
insertCrawlError(url: string | null, message: string, isExternal?: boolean): Promise<void>;
|
|
332
|
+
/**
|
|
333
|
+
* Pre-insert inventory non-HTML URLs into `resources` as placeholder rows
|
|
334
|
+
* with `source = 'inventory-seed'` and all metadata columns NULL — the
|
|
335
|
+
* non-HTML counterpart of {@link Database.insertInventorySeeds}. Used by
|
|
336
|
+
* `CrawlerOrchestrator.inventory` so the ingestion phase commits all of
|
|
337
|
+
* its non-HTML URLs in one chunked round-trip per 500 instead of N
|
|
338
|
+
* sequential `insertResource` awaits. On a 50k-URL inventory list the
|
|
339
|
+
* old per-URL loop spent minutes inside the `.bak`-protected window;
|
|
340
|
+
* the bulk path finishes in seconds.
|
|
341
|
+
*
|
|
342
|
+
* Idempotent: `onConflict('url').ignore()` leaves existing rows untouched
|
|
343
|
+
* (the orchestrator's `getExistingResourceUrls` filter is what keeps a
|
|
344
|
+
* crawled-lineage `resources` row from being downgraded to the
|
|
345
|
+
* inventory label here).
|
|
346
|
+
*
|
|
347
|
+
* Chunked at 500 to stay well under SQLite's `SQLITE_MAX_VARIABLE_NUMBER`
|
|
348
|
+
* (default 999) — every row binds the URL plus the `responseHeaders`
|
|
349
|
+
* JSON null, so the per-chunk bound budget is well within limits.
|
|
350
|
+
* @param urls - URL strings (already in `withoutHashAndAuth` form).
|
|
351
|
+
*/
|
|
352
|
+
insertInventoryResources(urls: readonly string[]): Promise<void>;
|
|
353
|
+
/**
|
|
354
|
+
* Pre-insert inventory HTML seeds into `pages` as `scraped = 0`,
|
|
355
|
+
* `source = 'inventory-seed'` placeholders so the URL's existence in the
|
|
356
|
+
* archive is **durable before the scrape phase starts**.
|
|
357
|
+
*
|
|
358
|
+
* Why this is the linchpin of `--inventory` Ctrl+C tolerance: HTML seeds
|
|
359
|
+
* used to live only in the Crawler's in-memory `LinkList` until the
|
|
360
|
+
* dealer eventually called `setPage`. A Ctrl+C / crash before that point
|
|
361
|
+
* lost the seed without trace, and `--resume` could not recover it
|
|
362
|
+
* because `getCrawlingState`'s strict pending set requires a `pages` row.
|
|
363
|
+
* Pre-inserting fills exactly that gap: the strict pending set picks
|
|
364
|
+
* these rows up via its `OR p.source != 'crawled'` clause, so
|
|
365
|
+
* `--resume` after an interrupted inventory pass picks every seed back
|
|
366
|
+
* up. See {@link Database.getCrawlingState} for the strict-set rationale.
|
|
367
|
+
*
|
|
368
|
+
* Idempotent: `onConflict('url').ignore()` keeps existing rows intact.
|
|
369
|
+
* The {@link Database.#getIdByUrl} crawled-wins downgrade still fires
|
|
370
|
+
* later when a crawled-lineage anchor reaches one of these seeds —
|
|
371
|
+
* that's the right behaviour (a seed that turned out to be reachable
|
|
372
|
+
* is not an orphan and should not retain the inventory label).
|
|
373
|
+
*
|
|
374
|
+
* Chunked into 500-URL batches so SQLite's bound-parameter limit
|
|
375
|
+
* (`SQLITE_MAX_VARIABLE_NUMBER`, default 999) cannot be hit even on a
|
|
376
|
+
* tens-of-thousands inventory list.
|
|
377
|
+
*
|
|
378
|
+
* Called by {@link CrawlerOrchestrator.inventory} during the
|
|
379
|
+
* `.bak`-protected ingestion phase, so any failure here aborts the run
|
|
380
|
+
* and restores from backup — the operator reruns from scratch.
|
|
381
|
+
* @param urls - URL strings already in `withoutHashAndAuth` form.
|
|
382
|
+
*/
|
|
383
|
+
insertInventorySeeds(urls: readonly string[]): Promise<void>;
|
|
243
384
|
/**
|
|
244
385
|
* Records a partial scrape failure against the page identified by `url`.
|
|
245
386
|
*
|
|
@@ -276,6 +417,44 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
276
417
|
* @param pageUrl - The URL of the page that references the resource.
|
|
277
418
|
*/
|
|
278
419
|
insertResourceReferrers(src: string, pageUrl: string): Promise<void>;
|
|
420
|
+
/**
|
|
421
|
+
* Hostnames whose `crawl_errors` history is consistently DNS failures and
|
|
422
|
+
* for which no recent 2xx-3xx page or resource is recorded — i.e. hosts
|
|
423
|
+
* the previous crawl already proved unreachable. Returned in lower-cased
|
|
424
|
+
* form. Used by `CrawlerOrchestrator.#preloadDnsBurnedHostCache` so the
|
|
425
|
+
* next session short-circuits HEAD pre-flight on these hosts.
|
|
426
|
+
*
|
|
427
|
+
* Implementation: a coarse `LIKE` filter over `crawl_errors.message`
|
|
428
|
+
* narrows the row set, then `classifyErrorKind` confirms `'dns'` in JS
|
|
429
|
+
* (the regex is the single truth source — DB-side filters never narrow
|
|
430
|
+
* it). Exclusion bags are built from a single `pages` and a single
|
|
431
|
+
* `resources` scan: any host with a 2xx-3xx page, a 2xx-3xx resource, or
|
|
432
|
+
* a `pages.lastCrawledAt` newer than its latest DNS error is dropped
|
|
433
|
+
* (the host probably recovered between the failure and the last crawl).
|
|
434
|
+
*
|
|
435
|
+
* Returns `[]` on legacy archives that pre-date the `crawl_errors`
|
|
436
|
+
* table — the `hasTable` guard keeps the call non-destructive.
|
|
437
|
+
* @returns Lower-cased hostnames safe to short-circuit.
|
|
438
|
+
*/
|
|
439
|
+
listDnsBurnedHostCandidates(): Promise<string[]>;
|
|
440
|
+
/**
|
|
441
|
+
* Appends one row to the `inventory_runs` audit log.
|
|
442
|
+
*
|
|
443
|
+
* Called by {@link CrawlerOrchestrator.inventory} on every successful
|
|
444
|
+
* `--inventory <list>` invocation so the archive carries a durable
|
|
445
|
+
* record of which deploy list was applied when and at what scale —
|
|
446
|
+
* the operational question "did we apply last month's list" the
|
|
447
|
+
* archive itself can answer without consulting external bookkeeping.
|
|
448
|
+
*
|
|
449
|
+
* Append-only at Phase 1. There is intentionally no UPDATE path and
|
|
450
|
+
* no UNIQUE constraint on `source_file_sha256`; two applies of the
|
|
451
|
+
* same list each get their own row, and `Phase 3 --refresh` is where
|
|
452
|
+
* dedupe / pre-flight against the hash will land. Field-level NULL
|
|
453
|
+
* semantics live on {@link InventoryRunMeta}.
|
|
454
|
+
* @param meta - The run metadata to record. Only `ran_at` is required.
|
|
455
|
+
* @returns The autoincremented `id` of the newly-inserted row.
|
|
456
|
+
*/
|
|
457
|
+
recordInventoryRun(meta: InventoryRunMeta): Promise<number>;
|
|
279
458
|
/**
|
|
280
459
|
* Records a redirect edge (source → destination) **without** re-storing the
|
|
281
460
|
* destination's content.
|
|
@@ -293,8 +472,17 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
293
472
|
* The destination's existing anchors / images are never touched here.
|
|
294
473
|
* @param page - HEAD-resolved page data carrying the redirect chain. Its
|
|
295
474
|
* `anchorList` / `imageList` are ignored (a redirect source owns no content).
|
|
296
|
-
|
|
297
|
-
|
|
475
|
+
* @param source - Inventory provenance forwarded by the orchestrator
|
|
476
|
+
* (`Archive.setRedirect` → here) for the redirect-edge fast path. Used
|
|
477
|
+
* as the fallback when the originating URL's row does NOT yet exist in
|
|
478
|
+
* the archive (`#73` convergence on first sight, js-redirect rescue
|
|
479
|
+
* before any prior write). When the originating row already exists
|
|
480
|
+
* (e.g. anchor-lineage INSERT from a prior pass), its stored `source`
|
|
481
|
+
* takes precedence so transitive lineage is preserved across resume /
|
|
482
|
+
* retry-failed sessions. `undefined` keeps the DB DEFAULT `'crawled'`
|
|
483
|
+
* on a brand-new destination row.
|
|
484
|
+
*/
|
|
485
|
+
recordRedirect(page: PageData, source?: PageSource): Promise<void>;
|
|
298
486
|
/**
|
|
299
487
|
* Promote previously-external pages whose URL falls under any of the new scope
|
|
300
488
|
* entries back to a "needs scraping" state so that the next crawl picks them up
|
|
@@ -330,13 +518,26 @@ export declare class Database extends EventEmitter<DatabaseEvent> {
|
|
|
330
518
|
* - `status` is in the `5xx` range — a (frequently transient) server error.
|
|
331
519
|
*
|
|
332
520
|
* Definitive `4xx` responses are intentionally excluded: re-fetching a 404
|
|
333
|
-
* almost always yields the same answer.
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
521
|
+
* almost always yields the same answer.
|
|
522
|
+
*
|
|
523
|
+
* A second exclusion runs in JS after the SQL candidate scan: any page whose
|
|
524
|
+
* latest recorded `page_errors` / `crawl_errors` message classifies into a
|
|
525
|
+
* permanent {@link PERMANENT_ERROR_KINDS} kind (dns / tls / client-blocked /
|
|
526
|
+
* parse-error / connection-refused) is left as-is rather than reset to
|
|
527
|
+
* pending. Without this filter, `--retry-failed` never converges: NXDOMAIN
|
|
528
|
+
* hosts, expired-cert hosts, and `ERR_BLOCKED_BY_CLIENT` ad pixels would be
|
|
529
|
+
* reset every iteration, re-attempted, fail identically, and rejoin the
|
|
530
|
+
* candidate pool for the next iteration. The exclusion keeps the retry
|
|
531
|
+
* target shrinking across `--retry-failed` passes by leaving deterministic
|
|
532
|
+
* dead-ends alone.
|
|
533
|
+
*
|
|
534
|
+
* Matching rows — internal and external alike — are demoted back to pending
|
|
535
|
+
* (`scraped = 0`) and have their stale scrape metadata cleared. The page row
|
|
536
|
+
* itself is kept (id preserved) so existing `anchors.hrefId` referrers stay
|
|
537
|
+
* valid, and `isExternal` is left untouched so the next pass re-classifies
|
|
538
|
+
* each page from the crawl scope. Related `anchors`, `images`,
|
|
539
|
+
* `resources-referrers`, and `page_errors` rows are deleted so the re-scrape
|
|
540
|
+
* can re-insert fresh data without duplicates.
|
|
340
541
|
*
|
|
341
542
|
* SELECT and UPDATE/DELETE statements are chunked to stay below SQLite's
|
|
342
543
|
* `SQLITE_LIMIT_VARIABLE_NUMBER`.
|