@nitpicker/crawler 0.6.3 → 0.7.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/crawler/fetch-destination.js +11 -5
- package/package.json +2 -2
- package/lib/archive/common-queries.d.ts +0 -14
- package/lib/archive/common-queries.js +0 -40
- package/lib/archive/filesystem/index.d.ts +0 -17
- package/lib/archive/filesystem/index.js +0 -17
- package/lib/archive/filesystem/utils.d.ts +0 -109
- package/lib/archive/filesystem/utils.js +0 -185
- package/lib/archive/filesystem/zip.d.ts +0 -29
- package/lib/archive/filesystem/zip.js +0 -53
- package/lib/archive/index.d.ts +0 -6
- package/lib/archive/index.js +0 -11
- package/lib/crawler/index.d.ts +0 -2
- package/lib/crawler/index.js +0 -2
- package/lib/crawler/network.d.ts +0 -30
- package/lib/crawler/network.js +0 -226
- package/lib/crawler/result-handler.d.ts +0 -118
- package/lib/crawler/result-handler.js +0 -153
- package/lib/crawler/speculative-pagination.d.ts +0 -52
- package/lib/crawler/speculative-pagination.js +0 -215
- package/lib/crawler/url-filter.d.ts +0 -56
- package/lib/crawler/url-filter.js +0 -110
- package/lib/index.d.ts +0 -16
- package/lib/index.js +0 -18
- package/lib/qzilla.d.ts +0 -136
- package/lib/qzilla.js +0 -292
- package/lib/utils/array/index.d.ts +0 -1
- package/lib/utils/array/index.js +0 -1
- package/lib/utils/async/index.d.ts +0 -1
- package/lib/utils/async/index.js +0 -1
- package/lib/utils/error/index.d.ts +0 -3
- package/lib/utils/error/index.js +0 -2
- package/lib/utils/event-emitter/index.d.ts +0 -6
- package/lib/utils/event-emitter/index.js +0 -6
- package/lib/utils/index.d.ts +0 -5
- package/lib/utils/index.js +0 -5
- package/lib/utils/network/index.d.ts +0 -1
- package/lib/utils/network/index.js +0 -1
- package/lib/utils/object/index.d.ts +0 -1
- package/lib/utils/object/index.js +0 -1
- package/lib/utils/path/index.d.ts +0 -1
- package/lib/utils/path/index.js +0 -1
- package/lib/utils/path/safe-filepath.d.ts +0 -7
- package/lib/utils/path/safe-filepath.js +0 -12
- package/lib/utils/regexp/index.d.ts +0 -1
- package/lib/utils/regexp/index.js +0 -1
- package/lib/utils/retryable/index.d.ts +0 -2
- package/lib/utils/retryable/index.js +0 -1
- package/lib/utils/sort/index.d.ts +0 -14
- package/lib/utils/sort/index.js +0 -61
- package/lib/utils/sort/remove-matches.d.ts +0 -9
- package/lib/utils/sort/remove-matches.js +0 -23
- package/lib/utils/types/index.d.ts +0 -1
- package/lib/utils/types/index.js +0 -1
- package/lib/utils/url/index.d.ts +0 -5
- package/lib/utils/url/index.js +0 -5
- package/lib/utils/url/is-lower-layer.d.ts +0 -15
- package/lib/utils/url/is-lower-layer.js +0 -55
- package/lib/utils/url/parse-url.d.ts +0 -11
- package/lib/utils/url/parse-url.js +0 -20
- package/lib/utils/url/path-match.d.ts +0 -11
- package/lib/utils/url/path-match.js +0 -18
- package/lib/utils/url/sort-url.d.ts +0 -10
- package/lib/utils/url/sort-url.js +0 -24
- package/lib/utils/url/url-partial-match.d.ts +0 -11
- package/lib/utils/url/url-partial-match.js +0 -32
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { isError } from '@nitpicker/beholder';
|
|
2
|
-
/**
|
|
3
|
-
* Compares two consecutive URL strings and detects a single-token numeric
|
|
4
|
-
* pagination pattern (e.g. `/page/1` → `/page/2`, or `?p=1` → `?p=2`).
|
|
5
|
-
*
|
|
6
|
-
* The algorithm decomposes each URL into tokens (path segments + sorted query values),
|
|
7
|
-
* then checks that exactly one token differs and both values are integers with a
|
|
8
|
-
* positive step. Returns `null` when no pattern is detected.
|
|
9
|
-
*
|
|
10
|
-
* WHY single-token constraint: Multi-token differences (e.g. both path and query
|
|
11
|
-
* changing) indicate different routes rather than pagination, so they are rejected.
|
|
12
|
-
* @param prevUrl - The previously pushed URL (protocol-agnostic, without hash/auth)
|
|
13
|
-
* @param currentUrl - The newly discovered URL
|
|
14
|
-
* @returns The detected pattern, or `null` if no pagination pattern was found
|
|
15
|
-
*/
|
|
16
|
-
export function detectPaginationPattern(prevUrl, currentUrl) {
|
|
17
|
-
const prev = decomposeUrl(prevUrl);
|
|
18
|
-
const curr = decomposeUrl(currentUrl);
|
|
19
|
-
if (!prev || !curr)
|
|
20
|
-
return null;
|
|
21
|
-
// Host (including port) must match
|
|
22
|
-
if (prev.host !== curr.host)
|
|
23
|
-
return null;
|
|
24
|
-
// Path segment count must match
|
|
25
|
-
if (prev.pathSegments.length !== curr.pathSegments.length)
|
|
26
|
-
return null;
|
|
27
|
-
// Query key sets must match in count and identity
|
|
28
|
-
if (prev.queryKeys.length !== curr.queryKeys.length)
|
|
29
|
-
return null;
|
|
30
|
-
for (let i = 0; i < prev.queryKeys.length; i++) {
|
|
31
|
-
if (prev.queryKeys[i] !== curr.queryKeys[i])
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
// Build combined token arrays: path segments + query values (sorted by key)
|
|
35
|
-
const prevTokens = [...prev.pathSegments, ...prev.queryValues];
|
|
36
|
-
const currTokens = [...curr.pathSegments, ...curr.queryValues];
|
|
37
|
-
let diffIndex = -1;
|
|
38
|
-
for (const [i, prevToken] of prevTokens.entries()) {
|
|
39
|
-
if (prevToken !== currTokens[i]) {
|
|
40
|
-
if (diffIndex !== -1)
|
|
41
|
-
return null; // more than one difference
|
|
42
|
-
diffIndex = i;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (diffIndex === -1)
|
|
46
|
-
return null; // identical URLs
|
|
47
|
-
const prevNum = Number(prevTokens[diffIndex]);
|
|
48
|
-
const currNum = Number(currTokens[diffIndex]);
|
|
49
|
-
if (!Number.isFinite(prevNum) || !Number.isFinite(currNum))
|
|
50
|
-
return null;
|
|
51
|
-
if (!Number.isInteger(prevNum) || !Number.isInteger(currNum))
|
|
52
|
-
return null;
|
|
53
|
-
const step = currNum - prevNum;
|
|
54
|
-
if (step <= 0)
|
|
55
|
-
return null;
|
|
56
|
-
return {
|
|
57
|
-
tokenIndex: diffIndex,
|
|
58
|
-
step,
|
|
59
|
-
currentNumber: currNum,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Generates speculative URLs by extrapolating the detected pagination pattern.
|
|
64
|
-
*
|
|
65
|
-
* Starting from `currentUrl`, applies the pattern's step `count` times to produce
|
|
66
|
-
* future page URLs (e.g. if step=1 and currentNumber=2, generates page 3, 4, ...).
|
|
67
|
-
* These URLs are pushed into the crawl queue and discarded later if they 404.
|
|
68
|
-
* @param pattern - The detected pagination pattern from {@link detectPaginationPattern}
|
|
69
|
-
* @param currentUrl - The URL to extrapolate from (protocol-agnostic, without hash/auth)
|
|
70
|
-
* @param count - Number of speculative URLs to generate (typically equals concurrency)
|
|
71
|
-
* @returns Array of speculative URL strings
|
|
72
|
-
*/
|
|
73
|
-
export function generateSpeculativeUrls(pattern, currentUrl, count) {
|
|
74
|
-
if (count <= 0)
|
|
75
|
-
return [];
|
|
76
|
-
const decomposed = decomposeUrl(currentUrl);
|
|
77
|
-
if (!decomposed)
|
|
78
|
-
return [];
|
|
79
|
-
const results = [];
|
|
80
|
-
for (let i = 1; i <= count; i++) {
|
|
81
|
-
const nextNum = pattern.currentNumber + pattern.step * i;
|
|
82
|
-
const url = reconstructUrl(decomposed, pattern.tokenIndex, String(nextNum));
|
|
83
|
-
results.push(url);
|
|
84
|
-
}
|
|
85
|
-
return results;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Determines whether a speculative URL's scrape result should be discarded.
|
|
89
|
-
*
|
|
90
|
-
* Speculative URLs are pre-emptively pushed into the crawl queue before
|
|
91
|
-
* knowing if they exist. This function filters out invalid results:
|
|
92
|
-
* - `error` type → discard (server unreachable, timeout, etc.)
|
|
93
|
-
* - `ignoreAndSkip` type → discard (matched exclusion rule)
|
|
94
|
-
* - `scrapeEnd` with HTTP error status (4xx/5xx) → discard
|
|
95
|
-
* - `scrapeEnd` with 2xx/3xx → keep
|
|
96
|
-
* @param result - The scrape result for the speculative URL
|
|
97
|
-
* @returns `true` if the result should be discarded (not saved to archive)
|
|
98
|
-
*/
|
|
99
|
-
export function shouldDiscardSpeculative(result) {
|
|
100
|
-
switch (result.type) {
|
|
101
|
-
case 'error': {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
case 'ignoreAndSkip': {
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
case 'scrapeEnd': {
|
|
108
|
-
if (!result.pageData)
|
|
109
|
-
return true;
|
|
110
|
-
return isError(result.pageData.status);
|
|
111
|
-
}
|
|
112
|
-
default: {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Decomposes a URL string into its constituent tokens for comparison.
|
|
119
|
-
* Handles both full URLs (`https://host/path?q=v`) and protocol-agnostic
|
|
120
|
-
* URLs (`//host/path?q=v`). Query parameters are sorted by key for
|
|
121
|
-
* consistent comparison.
|
|
122
|
-
* @param url - The URL string to decompose
|
|
123
|
-
* @returns The decomposed URL, or `null` if the format is invalid
|
|
124
|
-
*/
|
|
125
|
-
function decomposeUrl(url) {
|
|
126
|
-
// URL format: //host/path?query or //host?query (protocol-agnostic)
|
|
127
|
-
// Also handle protocol://host/path?query
|
|
128
|
-
let work = url;
|
|
129
|
-
let protocol = '';
|
|
130
|
-
// Strip protocol
|
|
131
|
-
const protoMatch = /^(https?:)?\/\//.exec(work);
|
|
132
|
-
if (!protoMatch)
|
|
133
|
-
return null;
|
|
134
|
-
protocol = protoMatch[1] ?? '';
|
|
135
|
-
work = work.slice(protoMatch[0].length);
|
|
136
|
-
// Split host from rest
|
|
137
|
-
const slashIdx = work.indexOf('/');
|
|
138
|
-
const qmarkIdx = work.indexOf('?');
|
|
139
|
-
let host;
|
|
140
|
-
let pathPart;
|
|
141
|
-
let queryPart;
|
|
142
|
-
if (slashIdx === -1 && qmarkIdx === -1) {
|
|
143
|
-
host = work;
|
|
144
|
-
pathPart = '';
|
|
145
|
-
queryPart = '';
|
|
146
|
-
}
|
|
147
|
-
else if (slashIdx === -1) {
|
|
148
|
-
host = work.slice(0, qmarkIdx);
|
|
149
|
-
pathPart = '';
|
|
150
|
-
queryPart = work.slice(qmarkIdx + 1);
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
host = work.slice(0, slashIdx);
|
|
154
|
-
const pathAndQuery = work.slice(slashIdx + 1);
|
|
155
|
-
const pq = pathAndQuery.indexOf('?');
|
|
156
|
-
if (pq === -1) {
|
|
157
|
-
pathPart = pathAndQuery;
|
|
158
|
-
queryPart = '';
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
pathPart = pathAndQuery.slice(0, pq);
|
|
162
|
-
queryPart = pathAndQuery.slice(pq + 1);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
const pathSegments = pathPart ? pathPart.split('/') : [];
|
|
166
|
-
// Parse query into sorted key-value pairs
|
|
167
|
-
const queryPairs = [];
|
|
168
|
-
if (queryPart) {
|
|
169
|
-
for (const pair of queryPart.split('&')) {
|
|
170
|
-
const eqIdx = pair.indexOf('=');
|
|
171
|
-
if (eqIdx === -1) {
|
|
172
|
-
queryPairs.push([pair, '']);
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
queryPairs.push([pair.slice(0, eqIdx), pair.slice(eqIdx + 1)]);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
queryPairs.sort((a, b) => a[0].localeCompare(b[0]));
|
|
180
|
-
return {
|
|
181
|
-
host,
|
|
182
|
-
pathSegments,
|
|
183
|
-
queryKeys: queryPairs.map(([k]) => k),
|
|
184
|
-
queryValues: queryPairs.map(([, v]) => v),
|
|
185
|
-
protocol,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Reconstructs a URL string from a decomposed representation with one
|
|
190
|
-
* token replaced at the specified index.
|
|
191
|
-
* @param decomposed - The decomposed URL to reconstruct
|
|
192
|
-
* @param tokenIndex - Index in the combined token array (path segments + query values)
|
|
193
|
-
* @param newValue - The replacement value for the token at `tokenIndex`
|
|
194
|
-
* @returns The reconstructed URL string
|
|
195
|
-
*/
|
|
196
|
-
function reconstructUrl(decomposed, tokenIndex, newValue) {
|
|
197
|
-
const { host, pathSegments, queryKeys, queryValues, protocol } = decomposed;
|
|
198
|
-
const newPathSegments = [...pathSegments];
|
|
199
|
-
const newQueryValues = [...queryValues];
|
|
200
|
-
if (tokenIndex < pathSegments.length) {
|
|
201
|
-
newPathSegments[tokenIndex] = newValue;
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
newQueryValues[tokenIndex - pathSegments.length] = newValue;
|
|
205
|
-
}
|
|
206
|
-
let url = `${protocol}//${host}`;
|
|
207
|
-
if (newPathSegments.length > 0) {
|
|
208
|
-
url += `/${newPathSegments.join('/')}`;
|
|
209
|
-
}
|
|
210
|
-
if (queryKeys.length > 0) {
|
|
211
|
-
const pairs = queryKeys.map((k, i) => `${k}=${newQueryValues[i]}`);
|
|
212
|
-
url += `?${pairs.join('&')}`;
|
|
213
|
-
}
|
|
214
|
-
return url;
|
|
215
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { ExURL, ParseURLOptions } from '@d-zero/shared/parse-url';
|
|
2
|
-
/**
|
|
3
|
-
* Determine whether a URL should be skipped during crawling.
|
|
4
|
-
*
|
|
5
|
-
* A URL is skipped if it matches any user-defined exclude glob pattern
|
|
6
|
-
* or starts with any of the excluded URL prefixes.
|
|
7
|
-
* @param url - The parsed URL to check.
|
|
8
|
-
* @param excludes - Array of glob patterns for URLs to exclude.
|
|
9
|
-
* @param excludeUrls - Array of URL prefixes to exclude (matched via `startsWith`).
|
|
10
|
-
* @param options - URL parsing options used for pattern matching.
|
|
11
|
-
* @returns `true` if the URL should be skipped.
|
|
12
|
-
*/
|
|
13
|
-
export declare function shouldSkipUrl(url: ExURL, excludes: readonly string[], excludeUrls: readonly string[], options: ParseURLOptions): boolean;
|
|
14
|
-
/**
|
|
15
|
-
* Determine whether a URL is external to the crawl scope.
|
|
16
|
-
*
|
|
17
|
-
* A URL is considered external if its hostname does not appear
|
|
18
|
-
* as a key in the scope map.
|
|
19
|
-
* @param url - The parsed URL to check.
|
|
20
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
21
|
-
* @returns `true` if the URL is outside the crawl scope.
|
|
22
|
-
*/
|
|
23
|
-
export declare function isExternalUrl(url: ExURL, scope: ReadonlyMap<string, readonly ExURL[]>): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Inject authentication credentials from a matching scope URL into the target URL.
|
|
26
|
-
*
|
|
27
|
-
* Finds the best-matching scope URL (deepest path match) for the given URL's
|
|
28
|
-
* hostname and copies its `username` and `password` properties. This mutates
|
|
29
|
-
* the `url` parameter in place.
|
|
30
|
-
* @param url - The parsed URL to receive authentication credentials (mutated in place).
|
|
31
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
32
|
-
*/
|
|
33
|
-
export declare function injectScopeAuth(url: ExURL, scope: ReadonlyMap<string, readonly ExURL[]>): void;
|
|
34
|
-
/**
|
|
35
|
-
* Find the scope URL with the deepest matching path for a given URL.
|
|
36
|
-
*
|
|
37
|
-
* Among all scope URLs sharing the same hostname, returns the one whose
|
|
38
|
-
* path segments are a prefix of the target URL's path segments and which
|
|
39
|
-
* has the greatest depth. Returns `null` if no scope URL matches.
|
|
40
|
-
* @param url - The parsed URL to match against scope URLs.
|
|
41
|
-
* @param scopes - The list of scope URLs to search.
|
|
42
|
-
* @returns The best-matching scope URL, or `null` if none match.
|
|
43
|
-
*/
|
|
44
|
-
export declare function findBestMatchingScope(url: ExURL, scopes: readonly ExURL[]): ExURL | null;
|
|
45
|
-
/**
|
|
46
|
-
* Check whether a URL is in a lower layer (subdirectory) of any scope URL.
|
|
47
|
-
*
|
|
48
|
-
* Tests the URL against each scope URL using the `isLowerLayer` utility,
|
|
49
|
-
* which checks if the URL's path is at the same level or deeper than
|
|
50
|
-
* the scope URL's path.
|
|
51
|
-
* @param url - The parsed URL to check.
|
|
52
|
-
* @param scopes - The list of scope URLs to test against.
|
|
53
|
-
* @param options - URL parsing options used for layer comparison.
|
|
54
|
-
* @returns `true` if the URL is in a lower layer of at least one scope URL.
|
|
55
|
-
*/
|
|
56
|
-
export declare function isInAnyLowerLayer(url: ExURL, scopes: readonly ExURL[], options: ParseURLOptions): boolean;
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { isLowerLayer } from '@d-zero/shared/is-lower-layer';
|
|
2
|
-
import { pathMatch } from '@d-zero/shared/path-match';
|
|
3
|
-
import { protocolAgnosticKey } from './protocol-agnostic-key.js';
|
|
4
|
-
/**
|
|
5
|
-
* Determine whether a URL should be skipped during crawling.
|
|
6
|
-
*
|
|
7
|
-
* A URL is skipped if it matches any user-defined exclude glob pattern
|
|
8
|
-
* or starts with any of the excluded URL prefixes.
|
|
9
|
-
* @param url - The parsed URL to check.
|
|
10
|
-
* @param excludes - Array of glob patterns for URLs to exclude.
|
|
11
|
-
* @param excludeUrls - Array of URL prefixes to exclude (matched via `startsWith`).
|
|
12
|
-
* @param options - URL parsing options used for pattern matching.
|
|
13
|
-
* @returns `true` if the URL should be skipped.
|
|
14
|
-
*/
|
|
15
|
-
export function shouldSkipUrl(url, excludes, excludeUrls, options) {
|
|
16
|
-
return (excludes.some((excludeGlobPattern) => pathMatch(url, excludeGlobPattern, options)) ||
|
|
17
|
-
excludeUrls.some((prefix) => protocolAgnosticKey(url.href).startsWith(protocolAgnosticKey(prefix))));
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Determine whether a URL is external to the crawl scope.
|
|
21
|
-
*
|
|
22
|
-
* A URL is considered external if its hostname does not appear
|
|
23
|
-
* as a key in the scope map.
|
|
24
|
-
* @param url - The parsed URL to check.
|
|
25
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
26
|
-
* @returns `true` if the URL is outside the crawl scope.
|
|
27
|
-
*/
|
|
28
|
-
export function isExternalUrl(url, scope) {
|
|
29
|
-
return !scope.has(url.hostname);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Inject authentication credentials from a matching scope URL into the target URL.
|
|
33
|
-
*
|
|
34
|
-
* Finds the best-matching scope URL (deepest path match) for the given URL's
|
|
35
|
-
* hostname and copies its `username` and `password` properties. This mutates
|
|
36
|
-
* the `url` parameter in place.
|
|
37
|
-
* @param url - The parsed URL to receive authentication credentials (mutated in place).
|
|
38
|
-
* @param scope - Map of hostnames to their scope URLs.
|
|
39
|
-
*/
|
|
40
|
-
export function injectScopeAuth(url, scope) {
|
|
41
|
-
const scopes = scope.get(url.hostname);
|
|
42
|
-
if (!scopes) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const matchedScope = findBestMatchingScope(url, scopes);
|
|
46
|
-
if (matchedScope) {
|
|
47
|
-
url.username = matchedScope.username;
|
|
48
|
-
url.password = matchedScope.password;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Find the scope URL with the deepest matching path for a given URL.
|
|
53
|
-
*
|
|
54
|
-
* Among all scope URLs sharing the same hostname, returns the one whose
|
|
55
|
-
* path segments are a prefix of the target URL's path segments and which
|
|
56
|
-
* has the greatest depth. Returns `null` if no scope URL matches.
|
|
57
|
-
* @param url - The parsed URL to match against scope URLs.
|
|
58
|
-
* @param scopes - The list of scope URLs to search.
|
|
59
|
-
* @returns The best-matching scope URL, or `null` if none match.
|
|
60
|
-
*/
|
|
61
|
-
export function findBestMatchingScope(url, scopes) {
|
|
62
|
-
let bestMatch = null;
|
|
63
|
-
let maxDepth = -1;
|
|
64
|
-
for (const scope of scopes) {
|
|
65
|
-
if (url.hostname !== scope.hostname) {
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
const isMatch = isPathMatch(url.paths, scope.paths);
|
|
69
|
-
if (isMatch && scope.depth > maxDepth) {
|
|
70
|
-
bestMatch = scope;
|
|
71
|
-
maxDepth = scope.depth;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return bestMatch;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Check whether a target path is equal to or is a descendant of a base path.
|
|
78
|
-
*
|
|
79
|
-
* Compares path segments element by element. The target path matches if
|
|
80
|
-
* all segments of the base path appear in the same positions at the
|
|
81
|
-
* beginning of the target path.
|
|
82
|
-
* @param targetPaths - The path segments of the URL being checked.
|
|
83
|
-
* @param basePaths - The path segments of the scope URL to match against.
|
|
84
|
-
* @returns `true` if the target path starts with or equals the base path.
|
|
85
|
-
*/
|
|
86
|
-
function isPathMatch(targetPaths, basePaths) {
|
|
87
|
-
if (targetPaths.length < basePaths.length) {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
for (const [i, basePath] of basePaths.entries()) {
|
|
91
|
-
if (targetPaths[i] !== basePath) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Check whether a URL is in a lower layer (subdirectory) of any scope URL.
|
|
99
|
-
*
|
|
100
|
-
* Tests the URL against each scope URL using the `isLowerLayer` utility,
|
|
101
|
-
* which checks if the URL's path is at the same level or deeper than
|
|
102
|
-
* the scope URL's path.
|
|
103
|
-
* @param url - The parsed URL to check.
|
|
104
|
-
* @param scopes - The list of scope URLs to test against.
|
|
105
|
-
* @param options - URL parsing options used for layer comparison.
|
|
106
|
-
* @returns `true` if the URL is in a lower layer of at least one scope URL.
|
|
107
|
-
*/
|
|
108
|
-
export function isInAnyLowerLayer(url, scopes, options) {
|
|
109
|
-
return scopes.some((scope) => isLowerLayer(url.href, scope.href, options));
|
|
110
|
-
}
|
package/lib/index.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module @nitpicker/crawler
|
|
3
|
-
*
|
|
4
|
-
* Core module of Nitpicker that provides the main crawling engine,
|
|
5
|
-
* utility functions, type definitions, and archive storage layer.
|
|
6
|
-
*/
|
|
7
|
-
export * from './utils/index.js';
|
|
8
|
-
export { ArchiveAccessor } from './archive/archive-accessor.js';
|
|
9
|
-
export type { Redirect, Referrer, Anchor, StaticPageData } from './archive/page.js';
|
|
10
|
-
export { default as Page } from './archive/page.js';
|
|
11
|
-
export { default as ArchiveResource } from './archive/resource.js';
|
|
12
|
-
export * from './archive/types.js';
|
|
13
|
-
export { default as Archive } from './archive/archive.js';
|
|
14
|
-
export { DEFAULT_EXCLUDED_EXTERNAL_URLS, CrawlerOrchestrator, } from './crawler-orchestrator.js';
|
|
15
|
-
export * from './types.js';
|
|
16
|
-
export * from './crawler/types.js';
|
package/lib/index.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module @nitpicker/crawler
|
|
3
|
-
*
|
|
4
|
-
* Core module of Nitpicker that provides the main crawling engine,
|
|
5
|
-
* utility functions, type definitions, and archive storage layer.
|
|
6
|
-
*/
|
|
7
|
-
// Types + Utils (旧 @nitpicker/types + utils)
|
|
8
|
-
export * from './utils/index.js';
|
|
9
|
-
// Archive
|
|
10
|
-
export { ArchiveAccessor } from './archive/archive-accessor.js';
|
|
11
|
-
export { default as Page } from './archive/page.js';
|
|
12
|
-
export { default as ArchiveResource } from './archive/resource.js';
|
|
13
|
-
export * from './archive/types.js';
|
|
14
|
-
export { default as Archive } from './archive/archive.js';
|
|
15
|
-
// Core
|
|
16
|
-
export { DEFAULT_EXCLUDED_EXTERNAL_URLS, CrawlerOrchestrator, } from './crawler-orchestrator.js';
|
|
17
|
-
export * from './types.js';
|
|
18
|
-
export * from './crawler/types.js';
|
package/lib/qzilla.d.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import type { QzillaEvent } from './types.js';
|
|
2
|
-
import type { Config } from './archive/types.js';
|
|
3
|
-
import Archive from './archive/archive.js';
|
|
4
|
-
import { EventEmitter } from './utils/index.js';
|
|
5
|
-
import type { ExURL } from './utils/index.js';
|
|
6
|
-
/**
|
|
7
|
-
* Default list of external URL prefixes excluded from crawling.
|
|
8
|
-
* Includes social media sharing endpoints that are commonly linked
|
|
9
|
-
* but provide no useful crawl data.
|
|
10
|
-
*/
|
|
11
|
-
export declare const DEFAULT_EXCLUDED_EXTERNAL_URLS: string[];
|
|
12
|
-
/**
|
|
13
|
-
* Configuration options for the Qzilla crawler.
|
|
14
|
-
*
|
|
15
|
-
* Extends the archive {@link Config} with additional runtime settings
|
|
16
|
-
* such as working directory, browser executable path, and output options.
|
|
17
|
-
*/
|
|
18
|
-
type QzillaConfig = {
|
|
19
|
-
/** The working directory for output files. Defaults to `process.cwd()`. */
|
|
20
|
-
cwd: string;
|
|
21
|
-
/** Path to a Chromium/Chrome executable for Puppeteer. */
|
|
22
|
-
executablePath: string;
|
|
23
|
-
/** Output file path for the archive. */
|
|
24
|
-
filePath: string;
|
|
25
|
-
/** Whether to capture image resources during crawling. */
|
|
26
|
-
image: boolean;
|
|
27
|
-
/** File-size threshold (in bytes) above which images are excluded. */
|
|
28
|
-
imageFileSizeThreshold: number;
|
|
29
|
-
/** Delay in milliseconds between each page request. */
|
|
30
|
-
interval: number;
|
|
31
|
-
/** Whether the input is a pre-defined URL list (non-recursive mode). */
|
|
32
|
-
list: boolean;
|
|
33
|
-
/** Whether to enable verbose logging output. */
|
|
34
|
-
verbose: boolean;
|
|
35
|
-
} & Config;
|
|
36
|
-
/**
|
|
37
|
-
* Callback invoked after the Qzilla instance is fully initialized
|
|
38
|
-
* but before crawling begins.
|
|
39
|
-
* @param qzilla - The initialized Qzilla instance.
|
|
40
|
-
* @param config - The resolved archive configuration.
|
|
41
|
-
*/
|
|
42
|
-
type QzillaInitializedCallback = (qzilla: Qzilla, config: Config) => void | Promise<void>;
|
|
43
|
-
/**
|
|
44
|
-
* The main entry point for Qzilla web crawling and archiving.
|
|
45
|
-
*
|
|
46
|
-
* Qzilla orchestrates the full lifecycle of a crawl session: it creates an archive,
|
|
47
|
-
* configures a {@link Crawler}, processes discovered pages and resources, and
|
|
48
|
-
* writes the final archive file. It emits events defined by {@link QzillaEvent}.
|
|
49
|
-
*
|
|
50
|
-
* Instances are created via the static factory methods {@link Qzilla.crawling}
|
|
51
|
-
* or {@link Qzilla.resume}; the constructor is private.
|
|
52
|
-
* @example
|
|
53
|
-
* ```ts
|
|
54
|
-
* const qzilla = await Qzilla.crawling(['https://example.com'], { recursive: true });
|
|
55
|
-
* await qzilla.write();
|
|
56
|
-
* ```
|
|
57
|
-
*/
|
|
58
|
-
export declare class Qzilla extends EventEmitter<QzillaEvent> {
|
|
59
|
-
#private;
|
|
60
|
-
/**
|
|
61
|
-
* The underlying archive instance used for storing crawl results.
|
|
62
|
-
*/
|
|
63
|
-
get archive(): Archive;
|
|
64
|
-
private constructor();
|
|
65
|
-
/**
|
|
66
|
-
* Abort the current crawl and archive operations.
|
|
67
|
-
*
|
|
68
|
-
* Delegates to the archive's abort method, which stops all in-progress
|
|
69
|
-
* database writes and cleans up temporary resources.
|
|
70
|
-
* @returns The result of the archive abort operation.
|
|
71
|
-
*/
|
|
72
|
-
abort(): void;
|
|
73
|
-
/**
|
|
74
|
-
* Execute the crawl for the given list of URLs.
|
|
75
|
-
*
|
|
76
|
-
* Sets up event listeners on the crawler, starts crawling, and resolves
|
|
77
|
-
* when the crawl completes. Discovered pages, external pages, skipped pages,
|
|
78
|
-
* and resources are forwarded to the archive for storage.
|
|
79
|
-
* @param list - The list of parsed URLs to crawl. The first URL is used as the root.
|
|
80
|
-
* @returns A promise that resolves when crawling is complete.
|
|
81
|
-
* @throws {Error} If the URL list is empty.
|
|
82
|
-
*/
|
|
83
|
-
crawling(list: ExURL[]): Promise<void>;
|
|
84
|
-
/**
|
|
85
|
-
* Kill any zombie Chromium processes that were not properly cleaned up.
|
|
86
|
-
*
|
|
87
|
-
* Retrieves the list of undead process IDs from the crawler and sends
|
|
88
|
-
* a SIGTERM signal to each one. Chromium is intentionally sent SIGTERM
|
|
89
|
-
* (not SIGKILL) to avoid leaving zombie processes.
|
|
90
|
-
*/
|
|
91
|
-
garbageCollect(): void;
|
|
92
|
-
/**
|
|
93
|
-
* Retrieve the list of process IDs for Chromium instances that are
|
|
94
|
-
* still running after crawling has ended.
|
|
95
|
-
* @returns An array of process IDs that should be terminated.
|
|
96
|
-
*/
|
|
97
|
-
getUndeadPid(): never[];
|
|
98
|
-
/**
|
|
99
|
-
* Write the archive to its configured file path.
|
|
100
|
-
*
|
|
101
|
-
* Emits `writeFileStart` before writing and `writeFileEnd` after
|
|
102
|
-
* the write completes successfully.
|
|
103
|
-
*/
|
|
104
|
-
write(): Promise<void>;
|
|
105
|
-
/**
|
|
106
|
-
* Create a new Qzilla instance and start crawling the given URLs.
|
|
107
|
-
*
|
|
108
|
-
* This is the primary factory method for starting a fresh crawl. It:
|
|
109
|
-
* 1. Parses and sorts the input URLs
|
|
110
|
-
* 2. Creates an archive file
|
|
111
|
-
* 3. Saves the crawl configuration
|
|
112
|
-
* 4. Runs the optional initialized callback
|
|
113
|
-
* 5. Executes the crawl
|
|
114
|
-
* 6. Sorts the archived URLs in natural order
|
|
115
|
-
* @param url - One or more URL strings to crawl.
|
|
116
|
-
* @param options - Optional configuration overrides for the crawl session.
|
|
117
|
-
* @param initializedCallback - Optional callback invoked after initialization but before crawling starts.
|
|
118
|
-
* @returns A promise that resolves to the Qzilla instance after crawling completes.
|
|
119
|
-
* @throws {Error} If the URL list is empty or contains no valid URLs.
|
|
120
|
-
*/
|
|
121
|
-
static crawling(url: string[], options?: Partial<QzillaConfig>, initializedCallback?: QzillaInitializedCallback): Promise<Qzilla>;
|
|
122
|
-
/**
|
|
123
|
-
* Resume a previously interrupted crawl from an existing archive file.
|
|
124
|
-
*
|
|
125
|
-
* Restores the crawl state (pending URLs, scraped URLs, and resources)
|
|
126
|
-
* from the archive, merges any option overrides, and continues crawling
|
|
127
|
-
* from where it left off.
|
|
128
|
-
* @param stubPath - Path to the existing archive file to resume from.
|
|
129
|
-
* @param options - Optional configuration overrides to apply on top of the archived config.
|
|
130
|
-
* @param initializedCallback - Optional callback invoked after initialization but before crawling resumes.
|
|
131
|
-
* @returns A promise that resolves to the Qzilla instance after crawling completes.
|
|
132
|
-
* @throws {Error} If the archived URL is invalid.
|
|
133
|
-
*/
|
|
134
|
-
static resume(stubPath: string, options?: Partial<QzillaConfig>, initializedCallback?: QzillaInitializedCallback): Promise<Qzilla>;
|
|
135
|
-
}
|
|
136
|
-
export {};
|