@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
|
@@ -93,6 +93,269 @@ export interface PageDenormalizedColumns {
|
|
|
93
93
|
/** Sorted unique providers, comma-separated (empty string when no tags). */
|
|
94
94
|
tags_providers_csv: string;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Denormalised aggregates computed at write time from beholder's
|
|
98
|
+
* `MainContentsData` / `ScrollHeightData`.
|
|
99
|
+
*
|
|
100
|
+
* Stored on `page_meta` following the same pattern as
|
|
101
|
+
* {@link PageDenormalizedColumns} (`tag_count` / `jsonld_count`): the full
|
|
102
|
+
* per-element detail lives in the `page_main_content_*` child tables, while
|
|
103
|
+
* these scalar columns let list / detail reads answer "how many headings?"
|
|
104
|
+
* without joining them. All fields are `null` when the page was not fully
|
|
105
|
+
* rendered (external / non-HTML / metadata-only scrape) — see
|
|
106
|
+
* `compute-main-contents-denormalized.ts` for the `null`-in-null-out contract.
|
|
107
|
+
* @see compute-main-contents-denormalized.ts
|
|
108
|
+
* @example
|
|
109
|
+
* const denorm: MainContentsDenormalizedColumns = {
|
|
110
|
+
* main_content_node_name: 'MAIN',
|
|
111
|
+
* main_content_id: null,
|
|
112
|
+
* main_content_role: null,
|
|
113
|
+
* main_content_selector: 'main.l-main',
|
|
114
|
+
* main_content_class_list: '["l-main"]',
|
|
115
|
+
* main_content_word_count: 1240,
|
|
116
|
+
* main_content_body_word_count: 1580,
|
|
117
|
+
* main_content_heading_count: 6,
|
|
118
|
+
* main_content_image_count: 3,
|
|
119
|
+
* main_content_table_count: 0,
|
|
120
|
+
* main_content_button_count: 1,
|
|
121
|
+
* main_content_iframe_count: 0,
|
|
122
|
+
* main_content_video_count: 0,
|
|
123
|
+
* main_content_audio_count: 0,
|
|
124
|
+
* main_content_canvas_count: 0,
|
|
125
|
+
* scroll_height_desktop: 3200,
|
|
126
|
+
* scroll_height_mobile: 5400,
|
|
127
|
+
* };
|
|
128
|
+
*/
|
|
129
|
+
export interface MainContentsDenormalizedColumns {
|
|
130
|
+
/** Detected main-content element's `nodeName` (e.g. `'MAIN'`), or `null`. */
|
|
131
|
+
main_content_node_name: string | null;
|
|
132
|
+
/** Detected main-content element's `id`, or `null`. */
|
|
133
|
+
main_content_id: string | null;
|
|
134
|
+
/** Detected main-content element's `role` attribute, or `null`. */
|
|
135
|
+
main_content_role: string | null;
|
|
136
|
+
/** Diagnostic tag+id+class selector for the detected element, or `null`. */
|
|
137
|
+
main_content_selector: string | null;
|
|
138
|
+
/** JSON-encoded array of the detected element's CSS classes, or `null`. */
|
|
139
|
+
main_content_class_list: string | null;
|
|
140
|
+
/** Character count of the main region's text content, or `null`. */
|
|
141
|
+
main_content_word_count: number | null;
|
|
142
|
+
/** Character count of `document.body`'s text content, or `null`. */
|
|
143
|
+
main_content_body_word_count: number | null;
|
|
144
|
+
/** Number of headings within the main region, or `null`. */
|
|
145
|
+
main_content_heading_count: number | null;
|
|
146
|
+
/** Number of images within the main region, or `null`. */
|
|
147
|
+
main_content_image_count: number | null;
|
|
148
|
+
/** Number of tables within the main region, or `null`. */
|
|
149
|
+
main_content_table_count: number | null;
|
|
150
|
+
/** Number of button-like elements within the main region, or `null`. */
|
|
151
|
+
main_content_button_count: number | null;
|
|
152
|
+
/** Number of iframes within the main region, or `null`. */
|
|
153
|
+
main_content_iframe_count: number | null;
|
|
154
|
+
/** Number of videos within the main region, or `null`. */
|
|
155
|
+
main_content_video_count: number | null;
|
|
156
|
+
/** Number of audios within the main region, or `null`. */
|
|
157
|
+
main_content_audio_count: number | null;
|
|
158
|
+
/** Number of canvases within the main region, or `null`. */
|
|
159
|
+
main_content_canvas_count: number | null;
|
|
160
|
+
/** `document.body.scrollHeight` at the desktop-compact preset, or `null`. */
|
|
161
|
+
scroll_height_desktop: number | null;
|
|
162
|
+
/** `document.body.scrollHeight` at the mobile-small preset, or `null`. */
|
|
163
|
+
scroll_height_mobile: number | null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* One row in the `page_main_content_headings` table.
|
|
167
|
+
* @example
|
|
168
|
+
* const row: MainContentHeadingRow = { id: 1, pageId: 42, order: 0, text: 'Welcome', level: 1 };
|
|
169
|
+
*/
|
|
170
|
+
export interface MainContentHeadingRow {
|
|
171
|
+
/** Auto-increment primary key. */
|
|
172
|
+
id: number;
|
|
173
|
+
/** FK to `content_items.id`. */
|
|
174
|
+
pageId: number;
|
|
175
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
176
|
+
order: number;
|
|
177
|
+
/** Heading text after whitespace removal, or `null` when empty. */
|
|
178
|
+
text: string | null;
|
|
179
|
+
/** Heading level (1-6) from the tag name. */
|
|
180
|
+
level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* One row in the `page_main_content_images` table.
|
|
184
|
+
* @example
|
|
185
|
+
* const row: MainContentImageRow = {
|
|
186
|
+
* id: 1,
|
|
187
|
+
* pageId: 42,
|
|
188
|
+
* order: 0,
|
|
189
|
+
* src: 'https://example.com/a.png',
|
|
190
|
+
* alt: 'A photo',
|
|
191
|
+
* };
|
|
192
|
+
*/
|
|
193
|
+
export interface MainContentImageRow {
|
|
194
|
+
/** Auto-increment primary key. */
|
|
195
|
+
id: number;
|
|
196
|
+
/** FK to `content_items.id`. */
|
|
197
|
+
pageId: number;
|
|
198
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
199
|
+
order: number;
|
|
200
|
+
/** Resolved absolute `src` URL. */
|
|
201
|
+
src: string;
|
|
202
|
+
/** `alt` attribute value (may be an empty string). */
|
|
203
|
+
alt: string;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* One row in the `page_main_content_tables` table.
|
|
207
|
+
* @example
|
|
208
|
+
* const row: MainContentTableRow = {
|
|
209
|
+
* id: 1,
|
|
210
|
+
* pageId: 42,
|
|
211
|
+
* order: 0,
|
|
212
|
+
* rows: 3,
|
|
213
|
+
* cols: 4,
|
|
214
|
+
* hasHeader: 1,
|
|
215
|
+
* hasFooter: 0,
|
|
216
|
+
* hasMergedCell: 0,
|
|
217
|
+
* };
|
|
218
|
+
*/
|
|
219
|
+
export interface MainContentTableRow {
|
|
220
|
+
/** Auto-increment primary key. */
|
|
221
|
+
id: number;
|
|
222
|
+
/** FK to `content_items.id`. */
|
|
223
|
+
pageId: number;
|
|
224
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
225
|
+
order: number;
|
|
226
|
+
/** Number of `<tr>` elements. */
|
|
227
|
+
rows: number;
|
|
228
|
+
/** Number of `th`/`td` cells in the first row. */
|
|
229
|
+
cols: number;
|
|
230
|
+
/** Whether the table contains a `<thead>` (raw SQLite 0/1; knex does not round-trip `.boolean()` columns back to JS `boolean` on read). */
|
|
231
|
+
hasHeader: 0 | 1;
|
|
232
|
+
/** Whether the table contains a `<tfoot>` (raw SQLite 0/1). */
|
|
233
|
+
hasFooter: 0 | 1;
|
|
234
|
+
/** Whether any cell uses `colspan` or `rowspan` (raw SQLite 0/1). */
|
|
235
|
+
hasMergedCell: 0 | 1;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* One row in the `page_main_content_buttons` table.
|
|
239
|
+
* @example
|
|
240
|
+
* const row: MainContentButtonRow = {
|
|
241
|
+
* id: 1,
|
|
242
|
+
* pageId: 42,
|
|
243
|
+
* order: 0,
|
|
244
|
+
* nodeName: 'BUTTON',
|
|
245
|
+
* role: null,
|
|
246
|
+
* type: 'submit',
|
|
247
|
+
* text: 'Send',
|
|
248
|
+
* disabled: 0,
|
|
249
|
+
* };
|
|
250
|
+
*/
|
|
251
|
+
export interface MainContentButtonRow {
|
|
252
|
+
/** Auto-increment primary key. */
|
|
253
|
+
id: number;
|
|
254
|
+
/** FK to `content_items.id`. */
|
|
255
|
+
pageId: number;
|
|
256
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
257
|
+
order: number;
|
|
258
|
+
/** Element tag name (e.g. `'BUTTON'`, `'A'`, `'DIV'`). */
|
|
259
|
+
nodeName: string;
|
|
260
|
+
/** `role` attribute, or `null` when absent. */
|
|
261
|
+
role: string | null;
|
|
262
|
+
/** `type` for `<button>` / `<input>`, otherwise `null`. */
|
|
263
|
+
type: string | null;
|
|
264
|
+
/** Label text after whitespace removal, or `null` when empty. */
|
|
265
|
+
text: string | null;
|
|
266
|
+
/** `true` when `disabled` or `aria-disabled="true"` (raw SQLite 0/1). */
|
|
267
|
+
disabled: 0 | 1;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* One row in the `page_main_content_iframes` table.
|
|
271
|
+
* @example
|
|
272
|
+
* const row: MainContentIframeRow = {
|
|
273
|
+
* id: 1,
|
|
274
|
+
* pageId: 42,
|
|
275
|
+
* order: 0,
|
|
276
|
+
* src: 'https://example.com/embed',
|
|
277
|
+
* title: null,
|
|
278
|
+
* width: '640',
|
|
279
|
+
* height: '360',
|
|
280
|
+
* };
|
|
281
|
+
*/
|
|
282
|
+
export interface MainContentIframeRow {
|
|
283
|
+
/** Auto-increment primary key. */
|
|
284
|
+
id: number;
|
|
285
|
+
/** FK to `content_items.id`. */
|
|
286
|
+
pageId: number;
|
|
287
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
288
|
+
order: number;
|
|
289
|
+
/** Resolved absolute `src` URL. */
|
|
290
|
+
src: string;
|
|
291
|
+
/** `title` attribute, or `null` when absent. */
|
|
292
|
+
title: string | null;
|
|
293
|
+
/** Raw `width` attribute string, or `null` when absent. */
|
|
294
|
+
width: string | null;
|
|
295
|
+
/** Raw `height` attribute string, or `null` when absent. */
|
|
296
|
+
height: string | null;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* One row in the `page_main_content_videos` table.
|
|
300
|
+
* @example
|
|
301
|
+
* const row: MainContentVideoRow = {
|
|
302
|
+
* id: 1,
|
|
303
|
+
* pageId: 42,
|
|
304
|
+
* order: 0,
|
|
305
|
+
* src: 'https://example.com/v.mp4',
|
|
306
|
+
* poster: null,
|
|
307
|
+
* width: 640,
|
|
308
|
+
* height: 360,
|
|
309
|
+
* };
|
|
310
|
+
*/
|
|
311
|
+
export interface MainContentVideoRow {
|
|
312
|
+
/** Auto-increment primary key. */
|
|
313
|
+
id: number;
|
|
314
|
+
/** FK to `content_items.id`. */
|
|
315
|
+
pageId: number;
|
|
316
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
317
|
+
order: number;
|
|
318
|
+
/** Resolved media URL. */
|
|
319
|
+
src: string;
|
|
320
|
+
/** Resolved `poster` URL, or `null` when unset. */
|
|
321
|
+
poster: string | null;
|
|
322
|
+
/** IDL `width` in pixels. */
|
|
323
|
+
width: number;
|
|
324
|
+
/** IDL `height` in pixels. */
|
|
325
|
+
height: number;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* One row in the `page_main_content_audios` table.
|
|
329
|
+
* @example
|
|
330
|
+
* const row: MainContentAudioRow = { id: 1, pageId: 42, order: 0, src: 'https://example.com/a.mp3' };
|
|
331
|
+
*/
|
|
332
|
+
export interface MainContentAudioRow {
|
|
333
|
+
/** Auto-increment primary key. */
|
|
334
|
+
id: number;
|
|
335
|
+
/** FK to `content_items.id`. */
|
|
336
|
+
pageId: number;
|
|
337
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
338
|
+
order: number;
|
|
339
|
+
/** Resolved media URL. */
|
|
340
|
+
src: string;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* One row in the `page_main_content_canvases` table.
|
|
344
|
+
* @example
|
|
345
|
+
* const row: MainContentCanvasRow = { id: 1, pageId: 42, order: 0, width: 300, height: 150 };
|
|
346
|
+
*/
|
|
347
|
+
export interface MainContentCanvasRow {
|
|
348
|
+
/** Auto-increment primary key. */
|
|
349
|
+
id: number;
|
|
350
|
+
/** FK to `content_items.id`. */
|
|
351
|
+
pageId: number;
|
|
352
|
+
/** 0-based DOM traversal order within the main content region. */
|
|
353
|
+
order: number;
|
|
354
|
+
/** IDL bitmap width. */
|
|
355
|
+
width: number;
|
|
356
|
+
/** IDL bitmap height. */
|
|
357
|
+
height: number;
|
|
358
|
+
}
|
|
96
359
|
/**
|
|
97
360
|
* One row in the `page_jsonld` table.
|
|
98
361
|
*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Adds the `info.mainContentSelector` column to archives created before it
|
|
4
|
+
* existed. `CREATE TABLE IF NOT EXISTS` (used for `info` itself) cannot
|
|
5
|
+
* retrofit a new column onto an already-existing table, so this lightweight,
|
|
6
|
+
* `hasColumn`-guarded `ALTER TABLE` runs on every `initSchema` call —
|
|
7
|
+
* idempotent, and self-healing for archives whose provisioning crashed
|
|
8
|
+
* partway through.
|
|
9
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
10
|
+
*/
|
|
11
|
+
export declare function migrateInfoMainContentSelector(instance: Knex): Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds the `info.mainContentSelector` column to archives created before it
|
|
3
|
+
* existed. `CREATE TABLE IF NOT EXISTS` (used for `info` itself) cannot
|
|
4
|
+
* retrofit a new column onto an already-existing table, so this lightweight,
|
|
5
|
+
* `hasColumn`-guarded `ALTER TABLE` runs on every `initSchema` call —
|
|
6
|
+
* idempotent, and self-healing for archives whose provisioning crashed
|
|
7
|
+
* partway through.
|
|
8
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
9
|
+
*/
|
|
10
|
+
export async function migrateInfoMainContentSelector(instance) {
|
|
11
|
+
const hasInfo = await instance.schema.hasTable('info');
|
|
12
|
+
if (!hasInfo) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const hasColumn = await instance.schema.hasColumn('info', 'mainContentSelector');
|
|
16
|
+
if (hasColumn) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
await instance.schema.table('info', (t) => {
|
|
20
|
+
t.string('mainContentSelector');
|
|
21
|
+
});
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console.error('[migrate] info.mainContentSelector column added');
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
/**
|
|
3
|
+
* Add the beholder `MainContentsData` / `ScrollHeightData` denormalised
|
|
4
|
+
* aggregate columns to `page_meta` on archives created before this feature.
|
|
5
|
+
*
|
|
6
|
+
* `page_meta` is provisioned via a bare `CREATE TABLE IF NOT EXISTS` in
|
|
7
|
+
* {@link import('./create-entity-tables.js').createEntityTables}, which
|
|
8
|
+
* self-heals a *missing table* on every `initSchema` call but is a no-op
|
|
9
|
+
* against an *existing* table — adding a column to the DDL string never
|
|
10
|
+
* reaches an archive whose `page_meta` predates this change. This mirrors
|
|
11
|
+
* {@link import('./migrate-info-roots.js').migrateInfoRoots}'s `info.roots`
|
|
12
|
+
* catch-up: a `hasColumn`-guarded `ALTER TABLE` for the one column set that
|
|
13
|
+
* `CREATE TABLE IF NOT EXISTS` cannot retrofit.
|
|
14
|
+
*
|
|
15
|
+
* Idempotent: a no-op once the columns exist. Guards on `page_meta`'s
|
|
16
|
+
* existence defensively, though by the time this runs (after `initSchema`,
|
|
17
|
+
* itself after `assertCompatibleVersion` rejects pre-0.13 archives) the
|
|
18
|
+
* table is always present.
|
|
19
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
20
|
+
*/
|
|
21
|
+
export declare function migrateMainContentsColumns(instance: Knex): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add the beholder `MainContentsData` / `ScrollHeightData` denormalised
|
|
3
|
+
* aggregate columns to `page_meta` on archives created before this feature.
|
|
4
|
+
*
|
|
5
|
+
* `page_meta` is provisioned via a bare `CREATE TABLE IF NOT EXISTS` in
|
|
6
|
+
* {@link import('./create-entity-tables.js').createEntityTables}, which
|
|
7
|
+
* self-heals a *missing table* on every `initSchema` call but is a no-op
|
|
8
|
+
* against an *existing* table — adding a column to the DDL string never
|
|
9
|
+
* reaches an archive whose `page_meta` predates this change. This mirrors
|
|
10
|
+
* {@link import('./migrate-info-roots.js').migrateInfoRoots}'s `info.roots`
|
|
11
|
+
* catch-up: a `hasColumn`-guarded `ALTER TABLE` for the one column set that
|
|
12
|
+
* `CREATE TABLE IF NOT EXISTS` cannot retrofit.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent: a no-op once the columns exist. Guards on `page_meta`'s
|
|
15
|
+
* existence defensively, though by the time this runs (after `initSchema`,
|
|
16
|
+
* itself after `assertCompatibleVersion` rejects pre-0.13 archives) the
|
|
17
|
+
* table is always present.
|
|
18
|
+
* @param instance - The Knex query builder instance connected to the database.
|
|
19
|
+
*/
|
|
20
|
+
export async function migrateMainContentsColumns(instance) {
|
|
21
|
+
const hasPageMeta = await instance.schema.hasTable('page_meta');
|
|
22
|
+
if (!hasPageMeta) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const hasColumn = await instance.schema.hasColumn('page_meta', 'main_content_word_count');
|
|
26
|
+
if (hasColumn) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await instance.schema.table('page_meta', (t) => {
|
|
30
|
+
t.string('main_content_node_name');
|
|
31
|
+
t.string('main_content_id');
|
|
32
|
+
t.string('main_content_role');
|
|
33
|
+
t.string('main_content_selector');
|
|
34
|
+
t.text('main_content_class_list');
|
|
35
|
+
t.integer('main_content_word_count');
|
|
36
|
+
t.integer('main_content_body_word_count');
|
|
37
|
+
t.integer('main_content_heading_count');
|
|
38
|
+
t.integer('main_content_image_count');
|
|
39
|
+
t.integer('main_content_table_count');
|
|
40
|
+
t.integer('main_content_button_count');
|
|
41
|
+
t.integer('main_content_iframe_count');
|
|
42
|
+
t.integer('main_content_video_count');
|
|
43
|
+
t.integer('main_content_audio_count');
|
|
44
|
+
t.integer('main_content_canvas_count');
|
|
45
|
+
t.integer('scroll_height_desktop');
|
|
46
|
+
t.integer('scroll_height_mobile');
|
|
47
|
+
});
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.error('[migrate] page_meta.main_content_* / scroll_height_* columns added');
|
|
50
|
+
}
|
package/lib/archive/page.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { Anchor, Redirect, Referrer, DB_Anchor, DB_Page, DB_Redirect, DB_Re
|
|
|
9
9
|
* Keep in sync with {@link import('./meta/types.js').FlatPageMetaColumns} —
|
|
10
10
|
* one row of `pages` has the same shape.
|
|
11
11
|
*/
|
|
12
|
-
declare const FLAT_META_COLUMNS: readonly ["lang", "dir", "charset", "baseHref", "viewport_raw", "themeColor", "applicationName", "author", "generator", "publisher", "title", "description", "keywords", "robots_raw", "robots_noindex", "robots_nofollow", "robots_noarchive", "robots_noimageindex", "googlebot", "canonical", "amphtml", "manifest", "icon_href", "appleTouchIcon_href", "og_type", "og_title", "og_url", "og_site_name", "og_description", "og_image", "og_image_alt", "og_image_width", "og_image_height", "og_locale", "og_article_published_time", "og_article_modified_time", "twitter_card", "twitter_site", "twitter_creator", "twitter_title", "twitter_description", "twitter_image", "fb_app_id", "verification_google", "formatDetection_telephone", "tag_count", "jsonld_count", "tags_providers_csv"];
|
|
12
|
+
declare const FLAT_META_COLUMNS: readonly ["lang", "dir", "charset", "baseHref", "viewport_raw", "themeColor", "applicationName", "author", "generator", "publisher", "title", "description", "keywords", "robots_raw", "robots_noindex", "robots_nofollow", "robots_noarchive", "robots_noimageindex", "googlebot", "canonical", "amphtml", "manifest", "icon_href", "appleTouchIcon_href", "og_type", "og_title", "og_url", "og_site_name", "og_description", "og_image", "og_image_alt", "og_image_width", "og_image_height", "og_locale", "og_article_published_time", "og_article_modified_time", "twitter_card", "twitter_site", "twitter_creator", "twitter_title", "twitter_description", "twitter_image", "fb_app_id", "verification_google", "formatDetection_telephone", "tag_count", "jsonld_count", "tags_providers_csv", "main_content_node_name", "main_content_id", "main_content_role", "main_content_selector", "main_content_class_list", "main_content_word_count", "main_content_body_word_count", "main_content_heading_count", "main_content_image_count", "main_content_table_count", "main_content_button_count", "main_content_iframe_count", "main_content_video_count", "main_content_audio_count", "main_content_canvas_count", "scroll_height_desktop", "scroll_height_mobile"];
|
|
13
13
|
/**
|
|
14
14
|
* Represents a crawled page stored in the archive.
|
|
15
15
|
*
|
|
@@ -90,6 +90,71 @@ export default class Page {
|
|
|
90
90
|
* or null for legacy rows / never-scraped pages.
|
|
91
91
|
*/
|
|
92
92
|
get lastCrawledAt(): number | null;
|
|
93
|
+
/**
|
|
94
|
+
* Detected main-content element's `nodeName` (e.g. `'MAIN'`), or null when
|
|
95
|
+
* no main region was found or the page was not fully rendered.
|
|
96
|
+
*/
|
|
97
|
+
get mainContentNodeName(): string | null;
|
|
98
|
+
/**
|
|
99
|
+
* Detected main-content element's `id`, or null.
|
|
100
|
+
*/
|
|
101
|
+
get mainContentId(): string | null;
|
|
102
|
+
/**
|
|
103
|
+
* Detected main-content element's `role` attribute, or null.
|
|
104
|
+
*/
|
|
105
|
+
get mainContentRole(): string | null;
|
|
106
|
+
/**
|
|
107
|
+
* Diagnostic tag+id+class selector for the detected main-content element, or null.
|
|
108
|
+
*/
|
|
109
|
+
get mainContentSelector(): string | null;
|
|
110
|
+
/**
|
|
111
|
+
* Detected main-content element's CSS classes, or null when no main region
|
|
112
|
+
* was found. Parsed from the JSON-encoded `main_content_class_list` column.
|
|
113
|
+
*/
|
|
114
|
+
get mainContentClassList(): string[] | null;
|
|
115
|
+
/**
|
|
116
|
+
* Character count of the main region's text content (denormalised
|
|
117
|
+
* aggregate written at scrape time), or null.
|
|
118
|
+
*/
|
|
119
|
+
get mainContentWordCount(): number | null;
|
|
120
|
+
/**
|
|
121
|
+
* Character count of `document.body`'s text content (denormalised
|
|
122
|
+
* aggregate written at scrape time), or null.
|
|
123
|
+
*/
|
|
124
|
+
get mainContentBodyWordCount(): number | null;
|
|
125
|
+
/**
|
|
126
|
+
* Number of headings within the main region (denormalised aggregate), or null.
|
|
127
|
+
*/
|
|
128
|
+
get mainContentHeadingCount(): number | null;
|
|
129
|
+
/**
|
|
130
|
+
* Number of images within the main region (denormalised aggregate), or null.
|
|
131
|
+
*/
|
|
132
|
+
get mainContentImageCount(): number | null;
|
|
133
|
+
/**
|
|
134
|
+
* Number of tables within the main region (denormalised aggregate), or null.
|
|
135
|
+
*/
|
|
136
|
+
get mainContentTableCount(): number | null;
|
|
137
|
+
/**
|
|
138
|
+
* Number of button-like elements within the main region (denormalised
|
|
139
|
+
* aggregate), or null.
|
|
140
|
+
*/
|
|
141
|
+
get mainContentButtonCount(): number | null;
|
|
142
|
+
/**
|
|
143
|
+
* Number of iframes within the main region (denormalised aggregate), or null.
|
|
144
|
+
*/
|
|
145
|
+
get mainContentIframeCount(): number | null;
|
|
146
|
+
/**
|
|
147
|
+
* Number of videos within the main region (denormalised aggregate), or null.
|
|
148
|
+
*/
|
|
149
|
+
get mainContentVideoCount(): number | null;
|
|
150
|
+
/**
|
|
151
|
+
* Number of audios within the main region (denormalised aggregate), or null.
|
|
152
|
+
*/
|
|
153
|
+
get mainContentAudioCount(): number | null;
|
|
154
|
+
/**
|
|
155
|
+
* Number of canvases within the main region (denormalised aggregate), or null.
|
|
156
|
+
*/
|
|
157
|
+
get mainContentCanvasCount(): number | null;
|
|
93
158
|
/**
|
|
94
159
|
* Iterable view over every flat meta column (~47 fields). Returns a frozen
|
|
95
160
|
* record so consumers can pick fields by name without re-enumerating
|
|
@@ -157,6 +222,16 @@ export default class Page {
|
|
|
157
222
|
* Use for diagnostics; specific directive flags live on `robots_*` getters.
|
|
158
223
|
*/
|
|
159
224
|
get robots_raw(): string | null;
|
|
225
|
+
/**
|
|
226
|
+
* `document.body.scrollHeight` at the desktop-compact preset (denormalised
|
|
227
|
+
* aggregate written at scrape time), or null.
|
|
228
|
+
*/
|
|
229
|
+
get scrollHeightDesktop(): number | null;
|
|
230
|
+
/**
|
|
231
|
+
* `document.body.scrollHeight` at the mobile-small preset (denormalised
|
|
232
|
+
* aggregate written at scrape time), or null.
|
|
233
|
+
*/
|
|
234
|
+
get scrollHeightMobile(): number | null;
|
|
160
235
|
/**
|
|
161
236
|
* The reason this page was skipped during crawling, or null if it was not skipped.
|
|
162
237
|
*/
|
|
@@ -210,6 +285,31 @@ export default class Page {
|
|
|
210
285
|
* @returns An array of {@link Anchor} objects representing the links on this page.
|
|
211
286
|
*/
|
|
212
287
|
getAnchors(): Promise<Anchor[]>;
|
|
288
|
+
/**
|
|
289
|
+
* Retrieves the audios within this page's detected main content region
|
|
290
|
+
* from `page_main_content_audios`. Lazy — runs a single SELECT per call.
|
|
291
|
+
* @returns Ordered audio rows.
|
|
292
|
+
*/
|
|
293
|
+
getAudios(): Promise<import("./meta/types.js").MainContentAudioRow[]>;
|
|
294
|
+
/**
|
|
295
|
+
* Retrieves the button-like elements within this page's detected main
|
|
296
|
+
* content region from `page_main_content_buttons`. Lazy — runs a single
|
|
297
|
+
* SELECT per call.
|
|
298
|
+
* @returns Ordered button rows.
|
|
299
|
+
*/
|
|
300
|
+
getButtons(): Promise<import("./meta/types.js").MainContentButtonRow[]>;
|
|
301
|
+
/**
|
|
302
|
+
* Retrieves the canvases within this page's detected main content region
|
|
303
|
+
* from `page_main_content_canvases`. Lazy — runs a single SELECT per call.
|
|
304
|
+
* @returns Ordered canvas rows.
|
|
305
|
+
*/
|
|
306
|
+
getCanvases(): Promise<import("./meta/types.js").MainContentCanvasRow[]>;
|
|
307
|
+
/**
|
|
308
|
+
* Retrieves the headings within this page's detected main content region
|
|
309
|
+
* from `page_main_content_headings`. Lazy — runs a single SELECT per call.
|
|
310
|
+
* @returns Ordered heading rows.
|
|
311
|
+
*/
|
|
312
|
+
getHeadings(): Promise<import("./meta/types.js").MainContentHeadingRow[]>;
|
|
213
313
|
/**
|
|
214
314
|
* Thin wrapper that forwards this page's id to the accessor's
|
|
215
315
|
* BLOB-read path. Lets callers hold a `Page` reference and ask for its
|
|
@@ -223,6 +323,12 @@ export default class Page {
|
|
|
223
323
|
* }
|
|
224
324
|
*/
|
|
225
325
|
getHtml(): Promise<string | null>;
|
|
326
|
+
/**
|
|
327
|
+
* Retrieves the iframes within this page's detected main content region
|
|
328
|
+
* from `page_main_content_iframes`. Lazy — runs a single SELECT per call.
|
|
329
|
+
* @returns Ordered iframe rows.
|
|
330
|
+
*/
|
|
331
|
+
getIframes(): Promise<import("./meta/types.js").MainContentIframeRow[]>;
|
|
226
332
|
/**
|
|
227
333
|
* Retrieves the JSON-LD entries for this page from `page_jsonld`.
|
|
228
334
|
* Lazy — runs a single SELECT per call. Returns entries in insertion
|
|
@@ -230,6 +336,19 @@ export default class Page {
|
|
|
230
336
|
* @returns Ordered JSON-LD / SpeculationRules rows.
|
|
231
337
|
*/
|
|
232
338
|
getJsonLd(): Promise<readonly JsonLdRow[]>;
|
|
339
|
+
/**
|
|
340
|
+
* Retrieves the images within this page's detected main content region
|
|
341
|
+
* from `page_main_content_images`. Lazy — runs a single SELECT per call.
|
|
342
|
+
* Distinct from the whole-page image scan (`image_items`).
|
|
343
|
+
* @returns Ordered image rows.
|
|
344
|
+
*/
|
|
345
|
+
getMainContentImages(): Promise<import("./meta/types.js").MainContentImageRow[]>;
|
|
346
|
+
/**
|
|
347
|
+
* Retrieves the tables within this page's detected main content region
|
|
348
|
+
* from `page_main_content_tables`. Lazy — runs a single SELECT per call.
|
|
349
|
+
* @returns Ordered table rows.
|
|
350
|
+
*/
|
|
351
|
+
getMainContentTables(): Promise<import("./meta/types.js").MainContentTableRow[]>;
|
|
233
352
|
/**
|
|
234
353
|
* Retrieves the referrers (incoming links) pointing to this page.
|
|
235
354
|
* Uses pre-loaded data if available, otherwise queries the database.
|
|
@@ -248,6 +367,12 @@ export default class Page {
|
|
|
248
367
|
* @returns Ordered tag rows.
|
|
249
368
|
*/
|
|
250
369
|
getTags(): Promise<readonly TagRow[]>;
|
|
370
|
+
/**
|
|
371
|
+
* Retrieves the videos within this page's detected main content region
|
|
372
|
+
* from `page_main_content_videos`. Lazy — runs a single SELECT per call.
|
|
373
|
+
* @returns Ordered video rows.
|
|
374
|
+
*/
|
|
375
|
+
getVideos(): Promise<import("./meta/types.js").MainContentVideoRow[]>;
|
|
251
376
|
/**
|
|
252
377
|
* Checks whether this page is an internal HTML page (not external and has `text/html` content type).
|
|
253
378
|
* @returns `true` if this is an internal HTML page, `false` otherwise.
|
|
@@ -281,6 +406,24 @@ export default class Page {
|
|
|
281
406
|
isInternalPage: boolean;
|
|
282
407
|
getAnchors: Anchor[];
|
|
283
408
|
getReferrers: Referrer[];
|
|
409
|
+
title: string | number | null;
|
|
410
|
+
main_content_word_count: string | number | null;
|
|
411
|
+
main_content_node_name: string | number | null;
|
|
412
|
+
main_content_id: string | number | null;
|
|
413
|
+
main_content_role: string | number | null;
|
|
414
|
+
main_content_selector: string | number | null;
|
|
415
|
+
main_content_class_list: string | number | null;
|
|
416
|
+
main_content_body_word_count: string | number | null;
|
|
417
|
+
main_content_heading_count: string | number | null;
|
|
418
|
+
main_content_image_count: string | number | null;
|
|
419
|
+
main_content_table_count: string | number | null;
|
|
420
|
+
main_content_button_count: string | number | null;
|
|
421
|
+
main_content_iframe_count: string | number | null;
|
|
422
|
+
main_content_video_count: string | number | null;
|
|
423
|
+
main_content_audio_count: string | number | null;
|
|
424
|
+
main_content_canvas_count: string | number | null;
|
|
425
|
+
scroll_height_desktop: string | number | null;
|
|
426
|
+
scroll_height_mobile: string | number | null;
|
|
284
427
|
lang: string | number | null;
|
|
285
428
|
dir: string | number | null;
|
|
286
429
|
charset: string | number | null;
|
|
@@ -291,7 +434,6 @@ export default class Page {
|
|
|
291
434
|
author: string | number | null;
|
|
292
435
|
generator: string | number | null;
|
|
293
436
|
publisher: string | number | null;
|
|
294
|
-
title: string | number | null;
|
|
295
437
|
description: string | number | null;
|
|
296
438
|
keywords: string | number | null;
|
|
297
439
|
robots_raw: string | number | null;
|