@nitpicker/crawler 0.18.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -15,6 +15,26 @@ import type { Knex } from 'knex';
|
|
|
15
15
|
* anything reachable via the crawled chain must be labelled `'crawled'`
|
|
16
16
|
* even if previously labelled `'inventory-*'`.
|
|
17
17
|
*
|
|
18
|
+
* `is_external`, by contrast, IS overwritten on every call — but demotion is
|
|
19
|
+
* guarded. `updatePage` keys the row by the redirect DESTINATION url while
|
|
20
|
+
* passing the REQUESTING url's `isExternal`, so the value written here
|
|
21
|
+
* describes the requester, not necessarily this row. Inheriting it is
|
|
22
|
+
* deliberate when PROMOTING (an out-of-scope soft-404 page reached from an
|
|
23
|
+
* in-scope request counts as covered by the crawl, and the viewer relies on
|
|
24
|
+
* that — see `@nitpicker/query`'s `build-directory-tree-rows.ts`). It would be
|
|
25
|
+
* wrong when DEMOTING: an out-of-scope url redirecting to an in-scope page
|
|
26
|
+
* that was already taken on as a target must not flip that page to
|
|
27
|
+
* `is_external = 1` — no reading of the column justifies erasing a real
|
|
28
|
+
* observation with an inherited one. `crawler.ts`'s `#scrapedDestinations`
|
|
29
|
+
* blocks this within one run, but that is per-`#runDeal` memory, so a later
|
|
30
|
+
* `--append` / `--retry-failed` process starts blind to what the DB already
|
|
31
|
+
* knows — hence the CASE below, which checks the ROW's own prior state
|
|
32
|
+
* instead: once `scraped = 1 AND is_external = 0` is true, no later call can
|
|
33
|
+
* flip it back to `1`. Promotion (`0 → 1` before the row has been scraped, or
|
|
34
|
+
* `1 → 0` at any time) is untouched. This mirrors how `first_crawled_at`'s `COALESCE` below
|
|
35
|
+
* protects an established value — deliberately NOT by re-deriving scope from
|
|
36
|
+
* the destination url, which would also kill the wanted promoting case.
|
|
37
|
+
*
|
|
18
38
|
* The page's response headers are decomposed and written into the
|
|
19
39
|
* header dictionary tables here — per response, not deferred to
|
|
20
40
|
* crawl-end — and the resulting `header_set_id` lands on the same
|
|
@@ -24,6 +24,26 @@ import { upsertUrlRef } from '../../_shared/upsert-url-ref.js';
|
|
|
24
24
|
* anything reachable via the crawled chain must be labelled `'crawled'`
|
|
25
25
|
* even if previously labelled `'inventory-*'`.
|
|
26
26
|
*
|
|
27
|
+
* `is_external`, by contrast, IS overwritten on every call — but demotion is
|
|
28
|
+
* guarded. `updatePage` keys the row by the redirect DESTINATION url while
|
|
29
|
+
* passing the REQUESTING url's `isExternal`, so the value written here
|
|
30
|
+
* describes the requester, not necessarily this row. Inheriting it is
|
|
31
|
+
* deliberate when PROMOTING (an out-of-scope soft-404 page reached from an
|
|
32
|
+
* in-scope request counts as covered by the crawl, and the viewer relies on
|
|
33
|
+
* that — see `@nitpicker/query`'s `build-directory-tree-rows.ts`). It would be
|
|
34
|
+
* wrong when DEMOTING: an out-of-scope url redirecting to an in-scope page
|
|
35
|
+
* that was already taken on as a target must not flip that page to
|
|
36
|
+
* `is_external = 1` — no reading of the column justifies erasing a real
|
|
37
|
+
* observation with an inherited one. `crawler.ts`'s `#scrapedDestinations`
|
|
38
|
+
* blocks this within one run, but that is per-`#runDeal` memory, so a later
|
|
39
|
+
* `--append` / `--retry-failed` process starts blind to what the DB already
|
|
40
|
+
* knows — hence the CASE below, which checks the ROW's own prior state
|
|
41
|
+
* instead: once `scraped = 1 AND is_external = 0` is true, no later call can
|
|
42
|
+
* flip it back to `1`. Promotion (`0 → 1` before the row has been scraped, or
|
|
43
|
+
* `1 → 0` at any time) is untouched. This mirrors how `first_crawled_at`'s `COALESCE` below
|
|
44
|
+
* protects an established value — deliberately NOT by re-deriving scope from
|
|
45
|
+
* the destination url, which would also kill the wanted promoting case.
|
|
46
|
+
*
|
|
27
47
|
* The page's response headers are decomposed and written into the
|
|
28
48
|
* header dictionary tables here — per response, not deferred to
|
|
29
49
|
* crawl-end — and the resulting `header_set_id` lands on the same
|
|
@@ -86,8 +106,23 @@ export async function insertPage(knex, caches, page, isTarget, trx, source) {
|
|
|
86
106
|
.where('id', pageId)
|
|
87
107
|
.update({
|
|
88
108
|
scraped: 1,
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
// Once a row has been scraped as a real crawl target, no later call
|
|
110
|
+
// may flip it back off — same inheritance-from-the-requester bug as
|
|
111
|
+
// `is_external` below (`setExternalPage` always passes `isTarget:
|
|
112
|
+
// false`), and the same fix: guard on the row's own prior state
|
|
113
|
+
// instead of per-run memory. Demoting is_target away from an
|
|
114
|
+
// established value would under-count `getScrapedHtmlPageCount`'s
|
|
115
|
+
// resume offset and silently break the "isTarget=1 means covered by
|
|
116
|
+
// the crawl" contract `accessor.getPages('page')` documents.
|
|
117
|
+
is_target: qb.raw('CASE WHEN scraped = 1 AND is_target = 1 THEN 1 ELSE ? END', [
|
|
118
|
+
isTarget ? 1 : 0,
|
|
119
|
+
]),
|
|
120
|
+
// Once a row has been scraped as internal, no later call may flip it
|
|
121
|
+
// back to external — see this function's docs for why the write this
|
|
122
|
+
// guards against happens at all.
|
|
123
|
+
is_external: qb.raw('CASE WHEN scraped = 1 AND is_external = 0 THEN 0 ELSE ? END', [
|
|
124
|
+
page.isExternal ? 1 : 0,
|
|
125
|
+
]),
|
|
91
126
|
status: page.status,
|
|
92
127
|
status_text: page.statusText,
|
|
93
128
|
content_type_id: contentTypeId,
|
|
@@ -20,7 +20,13 @@ import type { Knex } from 'knex';
|
|
|
20
20
|
* @param destId - `content_items.id` of the redirect destination page.
|
|
21
21
|
* @param destUrlNormalized - Normalised destination URL, used to detect and
|
|
22
22
|
* skip self-redirects.
|
|
23
|
-
* @param isExternal - Whether the sources are external to the crawl scope
|
|
23
|
+
* @param isExternal - Whether the sources are external to the crawl scope, as
|
|
24
|
+
* a single value applied to every hop in `sources` — the same
|
|
25
|
+
* requester-describes-a-different-row shape `insertPage` guards against
|
|
26
|
+
* (see its docs in `insert-page.ts`), since a hop can independently be a
|
|
27
|
+
* real, already-scraped internal page in its own right (reached earlier as
|
|
28
|
+
* a direct crawl target) before it is later observed as an intermediate hop
|
|
29
|
+
* in some other chain. The UPDATE below carries the same CASE guard.
|
|
24
30
|
* @param chainLineageSource - Lineage label propagated to each intermediate
|
|
25
31
|
* hop's row (passed through to {@link resolveContentItemId}). Derived by
|
|
26
32
|
* the caller from the **originating** page's source (`page.url`), not from
|
|
@@ -19,7 +19,13 @@ import { resolveContentItemId } from '../../_shared/resolve-content-item-id.js';
|
|
|
19
19
|
* @param destId - `content_items.id` of the redirect destination page.
|
|
20
20
|
* @param destUrlNormalized - Normalised destination URL, used to detect and
|
|
21
21
|
* skip self-redirects.
|
|
22
|
-
* @param isExternal - Whether the sources are external to the crawl scope
|
|
22
|
+
* @param isExternal - Whether the sources are external to the crawl scope, as
|
|
23
|
+
* a single value applied to every hop in `sources` — the same
|
|
24
|
+
* requester-describes-a-different-row shape `insertPage` guards against
|
|
25
|
+
* (see its docs in `insert-page.ts`), since a hop can independently be a
|
|
26
|
+
* real, already-scraped internal page in its own right (reached earlier as
|
|
27
|
+
* a direct crawl target) before it is later observed as an intermediate hop
|
|
28
|
+
* in some other chain. The UPDATE below carries the same CASE guard.
|
|
23
29
|
* @param chainLineageSource - Lineage label propagated to each intermediate
|
|
24
30
|
* hop's row (passed through to {@link resolveContentItemId}). Derived by
|
|
25
31
|
* the caller from the **originating** page's source (`page.url`), not from
|
|
@@ -53,7 +59,7 @@ export async function linkRedirectSources(trx, caches, sources, destId, destUrlN
|
|
|
53
59
|
.update({
|
|
54
60
|
scraped: 1,
|
|
55
61
|
redirect_dest_id: destId,
|
|
56
|
-
is_external: isExternal ? 1 : 0,
|
|
62
|
+
is_external: trx.raw('CASE WHEN scraped = 1 AND is_external = 0 THEN 0 ELSE ? END', [isExternal ? 1 : 0]),
|
|
57
63
|
});
|
|
58
64
|
// Conditional `301 Moved Permanently` stamp — applied ONLY when the
|
|
59
65
|
// row carries no definitive status yet (NULL or the `-1`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitpicker/crawler",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "Web crawler engine with headless browser rendering and archive storage",
|
|
5
5
|
"author": "D-ZERO",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"libsql": "0.5.29",
|
|
40
40
|
"puppeteer": "25.3.0",
|
|
41
41
|
"robots-parser": "3.0.1",
|
|
42
|
-
"tar": "7.5.
|
|
42
|
+
"tar": "7.5.22"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/debug": "4.1.13",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"@types/tar": "7.0.87",
|
|
49
49
|
"@types/unzipper": "0.10.11"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "cf366bfa2ca1e0528623b3fbe1c17ac4237ca4e4"
|
|
52
52
|
}
|