@crawlee/utils 4.0.0-beta.12 → 4.0.0-beta.121
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 +17 -13
- package/index.d.ts +4 -12
- package/index.js +3 -11
- package/internal.d.ts +7 -0
- package/internal.js +6 -0
- package/internals/blocked.d.ts +0 -1
- package/internals/blocked.js +0 -1
- package/internals/cheerio.d.ts +3 -2
- package/internals/cheerio.js +4 -5
- package/internals/extract-urls.d.ts +5 -1
- package/internals/extract-urls.js +8 -5
- package/internals/general.d.ts +0 -25
- package/internals/general.js +2 -110
- package/internals/iterables.d.ts +47 -0
- package/internals/iterables.js +96 -0
- package/internals/open_graph_parser.d.ts +2 -3
- package/internals/open_graph_parser.js +8 -9
- package/internals/robots.d.ts +19 -7
- package/internals/robots.js +47 -41
- package/internals/sitemap.d.ts +65 -8
- package/internals/sitemap.js +222 -78
- package/internals/social.d.ts +1 -2
- package/internals/social.js +7 -5
- package/internals/url.d.ts +1 -2
- package/internals/url.js +1 -2
- package/package.json +7 -6
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/internals/blocked.d.ts.map +0 -1
- package/internals/blocked.js.map +0 -1
- package/internals/cheerio.d.ts.map +0 -1
- package/internals/cheerio.js.map +0 -1
- package/internals/chunk.d.ts +0 -2
- package/internals/chunk.d.ts.map +0 -1
- package/internals/chunk.js +0 -40
- package/internals/chunk.js.map +0 -1
- package/internals/debug.d.ts +0 -31
- package/internals/debug.d.ts.map +0 -1
- package/internals/debug.js +0 -29
- package/internals/debug.js.map +0 -1
- package/internals/extract-urls.d.ts.map +0 -1
- package/internals/extract-urls.js.map +0 -1
- package/internals/general.d.ts.map +0 -1
- package/internals/general.js.map +0 -1
- package/internals/open_graph_parser.d.ts.map +0 -1
- package/internals/open_graph_parser.js.map +0 -1
- package/internals/robots.d.ts.map +0 -1
- package/internals/robots.js.map +0 -1
- package/internals/sitemap.d.ts.map +0 -1
- package/internals/sitemap.js.map +0 -1
- package/internals/social.d.ts.map +0 -1
- package/internals/social.js.map +0 -1
- package/internals/system-info/cpu-info.d.ts +0 -64
- package/internals/system-info/cpu-info.d.ts.map +0 -1
- package/internals/system-info/cpu-info.js +0 -211
- package/internals/system-info/cpu-info.js.map +0 -1
- package/internals/system-info/memory-info.d.ts +0 -28
- package/internals/system-info/memory-info.d.ts.map +0 -1
- package/internals/system-info/memory-info.js +0 -118
- package/internals/system-info/memory-info.js.map +0 -1
- package/internals/system-info/ps-tree.d.ts +0 -18
- package/internals/system-info/ps-tree.d.ts.map +0 -1
- package/internals/system-info/ps-tree.js +0 -145
- package/internals/system-info/ps-tree.js.map +0 -1
- package/internals/typedefs.d.ts +0 -5
- package/internals/typedefs.d.ts.map +0 -1
- package/internals/typedefs.js +0 -9
- package/internals/typedefs.js.map +0 -1
- package/internals/url.d.ts.map +0 -1
- package/internals/url.js.map +0 -1
- package/tsconfig.build.tsbuildinfo +0 -1
package/internals/sitemap.js
CHANGED
|
@@ -2,29 +2,30 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { PassThrough, pipeline, Readable, Transform } from 'node:stream';
|
|
3
3
|
import { StringDecoder } from 'node:string_decoder';
|
|
4
4
|
import { createGunzip } from 'node:zlib';
|
|
5
|
-
import
|
|
5
|
+
import { FetchHttpClient } from '@crawlee/http-client';
|
|
6
6
|
import MIMEType from 'whatwg-mimetype';
|
|
7
|
-
import
|
|
7
|
+
import { mergeAsyncIterables } from './iterables.js';
|
|
8
|
+
import { RobotsTxtFile } from './robots.js';
|
|
8
9
|
class SitemapTxtParser extends Transform {
|
|
9
|
-
decoder = new StringDecoder('utf8');
|
|
10
|
-
buffer = '';
|
|
10
|
+
#decoder = new StringDecoder('utf8');
|
|
11
|
+
#buffer = '';
|
|
11
12
|
constructor() {
|
|
12
13
|
super({
|
|
13
14
|
readableObjectMode: true,
|
|
14
15
|
transform: (chunk, _encoding, callback) => {
|
|
15
|
-
this.processBuffer(this
|
|
16
|
+
this.processBuffer(this.#decoder.write(chunk), false);
|
|
16
17
|
callback();
|
|
17
18
|
},
|
|
18
19
|
flush: (callback) => {
|
|
19
|
-
this.processBuffer(this
|
|
20
|
+
this.processBuffer(this.#decoder.end(), true);
|
|
20
21
|
callback();
|
|
21
22
|
},
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
processBuffer(input, finalize) {
|
|
25
|
-
this
|
|
26
|
-
if (finalize || this
|
|
27
|
-
const parts = this
|
|
26
|
+
this.#buffer += input;
|
|
27
|
+
if (finalize || this.#buffer.includes('\n')) {
|
|
28
|
+
const parts = this.#buffer
|
|
28
29
|
.split('\n')
|
|
29
30
|
.map((part) => part.trim())
|
|
30
31
|
.filter((part) => part.length > 0);
|
|
@@ -32,101 +33,109 @@ class SitemapTxtParser extends Transform {
|
|
|
32
33
|
for (const url of parts) {
|
|
33
34
|
this.push({ type: 'url', loc: url });
|
|
34
35
|
}
|
|
35
|
-
this
|
|
36
|
+
this.#buffer = '';
|
|
36
37
|
}
|
|
37
38
|
else if (parts.length > 0) {
|
|
38
39
|
for (const url of parts.slice(0, -1)) {
|
|
39
40
|
this.push({ type: 'url', loc: url });
|
|
40
41
|
}
|
|
41
|
-
this
|
|
42
|
+
this.#buffer = parts.at(-1);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
class SitemapXmlParser extends Transform {
|
|
47
|
-
decoder = new StringDecoder('utf8');
|
|
48
|
-
parser
|
|
49
|
-
rootTagName;
|
|
50
|
-
currentTag = undefined;
|
|
51
|
-
url = {};
|
|
52
|
-
|
|
48
|
+
#decoder = new StringDecoder('utf8');
|
|
49
|
+
#parser;
|
|
50
|
+
#rootTagName;
|
|
51
|
+
#currentTag = undefined;
|
|
52
|
+
#url = {};
|
|
53
|
+
static async create() {
|
|
54
|
+
const { SAXParser } = await import('sax');
|
|
55
|
+
return new SitemapXmlParser(new SAXParser(true));
|
|
56
|
+
}
|
|
57
|
+
constructor(parser) {
|
|
53
58
|
super({
|
|
54
59
|
readableObjectMode: true,
|
|
55
60
|
transform: (chunk, _encoding, callback) => {
|
|
56
|
-
this
|
|
61
|
+
this.#parser.write(this.#decoder.write(chunk));
|
|
57
62
|
callback();
|
|
58
63
|
},
|
|
59
64
|
flush: (callback) => {
|
|
60
|
-
const rest = this
|
|
65
|
+
const rest = this.#decoder.end();
|
|
61
66
|
if (rest.length > 0) {
|
|
62
|
-
this
|
|
67
|
+
this.#parser.write(rest);
|
|
63
68
|
}
|
|
64
|
-
this
|
|
69
|
+
this.#parser.end();
|
|
65
70
|
callback();
|
|
66
71
|
},
|
|
67
72
|
});
|
|
68
|
-
this
|
|
69
|
-
this
|
|
70
|
-
this
|
|
71
|
-
this
|
|
72
|
-
this
|
|
73
|
+
this.#parser = parser;
|
|
74
|
+
this.#parser.onopentag = this.onOpenTag.bind(this);
|
|
75
|
+
this.#parser.onclosetag = this.onCloseTag.bind(this);
|
|
76
|
+
this.#parser.ontext = this.onText.bind(this);
|
|
77
|
+
this.#parser.oncdata = this.onText.bind(this);
|
|
78
|
+
this.#parser.onerror = this.destroy.bind(this);
|
|
73
79
|
}
|
|
74
80
|
onOpenTag(node) {
|
|
75
|
-
if (this
|
|
81
|
+
if (this.#rootTagName !== undefined) {
|
|
76
82
|
if (node.name === 'loc' ||
|
|
77
83
|
node.name === 'lastmod' ||
|
|
78
84
|
node.name === 'priority' ||
|
|
79
85
|
node.name === 'changefreq') {
|
|
80
|
-
this
|
|
86
|
+
this.#currentTag = node.name;
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
89
|
if (node.name === 'urlset') {
|
|
84
|
-
this
|
|
90
|
+
this.#rootTagName = 'urlset';
|
|
85
91
|
}
|
|
86
92
|
if (node.name === 'sitemapindex') {
|
|
87
|
-
this
|
|
93
|
+
this.#rootTagName = 'sitemapindex';
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
onCloseTag(name) {
|
|
91
97
|
if (name === 'loc' || name === 'lastmod' || name === 'priority' || name === 'changefreq') {
|
|
92
|
-
this
|
|
98
|
+
this.#currentTag = undefined;
|
|
93
99
|
}
|
|
94
|
-
if (name === 'url'
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
if (name === 'url') {
|
|
101
|
+
if (this.#url.loc !== undefined) {
|
|
102
|
+
this.push({ type: 'url', ...this.#url, loc: this.#url.loc });
|
|
103
|
+
}
|
|
104
|
+
this.#url = {};
|
|
97
105
|
}
|
|
98
106
|
}
|
|
99
107
|
onText(text) {
|
|
100
|
-
if (this
|
|
101
|
-
if (this
|
|
108
|
+
if (this.#currentTag === 'loc') {
|
|
109
|
+
if (this.#rootTagName === 'sitemapindex') {
|
|
102
110
|
this.push({ type: 'sitemapUrl', url: text.trim() });
|
|
103
111
|
}
|
|
104
|
-
if (this
|
|
105
|
-
this
|
|
106
|
-
this
|
|
112
|
+
if (this.#rootTagName === 'urlset') {
|
|
113
|
+
this.#url ??= {};
|
|
114
|
+
this.#url.loc = text.trim();
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
117
|
text = text.trim();
|
|
110
|
-
if (this
|
|
111
|
-
|
|
118
|
+
if (this.#currentTag === 'lastmod') {
|
|
119
|
+
const lastmod = new Date(text);
|
|
120
|
+
if (!Number.isNaN(lastmod.getTime())) {
|
|
121
|
+
this.#url.lastmod = lastmod;
|
|
122
|
+
}
|
|
112
123
|
}
|
|
113
|
-
if (this
|
|
114
|
-
this
|
|
124
|
+
if (this.#currentTag === 'priority') {
|
|
125
|
+
this.#url.priority = Number(text);
|
|
115
126
|
}
|
|
116
|
-
if (this
|
|
127
|
+
if (this.#currentTag === 'changefreq') {
|
|
117
128
|
if (['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'].includes(text)) {
|
|
118
|
-
this
|
|
129
|
+
this.#url.changefreq = text;
|
|
119
130
|
}
|
|
120
131
|
}
|
|
121
132
|
}
|
|
122
133
|
}
|
|
123
134
|
export async function* parseSitemap(initialSources, proxyUrl, options) {
|
|
124
|
-
const {
|
|
125
|
-
const { fileTypeStream } = await import('file-type');
|
|
126
|
-
const { emitNestedSitemaps = false, maxDepth = Infinity, sitemapRetries = 3, networkTimeouts } = options ?? {};
|
|
135
|
+
const { httpClient = new FetchHttpClient(), emitNestedSitemaps = false, maxDepth = Infinity, sitemapRetries = 3, timeoutMillis: timeout = 30000, reportNetworkErrors = true, nestedSitemapFilter, logger, } = options ?? {};
|
|
127
136
|
const sources = [...initialSources];
|
|
128
137
|
const visitedSitemapUrls = new Set();
|
|
129
|
-
const createParser = (contentType = '', url) => {
|
|
138
|
+
const createParser = async (contentType = '', url) => {
|
|
130
139
|
let mimeType;
|
|
131
140
|
try {
|
|
132
141
|
mimeType = new MIMEType(contentType);
|
|
@@ -135,7 +144,7 @@ export async function* parseSitemap(initialSources, proxyUrl, options) {
|
|
|
135
144
|
mimeType = null;
|
|
136
145
|
}
|
|
137
146
|
if (mimeType?.isXML() || url?.pathname.endsWith('.xml')) {
|
|
138
|
-
return
|
|
147
|
+
return SitemapXmlParser.create();
|
|
139
148
|
}
|
|
140
149
|
if (mimeType?.essence === 'text/plain' || url?.pathname.endsWith('.txt')) {
|
|
141
150
|
return new SitemapTxtParser();
|
|
@@ -145,7 +154,6 @@ export async function* parseSitemap(initialSources, proxyUrl, options) {
|
|
|
145
154
|
while (sources.length > 0) {
|
|
146
155
|
const source = sources.shift();
|
|
147
156
|
if ((source?.depth ?? 0) > maxDepth) {
|
|
148
|
-
log.debug(`Skipping sitemap ${source.type === 'url' ? source.url : ''} because it reached max depth ${maxDepth}.`);
|
|
149
157
|
continue;
|
|
150
158
|
}
|
|
151
159
|
let items = null;
|
|
@@ -155,23 +163,29 @@ export async function* parseSitemap(initialSources, proxyUrl, options) {
|
|
|
155
163
|
let retriesLeft = sitemapRetries + 1;
|
|
156
164
|
while (retriesLeft-- > 0) {
|
|
157
165
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
proxyUrl,
|
|
166
|
+
let sitemapResponse;
|
|
167
|
+
try {
|
|
168
|
+
sitemapResponse = await httpClient.sendRequest(new Request(sitemapUrl, {
|
|
162
169
|
method: 'GET',
|
|
163
|
-
timeout: networkTimeouts,
|
|
164
170
|
headers: {
|
|
165
|
-
accept: '
|
|
171
|
+
accept: '*/*',
|
|
166
172
|
},
|
|
173
|
+
}), {
|
|
174
|
+
proxyUrl,
|
|
175
|
+
timeoutMillis: timeout,
|
|
167
176
|
});
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
sitemapResponse = null;
|
|
180
|
+
}
|
|
171
181
|
let error = null;
|
|
172
|
-
if (
|
|
173
|
-
let contentType =
|
|
174
|
-
|
|
182
|
+
if (sitemapResponse && sitemapResponse.status >= 200 && sitemapResponse.status < 300) {
|
|
183
|
+
let contentType = sitemapResponse.headers.get('content-type');
|
|
184
|
+
if (sitemapResponse.body === null) {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
const { fileTypeStream } = await import('file-type');
|
|
188
|
+
const streamWithType = await fileTypeStream(Readable.fromWeb(sitemapResponse.body));
|
|
175
189
|
if (streamWithType.fileType !== undefined) {
|
|
176
190
|
contentType = streamWithType.fileType.mime;
|
|
177
191
|
}
|
|
@@ -184,29 +198,37 @@ export async function* parseSitemap(initialSources, proxyUrl, options) {
|
|
|
184
198
|
sitemapUrl.pathname = sitemapUrl.pathname.substring(0, sitemapUrl.pathname.length - 3);
|
|
185
199
|
}
|
|
186
200
|
}
|
|
187
|
-
items = pipeline(streamWithType, isGzipped ? createGunzip() : new PassThrough(), createParser(contentType, sitemapUrl), (e) => {
|
|
188
|
-
if (e !== undefined) {
|
|
189
|
-
error = e;
|
|
201
|
+
items = pipeline(streamWithType, isGzipped ? createGunzip() : new PassThrough(), await createParser(contentType ?? undefined, sitemapUrl), (e) => {
|
|
202
|
+
if (e !== undefined && e !== null) {
|
|
203
|
+
error = { type: 'parser', error: e };
|
|
190
204
|
}
|
|
191
205
|
});
|
|
192
206
|
}
|
|
193
207
|
else {
|
|
194
|
-
error =
|
|
208
|
+
error = {
|
|
209
|
+
type: 'fetch',
|
|
210
|
+
error: new Error(`Failed to fetch sitemap: ${sitemapUrl}, status code: ${sitemapResponse?.status}`),
|
|
211
|
+
};
|
|
195
212
|
}
|
|
196
213
|
if (error !== null) {
|
|
197
|
-
|
|
214
|
+
const shouldIgnoreError = error.type === 'fetch' && !reportNetworkErrors;
|
|
215
|
+
if (!shouldIgnoreError) {
|
|
216
|
+
throw error.error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
break;
|
|
198
221
|
}
|
|
199
|
-
break;
|
|
200
222
|
}
|
|
201
223
|
catch (e) {
|
|
202
|
-
|
|
224
|
+
logger?.warning(`Malformed sitemap content: ${sitemapUrl}, ${retriesLeft === 0 ? 'no retries left.' : 'retrying...'} (${e})`);
|
|
203
225
|
}
|
|
204
226
|
}
|
|
205
227
|
}
|
|
206
228
|
else if (source.type === 'raw') {
|
|
207
|
-
items = pipeline(Readable.from([source.content]), createParser('text/xml'), (error) => {
|
|
229
|
+
items = pipeline(Readable.from([source.content]), await createParser('text/xml'), (error) => {
|
|
208
230
|
if (error !== undefined) {
|
|
209
|
-
|
|
231
|
+
logger?.warning(`Malformed sitemap content: ${error}`);
|
|
210
232
|
}
|
|
211
233
|
});
|
|
212
234
|
}
|
|
@@ -215,6 +237,10 @@ export async function* parseSitemap(initialSources, proxyUrl, options) {
|
|
|
215
237
|
}
|
|
216
238
|
for await (const item of items) {
|
|
217
239
|
if (item.type === 'sitemapUrl' && !visitedSitemapUrls.has(item.url)) {
|
|
240
|
+
if (nestedSitemapFilter && !nestedSitemapFilter(item.url)) {
|
|
241
|
+
logger?.debug(`Skipping sitemap ${item.url} due to nestedSitemapFilter.`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
218
244
|
sources.push({ type: 'url', url: item.url, depth: (source.depth ?? 0) + 1 });
|
|
219
245
|
if (emitNestedSitemaps) {
|
|
220
246
|
yield { loc: item.url, originSitemapUrl: null };
|
|
@@ -254,7 +280,7 @@ export class Sitemap {
|
|
|
254
280
|
* @param url The domain URL to fetch the sitemap for.
|
|
255
281
|
* @param proxyUrl A proxy to be used for fetching the sitemap file.
|
|
256
282
|
*/
|
|
257
|
-
static async tryCommonNames(url, proxyUrl) {
|
|
283
|
+
static async tryCommonNames(url, proxyUrl, parseSitemapOptions) {
|
|
258
284
|
const sitemapUrls = [];
|
|
259
285
|
const sitemapUrl = new URL(url);
|
|
260
286
|
sitemapUrl.search = '';
|
|
@@ -262,7 +288,7 @@ export class Sitemap {
|
|
|
262
288
|
sitemapUrls.push(sitemapUrl.toString());
|
|
263
289
|
sitemapUrl.pathname = '/sitemap.txt';
|
|
264
290
|
sitemapUrls.push(sitemapUrl.toString());
|
|
265
|
-
return Sitemap.load(sitemapUrls, proxyUrl);
|
|
291
|
+
return Sitemap.load(sitemapUrls, proxyUrl, { reportNetworkErrors: false, ...parseSitemapOptions });
|
|
266
292
|
}
|
|
267
293
|
/**
|
|
268
294
|
* Fetch sitemap content from given URL or URLs and return URLs of referenced pages.
|
|
@@ -277,8 +303,8 @@ export class Sitemap {
|
|
|
277
303
|
* @param content XML sitemap content
|
|
278
304
|
* @param proxyUrl URL of a proxy to be used for fetching sitemap contents
|
|
279
305
|
*/
|
|
280
|
-
static async fromXmlString(content, proxyUrl) {
|
|
281
|
-
return await this.parse([{ type: 'raw', content }], proxyUrl);
|
|
306
|
+
static async fromXmlString(content, proxyUrl, parseSitemapOptions) {
|
|
307
|
+
return await this.parse([{ type: 'raw', content }], proxyUrl, parseSitemapOptions);
|
|
282
308
|
}
|
|
283
309
|
static async parse(sources, proxyUrl, parseSitemapOptions) {
|
|
284
310
|
const urls = [];
|
|
@@ -287,10 +313,128 @@ export class Sitemap {
|
|
|
287
313
|
urls.push(item.loc);
|
|
288
314
|
}
|
|
289
315
|
}
|
|
290
|
-
catch {
|
|
316
|
+
catch (e) {
|
|
317
|
+
parseSitemapOptions?.logger?.warning(`Sitemap.load: Failed to load sitemap, returning empty result. (${e})`);
|
|
291
318
|
return new Sitemap([]);
|
|
292
319
|
}
|
|
293
320
|
return new Sitemap(urls);
|
|
294
321
|
}
|
|
295
322
|
}
|
|
296
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Given a list of URLs, discover related sitemap files for these domains by checking the `robots.txt` file,
|
|
325
|
+
* the default `sitemap.xml` & `sitemap.txt` files and the URLs themselves.
|
|
326
|
+
* @param `urls` The list of URLs to discover sitemaps for.
|
|
327
|
+
* @param `options` Options for sitemap discovery
|
|
328
|
+
* @returns An async iterable with the discovered sitemap URLs.
|
|
329
|
+
*/
|
|
330
|
+
export async function* discoverValidSitemaps(urls, options = {}) {
|
|
331
|
+
const { proxyUrl, timeoutMillis = 60_000, signal: externalSignal, requestTimeoutMillis = 20_000, httpClient = new FetchHttpClient(), logger, } = options;
|
|
332
|
+
const controller = new AbortController();
|
|
333
|
+
const timeoutHandle = setTimeout(() => controller.abort(), timeoutMillis);
|
|
334
|
+
const onExternalAbort = () => controller.abort();
|
|
335
|
+
if (externalSignal) {
|
|
336
|
+
if (externalSignal.aborted) {
|
|
337
|
+
controller.abort();
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
externalSignal.addEventListener('abort', onExternalAbort, { once: true });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const signal = controller.signal;
|
|
344
|
+
const sitemapUrls = new Set();
|
|
345
|
+
const addSitemapUrl = (url) => {
|
|
346
|
+
const sizeBefore = sitemapUrls.size;
|
|
347
|
+
sitemapUrls.add(url);
|
|
348
|
+
if (sitemapUrls.size > sizeBefore) {
|
|
349
|
+
return url;
|
|
350
|
+
}
|
|
351
|
+
return undefined;
|
|
352
|
+
};
|
|
353
|
+
const urlExists = async (url) => {
|
|
354
|
+
if (!httpClient) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
const response = await httpClient.sendRequest(new Request(url, { method: 'HEAD' }), {
|
|
359
|
+
proxyUrl,
|
|
360
|
+
timeoutMillis: requestTimeoutMillis,
|
|
361
|
+
signal,
|
|
362
|
+
});
|
|
363
|
+
return response.status >= 200 && response.status < 400;
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
const discoverSitemapsForDomainUrls = async function* (hostname, domainUrls) {
|
|
370
|
+
if (!hostname) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const robotsFile = await RobotsTxtFile.find(domainUrls[0], {
|
|
375
|
+
proxyUrl,
|
|
376
|
+
timeoutMillis: requestTimeoutMillis,
|
|
377
|
+
signal,
|
|
378
|
+
httpClient,
|
|
379
|
+
logger,
|
|
380
|
+
});
|
|
381
|
+
for (const sitemapUrl of robotsFile.getSitemaps()) {
|
|
382
|
+
if (addSitemapUrl(sitemapUrl)) {
|
|
383
|
+
yield sitemapUrl;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
logger?.warning(`Failed to fetch robots.txt file for ${hostname}`, { error: err });
|
|
389
|
+
}
|
|
390
|
+
const sitemapUrl = domainUrls.find((url) => /sitemap\.(?:xml|txt)(?:\.gz)?$/i.test(url));
|
|
391
|
+
if (sitemapUrl !== undefined) {
|
|
392
|
+
if (addSitemapUrl(sitemapUrl)) {
|
|
393
|
+
yield sitemapUrl;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
const firstUrl = new URL(domainUrls[0]);
|
|
398
|
+
const possibleSitemapPathnames = ['/sitemap.xml', '/sitemap.txt', '/sitemap_index.xml'];
|
|
399
|
+
const candidateSitemapUrls = possibleSitemapPathnames.map((pathname) => {
|
|
400
|
+
firstUrl.pathname = pathname;
|
|
401
|
+
return firstUrl.toString();
|
|
402
|
+
});
|
|
403
|
+
const candidateResults = await Promise.allSettled(candidateSitemapUrls.map(urlExists));
|
|
404
|
+
for (const [index, result] of candidateResults.entries()) {
|
|
405
|
+
const candidateSitemapUrl = candidateSitemapUrls[index];
|
|
406
|
+
if (result.status === 'fulfilled') {
|
|
407
|
+
if (result.value && addSitemapUrl(candidateSitemapUrl)) {
|
|
408
|
+
yield candidateSitemapUrl;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
logger?.debug(`Failed to check sitemap candidate ${candidateSitemapUrl} for ${hostname}`, {
|
|
413
|
+
error: result.reason,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const groupedUrls = urls.reduce((acc, url) => {
|
|
420
|
+
const hostname = new URL(url)?.hostname ?? '';
|
|
421
|
+
acc[hostname] ??= [];
|
|
422
|
+
acc[hostname].push(url);
|
|
423
|
+
return acc;
|
|
424
|
+
}, {});
|
|
425
|
+
const iterables = Object.entries(groupedUrls).map(([hostname, domainUrls]) => discoverSitemapsForDomainUrls(hostname, domainUrls));
|
|
426
|
+
const discoveredUrls = new Set();
|
|
427
|
+
try {
|
|
428
|
+
for await (const url of mergeAsyncIterables(...iterables)) {
|
|
429
|
+
if (discoveredUrls.has(url)) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
discoveredUrls.add(url);
|
|
433
|
+
yield url;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
finally {
|
|
437
|
+
clearTimeout(timeoutHandle);
|
|
438
|
+
externalSignal?.removeEventListener('abort', onExternalAbort);
|
|
439
|
+
}
|
|
440
|
+
}
|
package/internals/social.d.ts
CHANGED
|
@@ -488,5 +488,4 @@ export declare const DISCORD_REGEX_GLOBAL: RegExp;
|
|
|
488
488
|
* so that the caller doesn't need to parse the HTML document again, if needed.
|
|
489
489
|
* @return An object with the social handles.
|
|
490
490
|
*/
|
|
491
|
-
export declare function parseHandlesFromHtml(html: string, data?: Record<string, unknown> | null): SocialHandles
|
|
492
|
-
//# sourceMappingURL=social.d.ts.map
|
|
491
|
+
export declare function parseHandlesFromHtml(html: string, data?: Record<string, unknown> | null): Promise<SocialHandles>;
|
package/internals/social.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import * as cheerio from 'cheerio';
|
|
2
1
|
import { htmlToText } from './cheerio.js';
|
|
3
2
|
// Regex inspired by https://zapier.com/blog/extract-links-email-phone-regex/
|
|
4
|
-
|
|
3
|
+
// The dot-atom local part and domain labels use RFC 5321 length bounds ({1,64}, {0,62})
|
|
4
|
+
// instead of unbounded quantifiers to avoid quadratic backtracking (ReDoS) on long
|
|
5
|
+
// dotted or hyphenated inputs, e.g. text scraped by parseHandlesFromHtml().
|
|
6
|
+
const EMAIL_REGEX_STRING = '(?:[a-z0-9!#$%&\'*+/=?^_`{|}~-]{1,64}(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]{1,64}){0,32}|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\\])';
|
|
5
7
|
/**
|
|
6
8
|
* Regular expression to exactly match a single email address.
|
|
7
9
|
* It has the following form: `/^...$/i`.
|
|
@@ -587,7 +589,8 @@ export const DISCORD_REGEX_GLOBAL = new RegExp(DISCORD_REGEX_STRING, 'ig');
|
|
|
587
589
|
* so that the caller doesn't need to parse the HTML document again, if needed.
|
|
588
590
|
* @return An object with the social handles.
|
|
589
591
|
*/
|
|
590
|
-
export function parseHandlesFromHtml(html, data = null) {
|
|
592
|
+
export async function parseHandlesFromHtml(html, data = null) {
|
|
593
|
+
const cheerio = await import('cheerio');
|
|
591
594
|
const result = {
|
|
592
595
|
emails: [],
|
|
593
596
|
phones: [],
|
|
@@ -606,7 +609,7 @@ export function parseHandlesFromHtml(html, data = null) {
|
|
|
606
609
|
const $ = cheerio.load(html, { xml: { decodeEntities: true } });
|
|
607
610
|
if (data)
|
|
608
611
|
data.$ = $;
|
|
609
|
-
const text = htmlToText($);
|
|
612
|
+
const text = await htmlToText($);
|
|
610
613
|
if (data)
|
|
611
614
|
data.text = text;
|
|
612
615
|
// NOTE: we need to parse each text separately, orherwise we might concatenate unrelated texts
|
|
@@ -644,4 +647,3 @@ export function parseHandlesFromHtml(html, data = null) {
|
|
|
644
647
|
}
|
|
645
648
|
return result;
|
|
646
649
|
}
|
|
647
|
-
//# sourceMappingURL=social.js.map
|
package/internals/url.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import type { SearchParams } from '@crawlee/types';
|
|
2
2
|
/**
|
|
3
3
|
* Appends search (query string) parameters to a URL, replacing the original value (if any).
|
|
4
4
|
*
|
|
@@ -7,4 +7,3 @@ export type SearchParams = string | URLSearchParams | Record<string, string | nu
|
|
|
7
7
|
* @internal
|
|
8
8
|
*/
|
|
9
9
|
export declare function applySearchParams(url: URL, searchParams: SearchParams | undefined): void;
|
|
10
|
-
//# sourceMappingURL=url.d.ts.map
|
package/internals/url.js
CHANGED
|
@@ -19,7 +19,7 @@ export function applySearchParams(url, searchParams) {
|
|
|
19
19
|
}
|
|
20
20
|
else {
|
|
21
21
|
newSearchParams = new URLSearchParams();
|
|
22
|
-
for (const [key, value] of Object.entries(
|
|
22
|
+
for (const [key, value] of Object.entries(searchParams)) {
|
|
23
23
|
if (value === undefined) {
|
|
24
24
|
newSearchParams.delete(key);
|
|
25
25
|
}
|
|
@@ -33,4 +33,3 @@ export function applySearchParams(url, searchParams) {
|
|
|
33
33
|
}
|
|
34
34
|
url.search = newSearchParams.toString();
|
|
35
35
|
}
|
|
36
|
-
//# sourceMappingURL=url.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/utils",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.121",
|
|
4
4
|
"description": "A set of shared utilities that can be used by crawlers",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -8,6 +8,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": [
|
|
@@ -35,19 +36,19 @@
|
|
|
35
36
|
},
|
|
36
37
|
"homepage": "https://crawlee.dev",
|
|
37
38
|
"scripts": {
|
|
38
|
-
"build": "
|
|
39
|
+
"build": "pnpm clean && pnpm compile && pnpm copy",
|
|
39
40
|
"clean": "rimraf ./dist",
|
|
40
41
|
"compile": "tsc -p tsconfig.build.json",
|
|
41
42
|
"copy": "tsx ../../scripts/copy.ts"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@apify/log": "^2.5.18",
|
|
45
45
|
"@apify/ps-tree": "^1.2.0",
|
|
46
|
-
"@crawlee/
|
|
46
|
+
"@crawlee/http-client": "4.0.0-beta.121",
|
|
47
|
+
"@crawlee/types": "4.0.0-beta.121",
|
|
47
48
|
"@types/sax": "^1.2.7",
|
|
48
49
|
"cheerio": "^1.0.0",
|
|
50
|
+
"domhandler": "^5.0.3",
|
|
49
51
|
"file-type": "^21.0.0",
|
|
50
|
-
"got-scraping": "^4.1.1",
|
|
51
52
|
"ow": "^2.0.0",
|
|
52
53
|
"robots-parser": "^3.0.1",
|
|
53
54
|
"sax": "^1.4.1",
|
|
@@ -61,5 +62,5 @@
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
},
|
|
64
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "5027317de626f5ba6de5047ae9341a898258cc5a"
|
|
65
66
|
}
|
package/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AAEnC,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wCAAwC,CAAC;AAEnF,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
|
package/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AAEnC,OAAO,EAAE,oBAAoB,EAAa,MAAM,qCAAqC,CAAC;AACtF,OAAO,EAAE,aAAa,EAAc,MAAM,wCAAwC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"blocked.d.ts","sourceRoot":"","sources":["../../src/internals/blocked.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,8BAA8B,UAA0E,CAAC;AAEtH;;GAEG;AACH,eAAO,MAAM,mBAAmB,UAI/B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,UAM/B,CAAC"}
|
package/internals/blocked.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"blocked.js","sourceRoot":"","sources":["../../src/internals/blocked.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,qEAAqE,CAAC,CAAC;AAEtH;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IAC/B,GAAG,8BAA8B;IACjC,0DAA0D;IAC1D,oCAAoC;CACvC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IAC/B,YAAY;IACZ,cAAc;IACd,6BAA6B;IAC7B,8BAA8B;IAC9B,sBAAsB;CACzB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cheerio.d.ts","sourceRoot":"","sources":["../../src/internals/cheerio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAK1C,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC;AAOrC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,UAAU,CAAC,oBAAoB,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAwC7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,SAAM,EAAE,OAAO,SAAK,GAAG,MAAM,EAAE,CAwB5F"}
|
package/internals/cheerio.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cheerio.js","sourceRoot":"","sources":["../../src/internals/cheerio.ts"],"names":[],"mappings":"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"}
|
package/internals/chunk.d.ts
DELETED
package/internals/chunk.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"chunk.d.ts","sourceRoot":"","sources":["../../src/internals/chunk.ts"],"names":[],"mappings":"AA2BA,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,CAAC,EAAE,EAAE,CAQtE"}
|