@crawlee/utils 4.0.0-beta.99 → 4.0.0-rc.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.
- package/README.md +1 -1
- package/index.d.ts +9 -7
- package/index.js +7 -7
- package/internal.d.ts +10 -0
- package/internal.js +9 -0
- package/internals/cheerio.d.ts +1 -2
- package/internals/cheerio.js +5 -3
- package/internals/extract-microdata.d.ts +24 -0
- package/internals/extract-microdata.js +118 -0
- package/internals/extract-urls.d.ts +1 -1
- package/internals/extract-urls.js +17 -15
- package/internals/open_graph_parser.js +1 -1
- package/internals/robots.d.ts +26 -10
- package/internals/robots.js +56 -26
- package/internals/schemas.d.ts +114 -0
- package/internals/schemas.js +115 -0
- package/internals/sitemap.d.ts +11 -2
- package/internals/sitemap.js +98 -61
- package/internals/social.d.ts +1 -1
- package/internals/social.js +2 -2
- package/internals/url.d.ts +69 -0
- package/internals/url.js +119 -0
- package/internals/validation.d.ts +9 -0
- package/internals/validation.js +16 -0
- package/package.json +11 -9
package/internals/url.js
CHANGED
|
@@ -1,3 +1,122 @@
|
|
|
1
|
+
import { getDomain } from 'tldts';
|
|
2
|
+
/**
|
|
3
|
+
* The different enqueueing strategies available.
|
|
4
|
+
*
|
|
5
|
+
* Depending on the strategy you select, we will only check certain parts of the URLs found. Here is a diagram of each URL part and their name:
|
|
6
|
+
*
|
|
7
|
+
* ```md
|
|
8
|
+
* Protocol Domain
|
|
9
|
+
* ┌────┐ ┌─────────┐
|
|
10
|
+
* https://example.crawlee.dev/...
|
|
11
|
+
* │ └─────────────────┤
|
|
12
|
+
* │ Hostname │
|
|
13
|
+
* │ │
|
|
14
|
+
* └─────────────────────────┘
|
|
15
|
+
* Origin
|
|
16
|
+
*```
|
|
17
|
+
*
|
|
18
|
+
* - The `Protocol` is usually `http` or `https`
|
|
19
|
+
* - The `Domain` represents the path without any possible subdomains to a website. For example, `crawlee.dev` is the domain of `https://example.crawlee.dev/`
|
|
20
|
+
* - The `Hostname` is the full path to a website, including any subdomains. For example, `example.crawlee.dev` is the hostname of `https://example.crawlee.dev/`
|
|
21
|
+
* - The `Origin` is the combination of the `Protocol` and `Hostname`. For example, `https://example.crawlee.dev` is the origin of `https://example.crawlee.dev/`
|
|
22
|
+
*/
|
|
23
|
+
export var EnqueueStrategy;
|
|
24
|
+
(function (EnqueueStrategy) {
|
|
25
|
+
/**
|
|
26
|
+
* Matches any URLs found
|
|
27
|
+
*/
|
|
28
|
+
EnqueueStrategy["All"] = "all";
|
|
29
|
+
/**
|
|
30
|
+
* Matches any URLs that have the same hostname.
|
|
31
|
+
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
32
|
+
* `https://example.com/hello` will not be matched.
|
|
33
|
+
*
|
|
34
|
+
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
35
|
+
*/
|
|
36
|
+
EnqueueStrategy["SameHostname"] = "same-hostname";
|
|
37
|
+
/**
|
|
38
|
+
* Matches any URLs that have the same domain as the base URL.
|
|
39
|
+
* For example, `https://wow.an.example.com` and `https://example.com` will both be matched for a base url of
|
|
40
|
+
* `https://example.com`.
|
|
41
|
+
*
|
|
42
|
+
* > This strategy will match both `http` and `https` protocols regardless of the base URL protocol.
|
|
43
|
+
*/
|
|
44
|
+
EnqueueStrategy["SameDomain"] = "same-domain";
|
|
45
|
+
/**
|
|
46
|
+
* Matches any URLs that have the same hostname and protocol.
|
|
47
|
+
* For example, `https://wow.example.com/hello` will be matched for a base url of `https://wow.example.com/`, but
|
|
48
|
+
* `http://wow.example.com/hello` will not be matched.
|
|
49
|
+
*
|
|
50
|
+
* > This strategy will ensure the protocol of the base URL is the same as the protocol of the URL to be enqueued.
|
|
51
|
+
*/
|
|
52
|
+
EnqueueStrategy["SameOrigin"] = "same-origin";
|
|
53
|
+
})(EnqueueStrategy || (EnqueueStrategy = {}));
|
|
54
|
+
/** Reusable suffix for log messages explaining why a non-`http(s)` URL was rejected. */
|
|
55
|
+
export const UNSUPPORTED_SCHEME_MESSAGE = 'unsupported URL scheme (only http and https are allowed)';
|
|
56
|
+
const ALLOWED_SCHEMES = new Set(['http:', 'https:']);
|
|
57
|
+
function toUrl(value) {
|
|
58
|
+
if (value instanceof URL) {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return new URL(value);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Strip a trailing dot so `example.com.` equals `example.com`. */
|
|
69
|
+
function normalizeHostname(hostname) {
|
|
70
|
+
return hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check whether `target` matches `origin` under the given enqueue `strategy`. The URL scheme is not
|
|
74
|
+
* considered here (use {@link filterUrl} for the combined scheme + strategy check).
|
|
75
|
+
*
|
|
76
|
+
* The `enqueueLinks` implementation in `@crawlee/core` matches the same strategies via glob patterns
|
|
77
|
+
* (see `packages/core/src/enqueue_links/enqueue_links.ts`) — keep the two in sync when changing either.
|
|
78
|
+
*/
|
|
79
|
+
export function matchesEnqueueStrategy(strategy, target, origin) {
|
|
80
|
+
switch (strategy) {
|
|
81
|
+
case 'all':
|
|
82
|
+
return true;
|
|
83
|
+
case 'same-hostname':
|
|
84
|
+
return normalizeHostname(target.hostname) === normalizeHostname(origin.hostname);
|
|
85
|
+
case 'same-domain': {
|
|
86
|
+
const originDomain = getDomain(origin.hostname, { mixedInputs: false });
|
|
87
|
+
if (originDomain) {
|
|
88
|
+
return originDomain === getDomain(target.hostname, { mixedInputs: false });
|
|
89
|
+
}
|
|
90
|
+
// No registrable domain (e.g. an IP address), fall back to comparing origins.
|
|
91
|
+
return target.origin === origin.origin;
|
|
92
|
+
}
|
|
93
|
+
case 'same-origin':
|
|
94
|
+
// Compare scheme/host/port directly so a trailing-dot host is normalized.
|
|
95
|
+
return (target.protocol === origin.protocol &&
|
|
96
|
+
normalizeHostname(target.hostname) === normalizeHostname(origin.hostname) &&
|
|
97
|
+
target.port === origin.port);
|
|
98
|
+
default:
|
|
99
|
+
throw new Error(`Unknown enqueue strategy '${strategy}'.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check whether `target` may be enqueued under `strategy` relative to `origin`: it must use an `http(s)`
|
|
104
|
+
* scheme and match the strategy. On rejection, `reason` is a human-readable message for log output.
|
|
105
|
+
*/
|
|
106
|
+
export function filterUrl(target, origin, strategy) {
|
|
107
|
+
const targetUrl = toUrl(target);
|
|
108
|
+
if (targetUrl === null || !ALLOWED_SCHEMES.has(targetUrl.protocol)) {
|
|
109
|
+
return { allowed: false, reason: UNSUPPORTED_SCHEME_MESSAGE };
|
|
110
|
+
}
|
|
111
|
+
const originUrl = toUrl(origin);
|
|
112
|
+
if (originUrl === null) {
|
|
113
|
+
return { allowed: false, reason: 'invalid origin URL' };
|
|
114
|
+
}
|
|
115
|
+
if (!matchesEnqueueStrategy(strategy, targetUrl, originUrl)) {
|
|
116
|
+
return { allowed: false, reason: `does not match enqueue strategy '${strategy}'` };
|
|
117
|
+
}
|
|
118
|
+
return { allowed: true };
|
|
119
|
+
}
|
|
1
120
|
/**
|
|
2
121
|
* Appends search (query string) parameters to a URL, replacing the original value (if any).
|
|
3
122
|
*
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Dictionary } from '@crawlee/types';
|
|
2
|
+
export { ArgumentValidationError, parseArgument } from '@apify/validations';
|
|
3
|
+
/**
|
|
4
|
+
* Rejects options that exist only to configure the browser pool the crawler would have built for itself.
|
|
5
|
+
* Accepting them alongside a pre-built `browserPool` and quietly ignoring them is how `browserPoolOptions` grew
|
|
6
|
+
* into a second, half-working way of configuring the same pool.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function assertBrowserPoolNotConfigured(crawlerName: string, ignoredOptions: Dictionary): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { ArgumentValidationError, parseArgument } from '@apify/validations';
|
|
2
|
+
/**
|
|
3
|
+
* Rejects options that exist only to configure the browser pool the crawler would have built for itself.
|
|
4
|
+
* Accepting them alongside a pre-built `browserPool` and quietly ignoring them is how `browserPoolOptions` grew
|
|
5
|
+
* into a second, half-working way of configuring the same pool.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export function assertBrowserPoolNotConfigured(crawlerName, ignoredOptions) {
|
|
9
|
+
const names = Object.keys(ignoredOptions).filter((name) => ignoredOptions[name] !== undefined);
|
|
10
|
+
if (names.length === 0) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
throw new Error(`${crawlerName}: ${names.map((name) => `\`${name}\``).join(', ')} cannot be combined with \`browserPool\`, ` +
|
|
14
|
+
`${names.length > 1 ? 'they configure' : 'it configures'} the browser pool the crawler would build for ` +
|
|
15
|
+
'itself. Configure the pool you pass in instead.');
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/utils",
|
|
3
|
-
"version": "4.0.0-
|
|
3
|
+
"version": "4.0.0-rc.1",
|
|
4
4
|
"description": "A set of shared utilities that can be used by crawlers",
|
|
5
5
|
"engines": {
|
|
6
|
-
"node": ">=22.
|
|
6
|
+
"node": ">=22.13.0"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./index.js",
|
|
11
|
+
"./internal": "./internal.js",
|
|
11
12
|
"./package.json": "./package.json"
|
|
12
13
|
},
|
|
13
14
|
"keywords": [
|
|
@@ -41,18 +42,19 @@
|
|
|
41
42
|
"copy": "tsx ../../scripts/copy.ts"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@apify/
|
|
45
|
-
"@crawlee/http-client": "4.0.0-
|
|
46
|
-
"@crawlee/types": "4.0.0-
|
|
45
|
+
"@apify/validations": "^1.1.0",
|
|
46
|
+
"@crawlee/http-client": "4.0.0-rc.1",
|
|
47
|
+
"@crawlee/types": "4.0.0-rc.1",
|
|
47
48
|
"@types/sax": "^1.2.7",
|
|
48
49
|
"cheerio": "^1.0.0",
|
|
49
|
-
"domhandler": "^
|
|
50
|
+
"domhandler": "^6.0.0",
|
|
50
51
|
"file-type": "^21.0.0",
|
|
51
|
-
"ow": "^2.0.0",
|
|
52
52
|
"robots-parser": "^3.0.1",
|
|
53
53
|
"sax": "^1.4.1",
|
|
54
|
+
"tldts": "^7.0.6",
|
|
54
55
|
"tslib": "^2.8.1",
|
|
55
|
-
"whatwg-mimetype": "^
|
|
56
|
+
"whatwg-mimetype": "^5.0.0",
|
|
57
|
+
"zod": "^4.5.4"
|
|
56
58
|
},
|
|
57
59
|
"lerna": {
|
|
58
60
|
"command": {
|
|
@@ -61,5 +63,5 @@
|
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
},
|
|
64
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "f354ca5e943bed1a5c657a1fce1974c053f9fff0"
|
|
65
67
|
}
|