@nitpicker/crawler 0.13.0 → 0.15.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-accessor.d.ts +56 -0
- package/lib/archive/archive-accessor.js +72 -0
- package/lib/archive/archive.d.ts +108 -9
- package/lib/archive/archive.js +109 -7
- package/lib/archive/body-hash/compute-body-hash.d.ts +22 -0
- package/lib/archive/body-hash/compute-body-hash.js +31 -0
- package/lib/archive/body-hash/extract-body.d.ts +18 -0
- package/lib/archive/body-hash/extract-body.js +29 -0
- package/lib/archive/body-hash/mask-dynamic-ids.d.ts +33 -0
- package/lib/archive/body-hash/mask-dynamic-ids.js +44 -0
- package/lib/archive/body-hash/normalize-url-like-strings.d.ts +16 -0
- package/lib/archive/body-hash/normalize-url-like-strings.js +19 -0
- package/lib/archive/cache/clear-archive-cache-entry.d.ts +19 -0
- package/lib/archive/cache/clear-archive-cache-entry.js +25 -0
- package/lib/archive/cache/clear-archive-cache-root.d.ts +26 -0
- package/lib/archive/cache/clear-archive-cache-root.js +32 -0
- package/lib/archive/cache/list-archive-cache-entries.d.ts +23 -0
- package/lib/archive/cache/list-archive-cache-entries.js +125 -0
- package/lib/archive/cache/path-exists.d.ts +9 -0
- package/lib/archive/cache/path-exists.js +18 -0
- package/lib/archive/cache/types.d.ts +33 -0
- package/lib/archive/cache/types.js +1 -0
- package/lib/archive/create-adjunct-tables.d.ts +18 -0
- package/lib/archive/create-adjunct-tables.js +277 -0
- package/lib/archive/create-entity-tables.d.ts +14 -0
- package/lib/archive/create-entity-tables.js +52 -1
- package/lib/archive/database.d.ts +91 -2
- package/lib/archive/database.js +128 -0
- package/lib/archive/db-ops/_shared/clear-write-ref-caches.js +1 -0
- package/lib/archive/db-ops/_shared/compress-payload.d.ts +26 -0
- package/lib/archive/db-ops/_shared/compress-payload.js +30 -0
- package/lib/archive/db-ops/_shared/create-write-ref-caches.js +1 -0
- package/lib/archive/db-ops/_shared/types.d.ts +2 -0
- package/lib/archive/db-ops/_shared/upsert-json-ref.js +3 -3
- package/lib/archive/db-ops/analysis/replace-page-templates.d.ts +29 -0
- package/lib/archive/db-ops/analysis/replace-page-templates.js +84 -0
- package/lib/archive/db-ops/analysis/types.d.ts +61 -0
- package/lib/archive/db-ops/analysis/types.js +1 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +1 -0
- package/lib/archive/db-ops/console-logs/compute-console-log-hash.d.ts +19 -0
- package/lib/archive/db-ops/console-logs/compute-console-log-hash.js +30 -0
- package/lib/archive/db-ops/console-logs/replace-console-logs.d.ts +34 -0
- package/lib/archive/db-ops/console-logs/replace-console-logs.js +123 -0
- package/lib/archive/db-ops/console-logs/stringify-console-log-args.d.ts +16 -0
- package/lib/archive/db-ops/console-logs/stringify-console-log-args.js +26 -0
- package/lib/archive/db-ops/console-logs/types.d.ts +38 -0
- package/lib/archive/db-ops/console-logs/types.js +1 -0
- package/lib/archive/db-ops/console-logs/upsert-console-log-item.d.ts +29 -0
- package/lib/archive/db-ops/console-logs/upsert-console-log-item.js +52 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.d.ts +11 -0
- package/lib/archive/db-ops/errors/list-dns-burned-host-candidates.js +21 -4
- package/lib/archive/db-ops/inventory/record-inventory-run.js +1 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +25 -10
- package/lib/archive/db-ops/lifecycle/init.js +39 -10
- package/lib/archive/db-ops/meta/get-audios-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-audios-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-buttons-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-buttons-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-canvases-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-canvases-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-headings-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-headings-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-iframes-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-iframes-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-main-content-images-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-main-content-images-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-main-content-tables-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-main-content-tables-of-page.js +15 -0
- package/lib/archive/db-ops/meta/get-videos-of-page.d.ts +11 -0
- package/lib/archive/db-ops/meta/get-videos-of-page.js +15 -0
- package/lib/archive/db-ops/outages/close-network-outage.d.ts +12 -0
- package/lib/archive/db-ops/outages/close-network-outage.js +15 -0
- package/lib/archive/db-ops/outages/close-stale-open-network-outages.d.ts +26 -0
- package/lib/archive/db-ops/outages/close-stale-open-network-outages.js +42 -0
- package/lib/archive/db-ops/outages/compute-outage-clamp-timestamp.d.ts +26 -0
- package/lib/archive/db-ops/outages/compute-outage-clamp-timestamp.js +31 -0
- package/lib/archive/db-ops/outages/insert-network-outage.d.ts +14 -0
- package/lib/archive/db-ops/outages/insert-network-outage.js +29 -0
- package/lib/archive/db-ops/outages/list-network-outages.d.ts +28 -0
- package/lib/archive/db-ops/outages/list-network-outages.js +42 -0
- package/lib/archive/db-ops/pages/read/build-page-query.js +1 -1
- package/lib/archive/db-ops/pages/reset/repromote-external-pages.js +19 -10
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.d.ts +18 -3
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +48 -10
- package/lib/archive/db-ops/pages/write/insert-audios.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-audios.js +25 -0
- package/lib/archive/db-ops/pages/write/insert-buttons.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-buttons.js +29 -0
- package/lib/archive/db-ops/pages/write/insert-canvases.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-canvases.js +26 -0
- package/lib/archive/db-ops/pages/write/insert-headings.d.ts +15 -0
- package/lib/archive/db-ops/pages/write/insert-headings.js +27 -0
- package/lib/archive/db-ops/pages/write/insert-iframes.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-iframes.js +28 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-images.d.ts +20 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-images.js +32 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-tables.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-main-content-tables.js +29 -0
- package/lib/archive/db-ops/pages/write/insert-page.js +15 -2
- package/lib/archive/db-ops/pages/write/insert-videos.d.ts +14 -0
- package/lib/archive/db-ops/pages/write/insert-videos.js +28 -0
- package/lib/archive/db-ops/pages/write/update-page.js +33 -0
- package/lib/archive/db-ops/pages/write/write-page-html-blob.js +6 -6
- package/lib/archive/filesystem/output-binary.d.ts +12 -0
- package/lib/archive/filesystem/output-binary.js +17 -0
- package/lib/archive/get-failed-page-messages.d.ts +20 -8
- package/lib/archive/get-failed-page-messages.js +29 -15
- package/lib/archive/init-schema.js +2 -1
- package/lib/archive/meta/compute-main-contents-denormalized.d.ts +22 -0
- package/lib/archive/meta/compute-main-contents-denormalized.js +63 -0
- package/lib/archive/meta/types.d.ts +263 -0
- package/lib/archive/migrate-content-items-alias-of-id.d.ts +47 -0
- package/lib/archive/migrate-content-items-alias-of-id.js +58 -0
- package/lib/archive/migrate-info-main-content-selector.d.ts +11 -0
- package/lib/archive/migrate-info-main-content-selector.js +24 -0
- package/lib/archive/migrate-inventory-runs-invalid-skipped.d.ts +11 -0
- package/lib/archive/migrate-inventory-runs-invalid-skipped.js +24 -0
- package/lib/archive/migrate-main-contents-columns.d.ts +21 -0
- package/lib/archive/migrate-main-contents-columns.js +50 -0
- package/lib/archive/migrate-page-meta-body-hash.d.ts +39 -0
- package/lib/archive/migrate-page-meta-body-hash.js +52 -0
- package/lib/archive/migrate-page-meta-console-error-count.d.ts +22 -0
- package/lib/archive/migrate-page-meta-console-error-count.js +34 -0
- package/lib/archive/page.d.ts +145 -3
- package/lib/archive/page.js +195 -0
- package/lib/archive/types.d.ts +86 -2
- package/lib/archive/url-alias/compute-tier-a-alias-key.d.ts +31 -0
- package/lib/archive/url-alias/compute-tier-a-alias-key.js +36 -0
- package/lib/archive/url-alias/compute-tier-b-alias-key.d.ts +19 -0
- package/lib/archive/url-alias/compute-tier-b-alias-key.js +30 -0
- package/lib/archive/url-alias/format-alias-key.d.ts +13 -0
- package/lib/archive/url-alias/format-alias-key.js +14 -0
- package/lib/archive/url-alias/parse-alias-key-parts.d.ts +15 -0
- package/lib/archive/url-alias/parse-alias-key-parts.js +40 -0
- package/lib/archive/url-alias/types.d.ts +7 -0
- package/lib/archive/url-alias/types.js +1 -0
- package/lib/crawler/choose-probe-host.d.ts +24 -0
- package/lib/crawler/choose-probe-host.js +38 -0
- package/lib/crawler/clear-dns-burned-host-cache.js +2 -0
- package/lib/crawler/crawler.js +261 -2
- package/lib/crawler/dns-burned-host-burn-timestamps.d.ts +17 -0
- package/lib/crawler/dns-burned-host-burn-timestamps.js +17 -0
- package/lib/crawler/evict-network-classified-destination-cache-entries.d.ts +26 -0
- package/lib/crawler/evict-network-classified-destination-cache-entries.js +34 -0
- package/lib/crawler/evict-outage-tainted-dns-burns.d.ts +23 -0
- package/lib/crawler/evict-outage-tainted-dns-burns.js +26 -0
- package/lib/crawler/fetch-destination.js +2 -0
- package/lib/crawler/link-to-page-data.js +2 -0
- package/lib/crawler/network-gate.d.ts +49 -0
- package/lib/crawler/network-gate.js +78 -0
- package/lib/crawler/network-outage-detector.d.ts +51 -0
- package/lib/crawler/network-outage-detector.js +81 -0
- package/lib/crawler/network-outage-summary-counter.d.ts +21 -0
- package/lib/crawler/network-outage-summary-counter.js +18 -0
- package/lib/crawler/probe-network.d.ts +26 -0
- package/lib/crawler/probe-network.js +21 -0
- package/lib/crawler/resource-to-page-data.js +2 -0
- package/lib/crawler/types.d.ts +152 -1
- package/lib/crawler-orchestrator.d.ts +62 -18
- package/lib/crawler-orchestrator.js +163 -26
- package/lib/crawler.d.ts +21 -0
- package/lib/crawler.js +17 -0
- package/lib/is-within-outage-window.d.ts +49 -0
- package/lib/is-within-outage-window.js +33 -0
- package/lib/network-related-error-kinds.d.ts +23 -0
- package/lib/network-related-error-kinds.js +28 -0
- package/lib/types.d.ts +9 -1
- package/lib/utils/array/each-splitted.d.ts +1 -1
- package/lib/utils/compute-file-sha256.d.ts +17 -17
- package/lib/utils/compute-file-sha256.js +18 -47
- package/package.json +3 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the page's `page_main_content_buttons` rows with the freshly
|
|
4
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
5
|
+
* `mainContents !== null`.
|
|
6
|
+
*
|
|
7
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
8
|
+
* wipe prior rows on a degraded re-scrape.
|
|
9
|
+
* @param pageId - The owning `content_items.id`.
|
|
10
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
11
|
+
* @param trx - The active transaction.
|
|
12
|
+
*/
|
|
13
|
+
export async function insertButtons(pageId, mainContents, trx) {
|
|
14
|
+
if (mainContents.buttons.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const rows = mainContents.buttons.map((button, order) => ({
|
|
17
|
+
pageId,
|
|
18
|
+
order,
|
|
19
|
+
nodeName: button.nodeName,
|
|
20
|
+
role: button.role,
|
|
21
|
+
type: button.type,
|
|
22
|
+
text: button.text,
|
|
23
|
+
disabled: button.disabled,
|
|
24
|
+
}));
|
|
25
|
+
await trx('page_main_content_buttons').where('pageId', pageId).delete();
|
|
26
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
27
|
+
await trx('page_main_content_buttons').insert(chunk);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MainContentsData } from '@d-zero/beholder';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the page's `page_main_content_canvases` rows with the freshly
|
|
5
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
6
|
+
* `mainContents !== null`.
|
|
7
|
+
*
|
|
8
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
9
|
+
* wipe prior rows on a degraded re-scrape.
|
|
10
|
+
* @param pageId - The owning `content_items.id`.
|
|
11
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
12
|
+
* @param trx - The active transaction.
|
|
13
|
+
*/
|
|
14
|
+
export declare function insertCanvases(pageId: number, mainContents: MainContentsData, trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the page's `page_main_content_canvases` rows with the freshly
|
|
4
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
5
|
+
* `mainContents !== null`.
|
|
6
|
+
*
|
|
7
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
8
|
+
* wipe prior rows on a degraded re-scrape.
|
|
9
|
+
* @param pageId - The owning `content_items.id`.
|
|
10
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
11
|
+
* @param trx - The active transaction.
|
|
12
|
+
*/
|
|
13
|
+
export async function insertCanvases(pageId, mainContents, trx) {
|
|
14
|
+
if (mainContents.canvases.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const rows = mainContents.canvases.map((canvas, order) => ({
|
|
17
|
+
pageId,
|
|
18
|
+
order,
|
|
19
|
+
width: canvas.width,
|
|
20
|
+
height: canvas.height,
|
|
21
|
+
}));
|
|
22
|
+
await trx('page_main_content_canvases').where('pageId', pageId).delete();
|
|
23
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
24
|
+
await trx('page_main_content_canvases').insert(chunk);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MainContentsData } from '@d-zero/beholder';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the page's `page_main_content_headings` rows with the freshly
|
|
5
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
6
|
+
* `mainContents !== null` (non-HTML / external / metadata-only scrapes have
|
|
7
|
+
* no main-content data to write).
|
|
8
|
+
*
|
|
9
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
10
|
+
* wipe prior rows on a degraded re-scrape.
|
|
11
|
+
* @param pageId - The owning `content_items.id`.
|
|
12
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
13
|
+
* @param trx - The active transaction.
|
|
14
|
+
*/
|
|
15
|
+
export declare function insertHeadings(pageId: number, mainContents: MainContentsData, trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the page's `page_main_content_headings` rows with the freshly
|
|
4
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
5
|
+
* `mainContents !== null` (non-HTML / external / metadata-only scrapes have
|
|
6
|
+
* no main-content data to write).
|
|
7
|
+
*
|
|
8
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
9
|
+
* wipe prior rows on a degraded re-scrape.
|
|
10
|
+
* @param pageId - The owning `content_items.id`.
|
|
11
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
12
|
+
* @param trx - The active transaction.
|
|
13
|
+
*/
|
|
14
|
+
export async function insertHeadings(pageId, mainContents, trx) {
|
|
15
|
+
if (mainContents.headings.length === 0)
|
|
16
|
+
return;
|
|
17
|
+
const rows = mainContents.headings.map((heading, order) => ({
|
|
18
|
+
pageId,
|
|
19
|
+
order,
|
|
20
|
+
text: heading.text,
|
|
21
|
+
level: heading.level,
|
|
22
|
+
}));
|
|
23
|
+
await trx('page_main_content_headings').where('pageId', pageId).delete();
|
|
24
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
25
|
+
await trx('page_main_content_headings').insert(chunk);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MainContentsData } from '@d-zero/beholder';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the page's `page_main_content_iframes` rows with the freshly
|
|
5
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
6
|
+
* `mainContents !== null`.
|
|
7
|
+
*
|
|
8
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
9
|
+
* wipe prior rows on a degraded re-scrape.
|
|
10
|
+
* @param pageId - The owning `content_items.id`.
|
|
11
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
12
|
+
* @param trx - The active transaction.
|
|
13
|
+
*/
|
|
14
|
+
export declare function insertIframes(pageId: number, mainContents: MainContentsData, trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the page's `page_main_content_iframes` rows with the freshly
|
|
4
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
5
|
+
* `mainContents !== null`.
|
|
6
|
+
*
|
|
7
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
8
|
+
* wipe prior rows on a degraded re-scrape.
|
|
9
|
+
* @param pageId - The owning `content_items.id`.
|
|
10
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
11
|
+
* @param trx - The active transaction.
|
|
12
|
+
*/
|
|
13
|
+
export async function insertIframes(pageId, mainContents, trx) {
|
|
14
|
+
if (mainContents.iframes.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const rows = mainContents.iframes.map((iframe, order) => ({
|
|
17
|
+
pageId,
|
|
18
|
+
order,
|
|
19
|
+
src: iframe.src,
|
|
20
|
+
title: iframe.title,
|
|
21
|
+
width: iframe.width,
|
|
22
|
+
height: iframe.height,
|
|
23
|
+
}));
|
|
24
|
+
await trx('page_main_content_iframes').where('pageId', pageId).delete();
|
|
25
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
26
|
+
await trx('page_main_content_iframes').insert(chunk);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MainContentsData } from '@d-zero/beholder';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the page's `page_main_content_images` rows with the freshly
|
|
5
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
6
|
+
* `mainContents !== null`.
|
|
7
|
+
*
|
|
8
|
+
* Distinct from `image_items` (the whole-page image scan, `replaceImageItems`):
|
|
9
|
+
* this table only covers images inside the detected main-content region, so
|
|
10
|
+
* `src` is stored as a plain string rather than routed through
|
|
11
|
+
* `url_refs` / `blob_refs` — same low-cross-page-reuse rationale as the
|
|
12
|
+
* `page_meta.main_content_*` columns.
|
|
13
|
+
*
|
|
14
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
15
|
+
* wipe prior rows on a degraded re-scrape.
|
|
16
|
+
* @param pageId - The owning `content_items.id`.
|
|
17
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
18
|
+
* @param trx - The active transaction.
|
|
19
|
+
*/
|
|
20
|
+
export declare function insertMainContentImages(pageId: number, mainContents: MainContentsData, trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the page's `page_main_content_images` rows with the freshly
|
|
4
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
5
|
+
* `mainContents !== null`.
|
|
6
|
+
*
|
|
7
|
+
* Distinct from `image_items` (the whole-page image scan, `replaceImageItems`):
|
|
8
|
+
* this table only covers images inside the detected main-content region, so
|
|
9
|
+
* `src` is stored as a plain string rather than routed through
|
|
10
|
+
* `url_refs` / `blob_refs` — same low-cross-page-reuse rationale as the
|
|
11
|
+
* `page_meta.main_content_*` columns.
|
|
12
|
+
*
|
|
13
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
14
|
+
* wipe prior rows on a degraded re-scrape.
|
|
15
|
+
* @param pageId - The owning `content_items.id`.
|
|
16
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
17
|
+
* @param trx - The active transaction.
|
|
18
|
+
*/
|
|
19
|
+
export async function insertMainContentImages(pageId, mainContents, trx) {
|
|
20
|
+
if (mainContents.images.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
const rows = mainContents.images.map((image, order) => ({
|
|
23
|
+
pageId,
|
|
24
|
+
order,
|
|
25
|
+
src: image.src,
|
|
26
|
+
alt: image.alt,
|
|
27
|
+
}));
|
|
28
|
+
await trx('page_main_content_images').where('pageId', pageId).delete();
|
|
29
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
30
|
+
await trx('page_main_content_images').insert(chunk);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MainContentsData } from '@d-zero/beholder';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the page's `page_main_content_tables` rows with the freshly
|
|
5
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
6
|
+
* `mainContents !== null`.
|
|
7
|
+
*
|
|
8
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
9
|
+
* wipe prior rows on a degraded re-scrape.
|
|
10
|
+
* @param pageId - The owning `content_items.id`.
|
|
11
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
12
|
+
* @param trx - The active transaction.
|
|
13
|
+
*/
|
|
14
|
+
export declare function insertMainContentTables(pageId: number, mainContents: MainContentsData, trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the page's `page_main_content_tables` rows with the freshly
|
|
4
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
5
|
+
* `mainContents !== null`.
|
|
6
|
+
*
|
|
7
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
8
|
+
* wipe prior rows on a degraded re-scrape.
|
|
9
|
+
* @param pageId - The owning `content_items.id`.
|
|
10
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
11
|
+
* @param trx - The active transaction.
|
|
12
|
+
*/
|
|
13
|
+
export async function insertMainContentTables(pageId, mainContents, trx) {
|
|
14
|
+
if (mainContents.tables.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const rows = mainContents.tables.map((table, order) => ({
|
|
17
|
+
pageId,
|
|
18
|
+
order,
|
|
19
|
+
rows: table.rows,
|
|
20
|
+
cols: table.cols,
|
|
21
|
+
hasHeader: table.hasHeader,
|
|
22
|
+
hasFooter: table.hasFooter,
|
|
23
|
+
hasMergedCell: table.hasMergedCell,
|
|
24
|
+
}));
|
|
25
|
+
await trx('page_main_content_tables').where('pageId', pageId).delete();
|
|
26
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
27
|
+
await trx('page_main_content_tables').insert(chunk);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeContentType } from '../../../../crawler/normalize-content-type.js';
|
|
2
|
+
import { computeMainContentsDenormalized } from '../../../meta/compute-main-contents-denormalized.js';
|
|
2
3
|
import { computePageDenormalized } from '../../../meta/compute-page-denormalized.js';
|
|
3
4
|
import { deriveFlatFromMeta } from '../../../meta/derive-flat-from-meta.js';
|
|
4
5
|
import { deriveMetaExtras } from '../../../meta/derive-meta-extras.js';
|
|
@@ -46,6 +47,7 @@ export async function insertPage(knex, caches, page, isTarget, trx, source) {
|
|
|
46
47
|
const pageId = await resolveContentItemId(qb, caches, page.url.withoutHashAndAuth, undefined, source);
|
|
47
48
|
const flat = deriveFlatFromMeta(page.meta, page.url.href);
|
|
48
49
|
const denorm = computePageDenormalized(page.meta);
|
|
50
|
+
const mainContentsDenorm = computeMainContentsDenormalized(page.mainContents, page.scrollHeight);
|
|
49
51
|
const extras = deriveMetaExtras(page.meta);
|
|
50
52
|
const now = Date.now();
|
|
51
53
|
// Canonicalize so the stored dictionary value matches the exact-string
|
|
@@ -112,7 +114,7 @@ export async function insertPage(knex, caches, page, isTarget, trx, source) {
|
|
|
112
114
|
entry.source = source;
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
|
-
await upsertPageMeta(qb, caches, pageId, flat, denorm, extras);
|
|
117
|
+
await upsertPageMeta(qb, caches, pageId, flat, denorm, page.mainContents == null ? null : mainContentsDenorm, extras);
|
|
116
118
|
return pageId;
|
|
117
119
|
}
|
|
118
120
|
/**
|
|
@@ -125,9 +127,16 @@ export async function insertPage(knex, caches, page, isTarget, trx, source) {
|
|
|
125
127
|
* @param pageId - The owning `content_items.id`.
|
|
126
128
|
* @param flat - Flat meta columns from `deriveFlatFromMeta`.
|
|
127
129
|
* @param denorm - Denormalised aggregates from `computePageDenormalized`.
|
|
130
|
+
* @param mainContentsDenorm - Denormalised `main_content_*` / `scroll_height_*`
|
|
131
|
+
* aggregates from `computeMainContentsDenormalized`, or `null` when the
|
|
132
|
+
* page's `mainContents` was `null` (degraded / non-HTML scrape). `null`
|
|
133
|
+
* here omits all seventeen columns from the upsert entirely — on
|
|
134
|
+
* `ON CONFLICT DO UPDATE` this leaves a previous full scrape's values
|
|
135
|
+
* untouched rather than overwriting them with NULL, mirroring the
|
|
136
|
+
* `page_main_content_*` child-table writers' same no-op-on-null guard.
|
|
128
137
|
* @param extras - Nested-Meta catch-all from `deriveMetaExtras`.
|
|
129
138
|
*/
|
|
130
|
-
async function upsertPageMeta(qb, caches, pageId, flat, denorm, extras) {
|
|
139
|
+
async function upsertPageMeta(qb, caches, pageId, flat, denorm, mainContentsDenorm, extras) {
|
|
131
140
|
const texts = new Set();
|
|
132
141
|
for (const { source } of PAGE_META_COLUMN_MAPS.text) {
|
|
133
142
|
const value = flat[source];
|
|
@@ -174,6 +183,10 @@ async function upsertPageMeta(qb, caches, pageId, flat, denorm, extras) {
|
|
|
174
183
|
tag_count: denorm.tag_count,
|
|
175
184
|
jsonld_count: denorm.jsonld_count,
|
|
176
185
|
tags_providers_csv: denorm.tags_providers_csv,
|
|
186
|
+
// Omitted entirely (not set to null) when mainContentsDenorm is null —
|
|
187
|
+
// see the parameter doc above for why this preserves prior values on
|
|
188
|
+
// ON CONFLICT DO UPDATE instead of erasing them.
|
|
189
|
+
...mainContentsDenorm,
|
|
177
190
|
};
|
|
178
191
|
for (const { source, target } of PAGE_META_COLUMN_MAPS.text) {
|
|
179
192
|
const value = flat[source];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MainContentsData } from '@d-zero/beholder';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the page's `page_main_content_videos` rows with the freshly
|
|
5
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
6
|
+
* `mainContents !== null`.
|
|
7
|
+
*
|
|
8
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
9
|
+
* wipe prior rows on a degraded re-scrape.
|
|
10
|
+
* @param pageId - The owning `content_items.id`.
|
|
11
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
12
|
+
* @param trx - The active transaction.
|
|
13
|
+
*/
|
|
14
|
+
export declare function insertVideos(pageId: number, mainContents: MainContentsData, trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the page's `page_main_content_videos` rows with the freshly
|
|
4
|
+
* captured set. Called inside `updatePage`'s transaction, gated on
|
|
5
|
+
* `mainContents !== null`.
|
|
6
|
+
*
|
|
7
|
+
* Same empty-guard as `insertTags` / `insertJsonLd`: an empty array does not
|
|
8
|
+
* wipe prior rows on a degraded re-scrape.
|
|
9
|
+
* @param pageId - The owning `content_items.id`.
|
|
10
|
+
* @param mainContents - Beholder's per-page main-content metrics.
|
|
11
|
+
* @param trx - The active transaction.
|
|
12
|
+
*/
|
|
13
|
+
export async function insertVideos(pageId, mainContents, trx) {
|
|
14
|
+
if (mainContents.videos.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const rows = mainContents.videos.map((video, order) => ({
|
|
17
|
+
pageId,
|
|
18
|
+
order,
|
|
19
|
+
src: video.src,
|
|
20
|
+
poster: video.poster,
|
|
21
|
+
width: video.width,
|
|
22
|
+
height: video.height,
|
|
23
|
+
}));
|
|
24
|
+
await trx('page_main_content_videos').where('pageId', pageId).delete();
|
|
25
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
26
|
+
await trx('page_main_content_videos').insert(chunk);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { tryParseUrl as parseUrl } from '@d-zero/shared/parse-url';
|
|
2
2
|
import { isHtmlContentType } from '../../../../crawler/is-html-content-type.js';
|
|
3
3
|
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
4
|
+
import { computeBodyHash } from '../../../body-hash/compute-body-hash.js';
|
|
4
5
|
import { dbLog } from '../../../debug.js';
|
|
5
6
|
import { deriveLineageFromParent } from '../../../derive-lineage-from-parent.js';
|
|
6
7
|
import { matchImagesToDomPaths } from '../../../populate-entity-tables/match-images-to-dom-paths.js';
|
|
@@ -9,9 +10,17 @@ import { resolveRedirectChain } from '../../../resolve-redirect-chain.js';
|
|
|
9
10
|
import { clearWriteRefCaches } from '../../_shared/clear-write-ref-caches.js';
|
|
10
11
|
import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
|
|
11
12
|
import { resolveUrlOrBlob } from '../../_shared/resolve-url-or-blob.js';
|
|
13
|
+
import { insertAudios } from './insert-audios.js';
|
|
14
|
+
import { insertButtons } from './insert-buttons.js';
|
|
15
|
+
import { insertCanvases } from './insert-canvases.js';
|
|
16
|
+
import { insertHeadings } from './insert-headings.js';
|
|
17
|
+
import { insertIframes } from './insert-iframes.js';
|
|
12
18
|
import { insertJsonLd } from './insert-jsonld.js';
|
|
19
|
+
import { insertMainContentImages } from './insert-main-content-images.js';
|
|
20
|
+
import { insertMainContentTables } from './insert-main-content-tables.js';
|
|
13
21
|
import { insertPage } from './insert-page.js';
|
|
14
22
|
import { insertTags } from './insert-tags.js';
|
|
23
|
+
import { insertVideos } from './insert-videos.js';
|
|
15
24
|
import { linkRedirectSources } from './link-redirect-sources.js';
|
|
16
25
|
import { writePageHtmlBlob } from './write-page-html-blob.js';
|
|
17
26
|
/**
|
|
@@ -92,6 +101,19 @@ async function updatePageInTransaction(trx, knex, caches, page, destUrlObject, s
|
|
|
92
101
|
if (writeHtml) {
|
|
93
102
|
await insertJsonLd(pageId, page.meta, trx);
|
|
94
103
|
}
|
|
104
|
+
// beholder's MainContentsData is captured in the same render pass as the
|
|
105
|
+
// HTML body / JSON-LD, so it shares the same `writeHtml` + non-null gate:
|
|
106
|
+
// external / non-HTML / metadata-only scrapes never populate it.
|
|
107
|
+
if (writeHtml && page.mainContents) {
|
|
108
|
+
await insertHeadings(pageId, page.mainContents, trx);
|
|
109
|
+
await insertMainContentImages(pageId, page.mainContents, trx);
|
|
110
|
+
await insertMainContentTables(pageId, page.mainContents, trx);
|
|
111
|
+
await insertButtons(pageId, page.mainContents, trx);
|
|
112
|
+
await insertIframes(pageId, page.mainContents, trx);
|
|
113
|
+
await insertVideos(pageId, page.mainContents, trx);
|
|
114
|
+
await insertAudios(pageId, page.mainContents, trx);
|
|
115
|
+
await insertCanvases(pageId, page.mainContents, trx);
|
|
116
|
+
}
|
|
95
117
|
// Chain lineage propagates FROM the originating URL
|
|
96
118
|
// (`page.url`), NOT from the destination. See the matching
|
|
97
119
|
// rationale in `recordRedirect`: intermediates are reached
|
|
@@ -120,6 +142,12 @@ async function updatePageInTransaction(trx, knex, caches, page, destUrlObject, s
|
|
|
120
142
|
// content check alone expresses the intent without a redundant term.
|
|
121
143
|
if (writeHtml && page.html.length > 0) {
|
|
122
144
|
await writePageHtmlBlob(pageId, page.html, trx);
|
|
145
|
+
// Computed from the same `page.html` written above, in the same
|
|
146
|
+
// transaction, so `page_meta.body_hash` never observably lags the
|
|
147
|
+
// snapshot it was derived from.
|
|
148
|
+
await trx('page_meta')
|
|
149
|
+
.where('page_id', pageId)
|
|
150
|
+
.update({ body_hash: computeBodyHash(page.html) });
|
|
123
151
|
}
|
|
124
152
|
else if (writeHtml &&
|
|
125
153
|
page.contentType !== null &&
|
|
@@ -134,7 +162,12 @@ async function updatePageInTransaction(trx, knex, caches, page, destUrlObject, s
|
|
|
134
162
|
// stale ref can only have been written by a snapshot-capable call
|
|
135
163
|
// (`setPage`); `setExternalPage` passes `writeHtml = false` and never
|
|
136
164
|
// sets `html`, so it has nothing to clear.
|
|
165
|
+
//
|
|
166
|
+
// `page_meta.body_hash` is cleared alongside it for the same reason —
|
|
167
|
+
// it is derived from the same HTML `page_html_ref` points at, so it
|
|
168
|
+
// must not outlive the ref it was computed from.
|
|
137
169
|
await trx('page_html_ref').where('page_id', pageId).delete();
|
|
170
|
+
await trx('page_meta').where('page_id', pageId).update({ body_hash: null });
|
|
138
171
|
}
|
|
139
172
|
// Re-scrape semantics: the same URL can be scraped more than once
|
|
140
173
|
// (e.g. `crawl --resume`, re-visits, `--append` re-promotion). Edges
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { compressPayload } from '../../_shared/compress-payload.js';
|
|
3
3
|
/**
|
|
4
4
|
* Encodes, dedups, and persists a page's HTML snapshot.
|
|
5
5
|
*
|
|
@@ -20,14 +20,14 @@ import { zstdCompressSync } from 'node:zlib';
|
|
|
20
20
|
export async function writePageHtmlBlob(pageId, html, trx) {
|
|
21
21
|
const rawBytes = Buffer.from(html, 'utf8');
|
|
22
22
|
const hash = createHash('sha256').update(rawBytes).digest();
|
|
23
|
-
const
|
|
23
|
+
const { body, codec, sizeRaw, sizeStored } = compressPayload(rawBytes);
|
|
24
24
|
await trx('page_html_blobs')
|
|
25
25
|
.insert({
|
|
26
26
|
hash,
|
|
27
|
-
body
|
|
28
|
-
codec
|
|
29
|
-
size_raw:
|
|
30
|
-
size_stored:
|
|
27
|
+
body,
|
|
28
|
+
codec,
|
|
29
|
+
size_raw: sizeRaw,
|
|
30
|
+
size_stored: sizeStored,
|
|
31
31
|
})
|
|
32
32
|
.onConflict('hash')
|
|
33
33
|
.ignore();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes raw bytes to a file at the specified path, creating parent
|
|
3
|
+
* directories if needed.
|
|
4
|
+
*
|
|
5
|
+
* Unlike {@link outputText}, the buffer is written verbatim with no UTF-8
|
|
6
|
+
* re-encoding, so callers that need byte-for-byte fidelity (e.g. archiving
|
|
7
|
+
* a source file of unknown or mixed encoding for audit purposes) are not
|
|
8
|
+
* exposed to lossy round-tripping through a JS string.
|
|
9
|
+
* @param filePath - The absolute or relative path to the file to write.
|
|
10
|
+
* @param data - The raw bytes to write.
|
|
11
|
+
*/
|
|
12
|
+
export declare function outputBinary(filePath: string, data: Buffer): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { mkdir } from './mkdir.js';
|
|
3
|
+
/**
|
|
4
|
+
* Writes raw bytes to a file at the specified path, creating parent
|
|
5
|
+
* directories if needed.
|
|
6
|
+
*
|
|
7
|
+
* Unlike {@link outputText}, the buffer is written verbatim with no UTF-8
|
|
8
|
+
* re-encoding, so callers that need byte-for-byte fidelity (e.g. archiving
|
|
9
|
+
* a source file of unknown or mixed encoding for audit purposes) are not
|
|
10
|
+
* exposed to lossy round-tripping through a JS string.
|
|
11
|
+
* @param filePath - The absolute or relative path to the file to write.
|
|
12
|
+
* @param data - The raw bytes to write.
|
|
13
|
+
*/
|
|
14
|
+
export async function outputBinary(filePath, data) {
|
|
15
|
+
mkdir(filePath);
|
|
16
|
+
await fs.writeFile(filePath, data);
|
|
17
|
+
}
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import type { Knex } from 'knex';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* One resolved failure message, with the timestamp it was recorded at.
|
|
4
|
+
* `createdAt` is what lets a caller (`resetFailedPages`) decide whether the
|
|
5
|
+
* failure falls inside a `network_outages` window and should be treated as
|
|
6
|
+
* retryable regardless of its classified `ErrorKind` — see
|
|
7
|
+
* `is-within-outage-window.ts`.
|
|
8
|
+
*/
|
|
9
|
+
export interface FailedPageMessage {
|
|
10
|
+
message: string;
|
|
11
|
+
/** Epoch ms the message was recorded (`page_errors.createdAt` or `crawl_errors.createdAt`). */
|
|
12
|
+
createdAt: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Bulk-resolve a raw error message (plus its timestamp) for each given page
|
|
16
|
+
* id, using only sources reachable from a {@link Knex} handle. Read order:
|
|
17
|
+
* `page_errors` (keyed by `pageId`, the most direct signal a scrape attempt
|
|
18
|
+
* recorded), then `crawl_errors` (keyed by `url`, the crawler-channel
|
|
19
|
+
* record for failures that happened before a page row was scraped).
|
|
8
20
|
*
|
|
9
21
|
* **Known limitation — pre-`crawl_errors` archives**: This helper does NOT
|
|
10
22
|
* read `error.log`. The `crawl_errors` table is created empty (by
|
|
@@ -30,8 +42,8 @@ import type { Knex } from 'knex';
|
|
|
30
42
|
* @param urls - The corresponding `pages.url` values, in the same order as
|
|
31
43
|
* `ids`. Length and indexing MUST match `ids` so the page → url join can be
|
|
32
44
|
* reconstructed without a second `pages` round-trip.
|
|
33
|
-
* @returns `Map<pageId,
|
|
34
|
-
* found in one of the consulted tables.
|
|
45
|
+
* @returns `Map<pageId, FailedPageMessage>` populated only for ids whose
|
|
46
|
+
* message was found in one of the consulted tables.
|
|
35
47
|
* @example
|
|
36
48
|
* ```ts
|
|
37
49
|
* const messages = await getFailedPageMessages(
|
|
@@ -41,4 +53,4 @@ import type { Knex } from 'knex';
|
|
|
41
53
|
* );
|
|
42
54
|
* ```
|
|
43
55
|
*/
|
|
44
|
-
export declare function getFailedPageMessages(instance: Knex, ids: readonly number[], urls: readonly string[]): Promise<Map<number,
|
|
56
|
+
export declare function getFailedPageMessages(instance: Knex, ids: readonly number[], urls: readonly string[]): Promise<Map<number, FailedPageMessage>>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** chunk size for `WHERE … IN (?)` SQLite parameter packing. */
|
|
2
2
|
const CHUNK_SIZE = 500;
|
|
3
3
|
/**
|
|
4
|
-
* Bulk-resolve a raw error message for each given page
|
|
5
|
-
* reachable from a {@link Knex} handle. Read order:
|
|
6
|
-
* `pageId`, the most direct signal a scrape attempt
|
|
7
|
-
* `crawl_errors` (keyed by `url`, the crawler-channel
|
|
8
|
-
* that happened before a page row was scraped).
|
|
4
|
+
* Bulk-resolve a raw error message (plus its timestamp) for each given page
|
|
5
|
+
* id, using only sources reachable from a {@link Knex} handle. Read order:
|
|
6
|
+
* `page_errors` (keyed by `pageId`, the most direct signal a scrape attempt
|
|
7
|
+
* recorded), then `crawl_errors` (keyed by `url`, the crawler-channel
|
|
8
|
+
* record for failures that happened before a page row was scraped).
|
|
9
9
|
*
|
|
10
10
|
* **Known limitation — pre-`crawl_errors` archives**: This helper does NOT
|
|
11
11
|
* read `error.log`. The `crawl_errors` table is created empty (by
|
|
@@ -31,8 +31,8 @@ const CHUNK_SIZE = 500;
|
|
|
31
31
|
* @param urls - The corresponding `pages.url` values, in the same order as
|
|
32
32
|
* `ids`. Length and indexing MUST match `ids` so the page → url join can be
|
|
33
33
|
* reconstructed without a second `pages` round-trip.
|
|
34
|
-
* @returns `Map<pageId,
|
|
35
|
-
* found in one of the consulted tables.
|
|
34
|
+
* @returns `Map<pageId, FailedPageMessage>` populated only for ids whose
|
|
35
|
+
* message was found in one of the consulted tables.
|
|
36
36
|
* @example
|
|
37
37
|
* ```ts
|
|
38
38
|
* const messages = await getFailedPageMessages(
|
|
@@ -61,7 +61,7 @@ export async function getFailedPageMessages(instance, ids, urls) {
|
|
|
61
61
|
// it as `parse-error` on the next run when the rows happen to be
|
|
62
62
|
// returned in a different order.
|
|
63
63
|
const rows = (await instance('page_errors')
|
|
64
|
-
.select('pageId', 'message')
|
|
64
|
+
.select('pageId', 'message', 'createdAt')
|
|
65
65
|
.whereIn('pageId', chunk)
|
|
66
66
|
.orderBy('id', 'asc'));
|
|
67
67
|
for (const row of rows) {
|
|
@@ -79,7 +79,10 @@ export async function getFailedPageMessages(instance, ids, urls) {
|
|
|
79
79
|
// `dns` / `tls` / `client-blocked` etc. — defeating
|
|
80
80
|
// `--retry-failed`'s permanent-kind exclusion.
|
|
81
81
|
if (row.message !== '' && !messageByPageId.has(row.pageId)) {
|
|
82
|
-
messageByPageId.set(row.pageId,
|
|
82
|
+
messageByPageId.set(row.pageId, {
|
|
83
|
+
message: row.message,
|
|
84
|
+
createdAt: row.createdAt,
|
|
85
|
+
});
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
}
|
|
@@ -110,11 +113,22 @@ export async function getFailedPageMessages(instance, ids, urls) {
|
|
|
110
113
|
for (let i = 0; i < missingUrls.length; i += CHUNK_SIZE) {
|
|
111
114
|
const chunk = missingUrls.slice(i, i + CHUNK_SIZE);
|
|
112
115
|
const rows = (await instance('crawl_errors')
|
|
113
|
-
.select('url', 'message')
|
|
116
|
+
.select('url', 'message', 'createdAt')
|
|
114
117
|
.whereIn('url', chunk));
|
|
115
118
|
for (const row of rows) {
|
|
116
|
-
if (row.url
|
|
117
|
-
|
|
119
|
+
if (row.url === null) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Latest-createdAt wins (fixes a previously-undefined
|
|
123
|
+
// selection among duplicate URLs — SQLite's natural scan
|
|
124
|
+
// order is implementation-defined). The most recent message
|
|
125
|
+
// is the most relevant one for both classification and outage
|
|
126
|
+
// attribution: an old NXDOMAIN followed by a network-outage
|
|
127
|
+
// blip should resolve to the outage-era message, not whichever
|
|
128
|
+
// happened to be inserted first.
|
|
129
|
+
const existing = urlToMessage.get(row.url);
|
|
130
|
+
if (existing === undefined || row.createdAt > existing.createdAt) {
|
|
131
|
+
urlToMessage.set(row.url, { message: row.message, createdAt: row.createdAt });
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
}
|
|
@@ -123,9 +137,9 @@ export async function getFailedPageMessages(instance, ids, urls) {
|
|
|
123
137
|
if (url === undefined) {
|
|
124
138
|
continue;
|
|
125
139
|
}
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
128
|
-
messageByPageId.set(id,
|
|
140
|
+
const resolved = urlToMessage.get(url);
|
|
141
|
+
if (resolved !== undefined) {
|
|
142
|
+
messageByPageId.set(id, resolved);
|
|
129
143
|
}
|
|
130
144
|
}
|
|
131
145
|
return messageByPageId;
|
|
@@ -14,7 +14,7 @@ import { createRefTables } from './create-ref-tables.js';
|
|
|
14
14
|
export async function applyConnectionPragmas(instance) {
|
|
15
15
|
// Foreign-key enforcement defaults to OFF on every new SQLite
|
|
16
16
|
// connection. Required for ON DELETE CASCADE on `page_html_ref`,
|
|
17
|
-
// `page_tags`, and `
|
|
17
|
+
// `page_tags`, `page_jsonld`, and the `page_main_content_*` tables to fire.
|
|
18
18
|
await instance.raw('PRAGMA foreign_keys = ON');
|
|
19
19
|
await instance.raw('PRAGMA wal_autocheckpoint = 1000');
|
|
20
20
|
// Negative value = KiB of memory (64 MiB). Helps large BLOB scans.
|
|
@@ -111,6 +111,7 @@ export async function initSchema(instance) {
|
|
|
111
111
|
t.boolean('disableQueries');
|
|
112
112
|
t.string('userAgent');
|
|
113
113
|
t.boolean('ignoreRobots');
|
|
114
|
+
t.string('mainContentSelector');
|
|
114
115
|
});
|
|
115
116
|
}
|
|
116
117
|
// 0.13 ref / header dictionary tables. DDL + column-level rationale
|