@crawlee/basic 4.0.0-beta.149 → 4.0.0-beta.150
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/internals/basic-crawler.js +21 -14
- package/package.json +7 -7
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
-
import { applyRequestTransform, AutoscaledPool, bindMethodsToServiceLocator, BLOCKED_STATUS_CODES, buildEnqueueStrategyPatterns, ConcurrencySystem, Configuration, constructUrlPatternObjects, ContextPipeline, ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, createRequestOptions, createStorageTransaction, Request, CriticalError, currentStorageTransaction, Dataset, EnqueueStrategy, EventManager, EventType, filterRequestOptionsByPatterns, getObjectType, KeyValueStore, log, LogLevel, mergeCookies, MissingSessionError, NavigationSkippedError, NonRetryableError, OwnedOrInjected, PersistentRateLimitError, purgeDefaultStorages, RequestHandlerError, parseRetryAfterHeader, RequestThrottledError, RequestManagerTandem, RequestQueue, RequestState, RetryRequestError, Router, ServiceLocator, serviceLocator, Session, SessionError, SessionPool, Statistics, ThrottlingRequestManager, validateUserData, validators, withDirectStorageAccess, } from '@crawlee/core';
|
|
3
|
+
import { applyRequestTransform, AutoscaledPool, bindMethodsToServiceLocator, BLOCKED_STATUS_CODES, buildEnqueueStrategyPatterns, ConcurrencySystem, Configuration, constructUrlPatternObjects, ContextPipeline, ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, createRequestOptions, createSkippedRequestArgs, createStorageTransaction, Request, CriticalError, currentStorageTransaction, Dataset, EnqueueStrategy, EventManager, EventType, filterRequestOptionsByPatterns, getObjectType, KeyValueStore, log, LogLevel, mergeCookies, MissingSessionError, NavigationSkippedError, NonRetryableError, OwnedOrInjected, PersistentRateLimitError, purgeDefaultStorages, RequestHandlerError, parseRetryAfterHeader, RequestThrottledError, RequestManagerTandem, RequestQueue, RequestState, RetryRequestError, Router, ServiceLocator, serviceLocator, Session, SessionError, SessionPool, Statistics, ThrottlingRequestManager, validateUserData, validators, withDirectStorageAccess, } from '@crawlee/core';
|
|
4
4
|
import { BaseHttpClient, FetchHttpClient } from '@crawlee/http-client';
|
|
5
5
|
import { isAsyncIterable, isIterable, parseArgument, ROTATE_PROXY_ERRORS, schemas } from '@crawlee/utils/internal';
|
|
6
6
|
import { RobotsTxtFile } from '@crawlee/utils';
|
|
@@ -608,7 +608,7 @@ export class BasicCrawler {
|
|
|
608
608
|
request.state = RequestState.SKIPPED;
|
|
609
609
|
request.noRetry = true;
|
|
610
610
|
await this.#handleSkippedRequest({
|
|
611
|
-
|
|
611
|
+
request,
|
|
612
612
|
reason: 'robotsTxt',
|
|
613
613
|
});
|
|
614
614
|
throw new ContextPipelineInterruptedError(`Skipping request ${request.url} as disallowed by robots.txt`);
|
|
@@ -707,7 +707,7 @@ export class BasicCrawler {
|
|
|
707
707
|
this.log.debug(message);
|
|
708
708
|
request.noRetry = true;
|
|
709
709
|
request.state = RequestState.SKIPPED;
|
|
710
|
-
await this.#handleSkippedRequest({
|
|
710
|
+
await this.#handleSkippedRequest({ request, reason: 'redirect' });
|
|
711
711
|
throw new ContextPipelineInterruptedError(message);
|
|
712
712
|
}
|
|
713
713
|
return context;
|
|
@@ -1114,6 +1114,10 @@ export class BasicCrawler {
|
|
|
1114
1114
|
const maxCrawlDepth = this.#maxCrawlDepth;
|
|
1115
1115
|
const validateRequestUserData = this.validateRequestUserData.bind(this);
|
|
1116
1116
|
const allSkipped = [];
|
|
1117
|
+
// A skipped source (which can carry arbitrary userData) is only retained if something reads it -
|
|
1118
|
+
// otherwise the URL alone is enough to build the callback argument and to log with.
|
|
1119
|
+
const hasSkippedRequestCallback = this.#onSkippedRequest !== undefined || options.onSkippedRequest !== undefined;
|
|
1120
|
+
const keepSkippedSource = (source) => (hasSkippedRequestCallback ? source : source.url);
|
|
1117
1121
|
async function* filteredRequests() {
|
|
1118
1122
|
for await (const request of requests) {
|
|
1119
1123
|
const [requestOptions] = createRequestOptions([typeof request === 'string' ? request : request], { ...options, strategy });
|
|
@@ -1121,24 +1125,24 @@ export class BasicCrawler {
|
|
|
1121
1125
|
continue; // invalid URL, silently dropped (matches `createRequestOptions`'s own filtering)
|
|
1122
1126
|
}
|
|
1123
1127
|
if (maxCrawlDepth !== undefined && requestOptions.crawlDepth > maxCrawlDepth) {
|
|
1124
|
-
allSkipped.push({
|
|
1128
|
+
allSkipped.push({ source: keepSkippedSource(requestOptions), reason: 'depth' });
|
|
1125
1129
|
continue;
|
|
1126
1130
|
}
|
|
1127
1131
|
if (!(await isAllowedBasedOnRobotsTxtFile(requestOptions.url))) {
|
|
1128
|
-
allSkipped.push({
|
|
1132
|
+
allSkipped.push({ source: keepSkippedSource(requestOptions), reason: 'robotsTxt' });
|
|
1129
1133
|
continue;
|
|
1130
1134
|
}
|
|
1131
|
-
const
|
|
1135
|
+
const onSkippedByFilter = (opts) => allSkipped.push({ source: keepSkippedSource(opts), reason: 'filters' });
|
|
1132
1136
|
// Filter by user patterns first (with exclude)...
|
|
1133
|
-
let filtered = filterRequestOptionsByPatterns([requestOptions], urlPatternObjects.length > 0 ? urlPatternObjects : undefined, urlExcludePatternObjects, strategy,
|
|
1137
|
+
let filtered = filterRequestOptionsByPatterns([requestOptions], urlPatternObjects.length > 0 ? urlPatternObjects : undefined, urlExcludePatternObjects, strategy, onSkippedByFilter);
|
|
1134
1138
|
// ...then filter by the enqueue strategy (making this an AND check)
|
|
1135
|
-
filtered = filterRequestOptionsByPatterns(filtered, enqueueStrategyPatterns.length > 0 ? enqueueStrategyPatterns : undefined, [], strategy,
|
|
1139
|
+
filtered = filterRequestOptionsByPatterns(filtered, enqueueStrategyPatterns.length > 0 ? enqueueStrategyPatterns : undefined, [], strategy, onSkippedByFilter);
|
|
1136
1140
|
if (filtered.length === 0) {
|
|
1137
1141
|
continue;
|
|
1138
1142
|
}
|
|
1139
1143
|
let [finalOptions] = filtered;
|
|
1140
1144
|
if (options.transformRequestFunction) {
|
|
1141
|
-
const transformed = applyRequestTransform([finalOptions], options.transformRequestFunction, (r) => allSkipped.push({
|
|
1145
|
+
const transformed = applyRequestTransform([finalOptions], options.transformRequestFunction, (r) => allSkipped.push({ source: keepSkippedSource(r), reason: r.skippedReason ?? 'transform' }));
|
|
1142
1146
|
if (transformed.length === 0) {
|
|
1143
1147
|
continue;
|
|
1144
1148
|
}
|
|
@@ -1158,10 +1162,12 @@ export class BasicCrawler {
|
|
|
1158
1162
|
// Report requests skipped due to the maxNewRequests budget (i.e. maxRequestsPerCrawl limit, or an
|
|
1159
1163
|
// explicit `limit` option)
|
|
1160
1164
|
for (const request of result.requestsOverLimit ?? []) {
|
|
1161
|
-
allSkipped.push({
|
|
1165
|
+
allSkipped.push({ source: request, reason: 'limit' });
|
|
1162
1166
|
}
|
|
1163
1167
|
if (allSkipped.length > 0) {
|
|
1164
|
-
const skippedRobotsUrls = allSkipped
|
|
1168
|
+
const skippedRobotsUrls = allSkipped
|
|
1169
|
+
.filter((s) => s.reason === 'robotsTxt')
|
|
1170
|
+
.map(({ source }) => (typeof source === 'string' ? source : source.url));
|
|
1165
1171
|
if (skippedRobotsUrls.length > 0) {
|
|
1166
1172
|
this.log.warning(`Some requests were skipped because they were disallowed based on the robots.txt file`, { skipped: skippedRobotsUrls });
|
|
1167
1173
|
}
|
|
@@ -1172,9 +1178,10 @@ export class BasicCrawler {
|
|
|
1172
1178
|
? `Skipping requests in this call due to the enqueueLinks limit of ${options.limit}.`
|
|
1173
1179
|
: `Skipping requests in this call due to the remaining maxRequestsPerCrawl budget of ${requestLimit}, which is lower than the enqueueLinks limit of ${options.limit}.`);
|
|
1174
1180
|
}
|
|
1175
|
-
await Promise.all(allSkipped.map(async ({
|
|
1176
|
-
|
|
1177
|
-
await
|
|
1181
|
+
await Promise.all(allSkipped.map(async ({ source, reason }) => {
|
|
1182
|
+
const args = createSkippedRequestArgs(source, reason);
|
|
1183
|
+
await this.#handleSkippedRequest(args);
|
|
1184
|
+
await options.onSkippedRequest?.(args);
|
|
1178
1185
|
}));
|
|
1179
1186
|
}
|
|
1180
1187
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/basic",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.150",
|
|
4
4
|
"description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"@apify/datastructures": "^2.0.0",
|
|
43
43
|
"@apify/timeout": "^0.4.4",
|
|
44
44
|
"@apify/utilities": "^2.15.5",
|
|
45
|
-
"@crawlee/core": "4.0.0-beta.
|
|
46
|
-
"@crawlee/http-client": "4.0.0-beta.
|
|
47
|
-
"@crawlee/types": "4.0.0-beta.
|
|
48
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
45
|
+
"@crawlee/core": "4.0.0-beta.150",
|
|
46
|
+
"@crawlee/http-client": "4.0.0-beta.150",
|
|
47
|
+
"@crawlee/types": "4.0.0-beta.150",
|
|
48
|
+
"@crawlee/utils": "4.0.0-beta.150",
|
|
49
49
|
"csv-stringify": "^6.5.2",
|
|
50
50
|
"tldts": "^7.0.6",
|
|
51
51
|
"tslib": "^2.8.1",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"zod": "^4.4.3"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"@crawlee/impit-client": "^4.0.0-beta.
|
|
56
|
+
"@crawlee/impit-client": "^4.0.0-beta.150"
|
|
57
57
|
},
|
|
58
58
|
"lerna": {
|
|
59
59
|
"command": {
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "ef7e9fb2e2fcaa0f74e60a30fa8ae61c60c64626"
|
|
66
66
|
}
|