@nitpicker/crawler 0.13.0 → 0.14.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 +11 -0
- package/lib/archive/archive.js +13 -0
- package/lib/archive/create-adjunct-tables.d.ts +7 -0
- package/lib/archive/create-adjunct-tables.js +150 -0
- package/lib/archive/create-entity-tables.d.ts +14 -0
- package/lib/archive/create-entity-tables.js +32 -1
- package/lib/archive/database.d.ts +55 -1
- package/lib/archive/database.js +81 -0
- package/lib/archive/db-ops/analysis/replace-page-templates.d.ts +20 -0
- package/lib/archive/db-ops/analysis/replace-page-templates.js +52 -0
- package/lib/archive/db-ops/config/info-column-allowlist.js +1 -0
- package/lib/archive/db-ops/lifecycle/init.d.ts +16 -10
- package/lib/archive/db-ops/lifecycle/init.js +20 -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/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 +5 -3
- package/lib/archive/db-ops/pages/reset/reset-failed-pages.js +13 -3
- 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 +21 -0
- 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-info-main-content-selector.d.ts +11 -0
- package/lib/archive/migrate-info-main-content-selector.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/page.d.ts +144 -2
- package/lib/archive/page.js +195 -0
- package/lib/archive/types.d.ts +39 -0
- package/lib/crawler/crawler.js +4 -2
- package/lib/crawler/fetch-destination.js +2 -0
- package/lib/crawler/link-to-page-data.js +2 -0
- package/lib/crawler/resource-to-page-data.js +2 -0
- package/lib/crawler/types.d.ts +5 -0
- package/lib/crawler-orchestrator.js +2 -0
- package/package.json +3 -3
|
@@ -38,9 +38,11 @@ import { getFailedPageMessages } from '../../../get-failed-page-messages.js';
|
|
|
38
38
|
* The page row itself is kept (id preserved) so existing
|
|
39
39
|
* `anchor_edges.href_page_id` referrers stay valid, and `is_external` is
|
|
40
40
|
* left untouched so the next pass re-classifies each page from the crawl
|
|
41
|
-
* scope. Related `anchor_edges`, `image_items`, `resource_ref_edges`,
|
|
42
|
-
* `page_errors`
|
|
43
|
-
* without duplicates
|
|
41
|
+
* scope. Related `anchor_edges`, `image_items`, `resource_ref_edges`,
|
|
42
|
+
* `page_errors`, and the `page_main_content_*` child tables are deleted so
|
|
43
|
+
* the re-scrape can re-insert fresh data without duplicates — kept in sync
|
|
44
|
+
* with the `page_meta` row deletion above so a reset page's `main_content_*`
|
|
45
|
+
* counts and its child-table detail never disagree.
|
|
44
46
|
*
|
|
45
47
|
* SELECT and UPDATE/DELETE statements are chunked to stay below SQLite's
|
|
46
48
|
* `SQLITE_LIMIT_VARIABLE_NUMBER`.
|
|
@@ -118,6 +120,14 @@ export async function resetFailedPages(knex) {
|
|
|
118
120
|
await knex('page_html_ref').whereIn('page_id', chunk).delete();
|
|
119
121
|
await knex('page_tags').whereIn('pageId', chunk).delete();
|
|
120
122
|
await knex('page_jsonld').whereIn('pageId', chunk).delete();
|
|
123
|
+
await knex('page_main_content_headings').whereIn('pageId', chunk).delete();
|
|
124
|
+
await knex('page_main_content_images').whereIn('pageId', chunk).delete();
|
|
125
|
+
await knex('page_main_content_tables').whereIn('pageId', chunk).delete();
|
|
126
|
+
await knex('page_main_content_buttons').whereIn('pageId', chunk).delete();
|
|
127
|
+
await knex('page_main_content_iframes').whereIn('pageId', chunk).delete();
|
|
128
|
+
await knex('page_main_content_videos').whereIn('pageId', chunk).delete();
|
|
129
|
+
await knex('page_main_content_audios').whereIn('pageId', chunk).delete();
|
|
130
|
+
await knex('page_main_content_canvases').whereIn('pageId', chunk).delete();
|
|
121
131
|
}
|
|
122
132
|
dbLog('Reset %d failed pages back to pending', urls.length);
|
|
123
133
|
return urls;
|
|
@@ -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_audios` 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 insertAudios(pageId: number, mainContents: MainContentsData, trx: Knex.Transaction): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { eachSplitted } from '../../../../utils/array/each-splitted.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the page's `page_main_content_audios` 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 insertAudios(pageId, mainContents, trx) {
|
|
14
|
+
if (mainContents.audios.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const rows = mainContents.audios.map((audio, order) => ({
|
|
17
|
+
pageId,
|
|
18
|
+
order,
|
|
19
|
+
src: audio.src,
|
|
20
|
+
}));
|
|
21
|
+
await trx('page_main_content_audios').where('pageId', pageId).delete();
|
|
22
|
+
await eachSplitted(rows, 100, async (chunk) => {
|
|
23
|
+
await trx('page_main_content_audios').insert(chunk);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -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_buttons` 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 insertButtons(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_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
|
+
}
|
|
@@ -9,9 +9,17 @@ import { resolveRedirectChain } from '../../../resolve-redirect-chain.js';
|
|
|
9
9
|
import { clearWriteRefCaches } from '../../_shared/clear-write-ref-caches.js';
|
|
10
10
|
import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
|
|
11
11
|
import { resolveUrlOrBlob } from '../../_shared/resolve-url-or-blob.js';
|
|
12
|
+
import { insertAudios } from './insert-audios.js';
|
|
13
|
+
import { insertButtons } from './insert-buttons.js';
|
|
14
|
+
import { insertCanvases } from './insert-canvases.js';
|
|
15
|
+
import { insertHeadings } from './insert-headings.js';
|
|
16
|
+
import { insertIframes } from './insert-iframes.js';
|
|
12
17
|
import { insertJsonLd } from './insert-jsonld.js';
|
|
18
|
+
import { insertMainContentImages } from './insert-main-content-images.js';
|
|
19
|
+
import { insertMainContentTables } from './insert-main-content-tables.js';
|
|
13
20
|
import { insertPage } from './insert-page.js';
|
|
14
21
|
import { insertTags } from './insert-tags.js';
|
|
22
|
+
import { insertVideos } from './insert-videos.js';
|
|
15
23
|
import { linkRedirectSources } from './link-redirect-sources.js';
|
|
16
24
|
import { writePageHtmlBlob } from './write-page-html-blob.js';
|
|
17
25
|
/**
|
|
@@ -92,6 +100,19 @@ async function updatePageInTransaction(trx, knex, caches, page, destUrlObject, s
|
|
|
92
100
|
if (writeHtml) {
|
|
93
101
|
await insertJsonLd(pageId, page.meta, trx);
|
|
94
102
|
}
|
|
103
|
+
// beholder's MainContentsData is captured in the same render pass as the
|
|
104
|
+
// HTML body / JSON-LD, so it shares the same `writeHtml` + non-null gate:
|
|
105
|
+
// external / non-HTML / metadata-only scrapes never populate it.
|
|
106
|
+
if (writeHtml && page.mainContents) {
|
|
107
|
+
await insertHeadings(pageId, page.mainContents, trx);
|
|
108
|
+
await insertMainContentImages(pageId, page.mainContents, trx);
|
|
109
|
+
await insertMainContentTables(pageId, page.mainContents, trx);
|
|
110
|
+
await insertButtons(pageId, page.mainContents, trx);
|
|
111
|
+
await insertIframes(pageId, page.mainContents, trx);
|
|
112
|
+
await insertVideos(pageId, page.mainContents, trx);
|
|
113
|
+
await insertAudios(pageId, page.mainContents, trx);
|
|
114
|
+
await insertCanvases(pageId, page.mainContents, trx);
|
|
115
|
+
}
|
|
95
116
|
// Chain lineage propagates FROM the originating URL
|
|
96
117
|
// (`page.url`), NOT from the destination. See the matching
|
|
97
118
|
// rationale in `recordRedirect`: intermediates are reached
|
|
@@ -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
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MainContentsDenormalizedColumns } from './types.js';
|
|
2
|
+
import type { MainContentsData, ScrollHeightData } from '@d-zero/beholder';
|
|
3
|
+
/**
|
|
4
|
+
* Computes the `page_meta.main_content_*` / `scroll_height_*` denormalised
|
|
5
|
+
* aggregate columns from beholder's `MainContentsData` / `ScrollHeightData`.
|
|
6
|
+
*
|
|
7
|
+
* `mainContents` is `null` for pages that were not fully rendered (external,
|
|
8
|
+
* non-HTML, or metadata-only scrapes) — in that case every column is `null`.
|
|
9
|
+
* This mirrors {@link import('./compute-page-denormalized.js').computePageDenormalized}'s
|
|
10
|
+
* write-once-at-scrape-time pattern so list / detail reads never re-derive
|
|
11
|
+
* counts from the `page_main_content_*` child tables.
|
|
12
|
+
*
|
|
13
|
+
* Accepts `undefined` as well as `null`: `PageData` declares `mainContents`
|
|
14
|
+
* as required, but test fixtures across the codebase predate this field and
|
|
15
|
+
* omit it (`.spec.ts` files are excluded from the `tsc` build, so this goes
|
|
16
|
+
* uncaught at compile time) — `== null` tolerates both without forcing every
|
|
17
|
+
* fixture to be updated.
|
|
18
|
+
* @param mainContents - Beholder's per-page main-content metrics, or `null`/`undefined`.
|
|
19
|
+
* @param scrollHeight - Beholder's per-page scroll-height measurements, or `null`/`undefined`.
|
|
20
|
+
* @returns The seventeen denormalised columns.
|
|
21
|
+
*/
|
|
22
|
+
export declare function computeMainContentsDenormalized(mainContents: MainContentsData | null | undefined, scrollHeight: ScrollHeightData | null | undefined): MainContentsDenormalizedColumns;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes the `page_meta.main_content_*` / `scroll_height_*` denormalised
|
|
3
|
+
* aggregate columns from beholder's `MainContentsData` / `ScrollHeightData`.
|
|
4
|
+
*
|
|
5
|
+
* `mainContents` is `null` for pages that were not fully rendered (external,
|
|
6
|
+
* non-HTML, or metadata-only scrapes) — in that case every column is `null`.
|
|
7
|
+
* This mirrors {@link import('./compute-page-denormalized.js').computePageDenormalized}'s
|
|
8
|
+
* write-once-at-scrape-time pattern so list / detail reads never re-derive
|
|
9
|
+
* counts from the `page_main_content_*` child tables.
|
|
10
|
+
*
|
|
11
|
+
* Accepts `undefined` as well as `null`: `PageData` declares `mainContents`
|
|
12
|
+
* as required, but test fixtures across the codebase predate this field and
|
|
13
|
+
* omit it (`.spec.ts` files are excluded from the `tsc` build, so this goes
|
|
14
|
+
* uncaught at compile time) — `== null` tolerates both without forcing every
|
|
15
|
+
* fixture to be updated.
|
|
16
|
+
* @param mainContents - Beholder's per-page main-content metrics, or `null`/`undefined`.
|
|
17
|
+
* @param scrollHeight - Beholder's per-page scroll-height measurements, or `null`/`undefined`.
|
|
18
|
+
* @returns The seventeen denormalised columns.
|
|
19
|
+
*/
|
|
20
|
+
export function computeMainContentsDenormalized(mainContents, scrollHeight) {
|
|
21
|
+
if (mainContents == null) {
|
|
22
|
+
return {
|
|
23
|
+
main_content_node_name: null,
|
|
24
|
+
main_content_id: null,
|
|
25
|
+
main_content_role: null,
|
|
26
|
+
main_content_selector: null,
|
|
27
|
+
main_content_class_list: null,
|
|
28
|
+
main_content_word_count: null,
|
|
29
|
+
main_content_body_word_count: null,
|
|
30
|
+
main_content_heading_count: null,
|
|
31
|
+
main_content_image_count: null,
|
|
32
|
+
main_content_table_count: null,
|
|
33
|
+
main_content_button_count: null,
|
|
34
|
+
main_content_iframe_count: null,
|
|
35
|
+
main_content_video_count: null,
|
|
36
|
+
main_content_audio_count: null,
|
|
37
|
+
main_content_canvas_count: null,
|
|
38
|
+
scroll_height_desktop: null,
|
|
39
|
+
scroll_height_mobile: null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
main_content_node_name: mainContents.main?.nodeName ?? null,
|
|
44
|
+
main_content_id: mainContents.main?.id ?? null,
|
|
45
|
+
main_content_role: mainContents.main?.role ?? null,
|
|
46
|
+
main_content_selector: mainContents.main?.selector ?? null,
|
|
47
|
+
main_content_class_list: mainContents.main
|
|
48
|
+
? JSON.stringify(mainContents.main.classList)
|
|
49
|
+
: null,
|
|
50
|
+
main_content_word_count: mainContents.wordCount,
|
|
51
|
+
main_content_body_word_count: mainContents.bodyWordCount,
|
|
52
|
+
main_content_heading_count: mainContents.headings.length,
|
|
53
|
+
main_content_image_count: mainContents.images.length,
|
|
54
|
+
main_content_table_count: mainContents.tables.length,
|
|
55
|
+
main_content_button_count: mainContents.buttons.length,
|
|
56
|
+
main_content_iframe_count: mainContents.iframes.length,
|
|
57
|
+
main_content_video_count: mainContents.videos.length,
|
|
58
|
+
main_content_audio_count: mainContents.audios.length,
|
|
59
|
+
main_content_canvas_count: mainContents.canvases.length,
|
|
60
|
+
scroll_height_desktop: scrollHeight?.desktop ?? null,
|
|
61
|
+
scroll_height_mobile: scrollHeight?.mobile ?? null,
|
|
62
|
+
};
|
|
63
|
+
}
|