@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.
@@ -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
- url: request.url,
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({ url: request.url, reason: 'redirect' });
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({ url: requestOptions.url, reason: 'depth' });
1128
+ allSkipped.push({ source: keepSkippedSource(requestOptions), reason: 'depth' });
1125
1129
  continue;
1126
1130
  }
1127
1131
  if (!(await isAllowedBasedOnRobotsTxtFile(requestOptions.url))) {
1128
- allSkipped.push({ url: requestOptions.url, reason: 'robotsTxt' });
1132
+ allSkipped.push({ source: keepSkippedSource(requestOptions), reason: 'robotsTxt' });
1129
1133
  continue;
1130
1134
  }
1131
- const onSkippedFilterUrl = (url) => allSkipped.push({ url, reason: 'filters' });
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, onSkippedFilterUrl);
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, onSkippedFilterUrl);
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({ url: r.url, reason: r.skippedReason ?? 'transform' }));
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({ url: typeof request === 'string' ? request : request.url, reason: 'limit' });
1165
+ allSkipped.push({ source: request, reason: 'limit' });
1162
1166
  }
1163
1167
  if (allSkipped.length > 0) {
1164
- const skippedRobotsUrls = allSkipped.filter((s) => s.reason === 'robotsTxt').map((s) => s.url);
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 ({ url, reason }) => {
1176
- await this.#handleSkippedRequest({ url, reason });
1177
- await options.onSkippedRequest?.({ url, reason });
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.149",
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.149",
46
- "@crawlee/http-client": "4.0.0-beta.149",
47
- "@crawlee/types": "4.0.0-beta.149",
48
- "@crawlee/utils": "4.0.0-beta.149",
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.149"
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": "c88124c43b4c366d4b0ade01da5e90843d59c582"
65
+ "gitHead": "ef7e9fb2e2fcaa0f74e60a30fa8ae61c60c64626"
66
66
  }