@crawlee/basic 4.0.0-beta.121 → 4.0.0-beta.123
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.d.ts +98 -87
- package/internals/basic-crawler.js +168 -137
- package/internals/send-request.d.ts +2 -1
- package/package.json +9 -9
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, ConcurrencySystemOptions,
|
|
2
|
-
import { ConcurrencySystem, ContextPipeline, Dataset, RequestQueue } from '@crawlee/core';
|
|
3
|
-
import
|
|
1
|
+
import type { AddRequestsBatchedOptions, AddRequestsBatchedResult, ConcurrencySystemOptions, CrawleeLogger, CrawlingContext, DatasetExportOptions, EnqueueUrlsOptions, FinalStatistics, GetUserDataFromRequest, IConcurrencySystem, IProxyConfiguration, IRequestLoader, IRequestManager, IStatistics, RequestsLike, RouterHandler, RouterRoutes, SkippedRequestCallback, Source, StatisticState, StorageIdentifier, StorageWritePolicy, TaskLoopPredicates, TypedRequestsLike } from '@crawlee/core';
|
|
2
|
+
import { ConcurrencySystem, Configuration, ContextPipeline, Request, Dataset, EventManager, RequestQueue } from '@crawlee/core';
|
|
3
|
+
import { BaseHttpClient } from '@crawlee/http-client';
|
|
4
|
+
import type { Awaitable, Dictionary, ISession, ISessionPool, ProxyInfo, SetStatusMessageOptions, StorageBackend } from '@crawlee/types';
|
|
4
5
|
import { RobotsTxtFile } from '@crawlee/utils';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
6
|
+
import type { ReadonlyDeep } from 'type-fest';
|
|
7
|
+
import { z } from 'zod';
|
|
7
8
|
import { TimeoutError } from '@apify/timeout';
|
|
8
9
|
export interface BasicCrawlingContext<UserData extends Dictionary = Dictionary> extends CrawlingContext<UserData> {
|
|
9
10
|
}
|
|
@@ -514,80 +515,93 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
514
515
|
protected readonly onSkippedRequest?: SkippedRequestCallback;
|
|
515
516
|
protected readonly identity: CrawlerIdentity;
|
|
516
517
|
protected static optionsShape: {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
560
|
-
blockedStatusCodes: import("ow").ArrayPredicate<number>;
|
|
561
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
562
|
-
retryOnBlocked: import("ow").BooleanPredicate & BasePredicate<boolean | undefined>;
|
|
563
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
564
|
-
respectRobotsTxtFile: import("ow").AnyPredicate<boolean | object>;
|
|
565
|
-
transactionalStorage: BasePredicate<boolean | Partial<StorageWritePolicy> | undefined>;
|
|
566
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
567
|
-
onSkippedRequest: import("ow").Predicate<Function> & BasePredicate<Function | undefined>;
|
|
568
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
569
|
-
httpClient: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
|
|
570
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
571
|
-
configuration: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
|
|
572
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
573
|
-
storageBackend: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
|
|
574
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
575
|
-
eventManager: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
|
|
576
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
577
|
-
logger: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
|
|
578
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
579
|
-
minConcurrency: import("ow").NumberPredicate & BasePredicate<number | undefined>;
|
|
580
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
581
|
-
maxConcurrency: import("ow").NumberPredicate & BasePredicate<number | undefined>;
|
|
582
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
583
|
-
maxRequestsPerMinute: import("ow").NumberPredicate & BasePredicate<number | undefined>;
|
|
584
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
585
|
-
keepAlive: import("ow").BooleanPredicate & BasePredicate<boolean | undefined>;
|
|
586
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
587
|
-
statistics: import("ow").ObjectPredicate<object> & BasePredicate<object | undefined>;
|
|
588
|
-
// @ts-ignore optional peer dependency or compatibility with es2022
|
|
589
|
-
id: import("ow").StringPredicate & BasePredicate<string | undefined>;
|
|
518
|
+
contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
519
|
+
extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
520
|
+
requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
521
|
+
requestQueue: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
522
|
+
requestManager: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
523
|
+
requestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
524
|
+
requestHandlerTimeoutSecs: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
525
|
+
errorHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
526
|
+
failedRequestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
527
|
+
maxRequestRetries: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
528
|
+
sameDomainDelaySecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
529
|
+
maxRequestsPerCrawl: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
530
|
+
maxCrawlDepth: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
531
|
+
taskLoopOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
532
|
+
concurrencySystem: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
533
|
+
sessionPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
534
|
+
proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
535
|
+
statusMessageLoggingInterval: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
536
|
+
statusMessageCallback: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
537
|
+
additionalHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
538
|
+
ignoreHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
539
|
+
blockedStatusCodes: z.ZodOptional<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
540
|
+
retryOnBlocked: z.ZodDefault<z.ZodBoolean>;
|
|
541
|
+
respectRobotsTxtFile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<Dictionary, Dictionary>]>>;
|
|
542
|
+
transactionalStorage: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
543
|
+
requestQueue: z.ZodOptional<z.ZodEnum<{
|
|
544
|
+
deferred: "deferred";
|
|
545
|
+
writeThrough: "writeThrough";
|
|
546
|
+
}>>;
|
|
547
|
+
}, z.core.$strict>]>>;
|
|
548
|
+
onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
549
|
+
httpClient: z.ZodOptional<z.ZodCustom<BaseHttpClient, BaseHttpClient>>;
|
|
550
|
+
configuration: z.ZodOptional<z.ZodCustom<Configuration, Configuration>>;
|
|
551
|
+
storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
552
|
+
eventManager: z.ZodOptional<z.ZodCustom<EventManager, EventManager>>;
|
|
553
|
+
logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
554
|
+
minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
555
|
+
maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
556
|
+
maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
557
|
+
keepAlive: z.ZodOptional<z.ZodBoolean>;
|
|
558
|
+
statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
559
|
+
id: z.ZodOptional<z.ZodString>;
|
|
590
560
|
};
|
|
561
|
+
protected static optionsSchema: z.ZodObject<{
|
|
562
|
+
contextPipelineBuilder: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
563
|
+
extendContext: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
564
|
+
requestList: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
565
|
+
requestQueue: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
566
|
+
requestManager: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
567
|
+
requestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
568
|
+
requestHandlerTimeoutSecs: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
569
|
+
errorHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
570
|
+
failedRequestHandler: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
571
|
+
maxRequestRetries: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
572
|
+
sameDomainDelaySecs: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
573
|
+
maxRequestsPerCrawl: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
574
|
+
maxCrawlDepth: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
575
|
+
taskLoopOptions: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
576
|
+
concurrencySystem: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
577
|
+
sessionPool: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
578
|
+
proxyConfiguration: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
579
|
+
statusMessageLoggingInterval: z.ZodDefault<z.ZodCustom<number, number>>;
|
|
580
|
+
statusMessageCallback: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
581
|
+
additionalHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
582
|
+
ignoreHttpErrorStatusCodes: z.ZodDefault<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
583
|
+
blockedStatusCodes: z.ZodOptional<z.ZodArray<z.ZodCustom<number, number>>>;
|
|
584
|
+
retryOnBlocked: z.ZodDefault<z.ZodBoolean>;
|
|
585
|
+
respectRobotsTxtFile: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodCustom<Dictionary, Dictionary>]>>;
|
|
586
|
+
transactionalStorage: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
587
|
+
requestQueue: z.ZodOptional<z.ZodEnum<{
|
|
588
|
+
deferred: "deferred";
|
|
589
|
+
writeThrough: "writeThrough";
|
|
590
|
+
}>>;
|
|
591
|
+
}, z.core.$strict>]>>;
|
|
592
|
+
onSkippedRequest: z.ZodOptional<z.ZodCustom<(...args: any[]) => unknown, (...args: any[]) => unknown>>;
|
|
593
|
+
httpClient: z.ZodOptional<z.ZodCustom<BaseHttpClient, BaseHttpClient>>;
|
|
594
|
+
configuration: z.ZodOptional<z.ZodCustom<Configuration, Configuration>>;
|
|
595
|
+
storageBackend: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
596
|
+
eventManager: z.ZodOptional<z.ZodCustom<EventManager, EventManager>>;
|
|
597
|
+
logger: z.ZodOptional<z.ZodType<Dictionary<any>, unknown, z.core.$ZodTypeInternals<Dictionary<any>, unknown>>>;
|
|
598
|
+
minConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
599
|
+
maxConcurrency: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
600
|
+
maxRequestsPerMinute: z.ZodOptional<z.ZodCustom<number, number>>;
|
|
601
|
+
keepAlive: z.ZodOptional<z.ZodBoolean>;
|
|
602
|
+
statistics: z.ZodOptional<z.ZodCustom<Dictionary, Dictionary>>;
|
|
603
|
+
id: z.ZodOptional<z.ZodString>;
|
|
604
|
+
}, z.core.$strict>;
|
|
591
605
|
/**
|
|
592
606
|
* All `BasicCrawler` parameters are passed via an options object.
|
|
593
607
|
*/
|
|
@@ -719,6 +733,11 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
719
733
|
* the batches via `waitBetweenBatchesMillis`. If you want to wait for all batches to be added to the queue, you can use
|
|
720
734
|
* the `waitForAllRequestsToBeAdded` promise you get in the response object.
|
|
721
735
|
*
|
|
736
|
+
* Optionally, the requests can be filtered using `include`/`exclude` glob or regexp patterns and an
|
|
737
|
+
* enqueue `strategy` (both AND-ed together, same as {@link CrawlingContext.enqueueLinks|`enqueueLinks`}),
|
|
738
|
+
* relative to `baseUrl`. Unlike `enqueueLinks`, there is no implicit "current page" to anchor the strategy
|
|
739
|
+
* to, so `strategy` defaults to {@link EnqueueStrategy.All|`all`} here.
|
|
740
|
+
*
|
|
722
741
|
* This is an alias for calling `addRequestsBatched()` on the implicit `RequestQueue` for this crawler instance.
|
|
723
742
|
*
|
|
724
743
|
* @param requests The requests to add
|
|
@@ -813,14 +832,6 @@ export declare class BasicCrawler<Context extends CrawlingContext = CrawlingCont
|
|
|
813
832
|
private delayRequest;
|
|
814
833
|
/** Handles a single request - runs the request handler with retries, error handling, and lifecycle management. */
|
|
815
834
|
private handleRequest;
|
|
816
|
-
/**
|
|
817
|
-
* Wrapper around the crawling context's `enqueueLinks` method:
|
|
818
|
-
* - Injects `crawlDepth` to each request being added based on the crawling context request.
|
|
819
|
-
* - Provides defaults for the `enqueueLinks` options based on the crawler configuration.
|
|
820
|
-
* - These options can be overridden by the user.
|
|
821
|
-
* @internal
|
|
822
|
-
*/
|
|
823
|
-
protected enqueueLinksWithCrawlDepth(options: SetRequired<EnqueueLinksOptions, 'urls'>, request: Request<Dictionary>, requestManager: IRequestManager): Promise<BatchAddRequestsResult>;
|
|
824
835
|
/**
|
|
825
836
|
* Generator function that yields requests injected with the given crawl depth.
|
|
826
837
|
* @internal
|
|
@@ -879,7 +890,7 @@ export interface CreateContextOptions {
|
|
|
879
890
|
session: ISession;
|
|
880
891
|
proxyInfo?: ProxyInfo;
|
|
881
892
|
}
|
|
882
|
-
export interface CrawlerAddRequestsOptions extends AddRequestsBatchedOptions {
|
|
893
|
+
export interface CrawlerAddRequestsOptions extends AddRequestsBatchedOptions, EnqueueUrlsOptions {
|
|
883
894
|
}
|
|
884
895
|
export interface CrawlerAddRequestsResult extends AddRequestsBatchedResult {
|
|
885
896
|
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
-
import { AutoscaledPool, bindMethodsToServiceLocator, BLOCKED_STATUS_CODES, ConcurrencySystem, ContextPipeline, ContextPipelineCleanupError, ContextPipelineInitializationError, ContextPipelineInterruptedError, createStorageTransaction, CriticalError, currentStorageTransaction, Dataset,
|
|
4
|
-
import { FetchHttpClient } from '@crawlee/http-client';
|
|
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, parseArgument, purgeDefaultStorages, RequestHandlerError, parseRetryAfterHeader, RequestThrottledError, RequestManagerTandem, RequestQueue, RequestState, RetryRequestError, supportsDomainThrottling, Router, schemas, ServiceLocator, serviceLocator, Session, SessionError, SessionPool, Statistics, validateUserData, validators, withDirectStorageAccess, } from '@crawlee/core';
|
|
4
|
+
import { BaseHttpClient, FetchHttpClient } from '@crawlee/http-client';
|
|
5
5
|
import { isAsyncIterable, isIterable, ROTATE_PROXY_ERRORS } from '@crawlee/utils/internal';
|
|
6
6
|
import { RobotsTxtFile } from '@crawlee/utils';
|
|
7
|
-
import ow, { ArgumentError } from 'ow';
|
|
8
7
|
import { getDomain } from 'tldts';
|
|
8
|
+
import { z } from 'zod';
|
|
9
9
|
import { LruCache } from '@apify/datastructures';
|
|
10
10
|
import { addTimeoutToPromise, extendTimeout, TimeoutError } from '@apify/timeout';
|
|
11
11
|
import { cryptoRandomObjectId } from '@apify/utilities';
|
|
12
12
|
import { extendTimeoutKey, navigationDeadlineKey, raceWithTimeout, timeoutExpiredKey, } from './request-timeout.js';
|
|
13
13
|
import { createSendRequest } from './send-request.js';
|
|
14
|
-
class LazyDefaultHttpClient {
|
|
14
|
+
class LazyDefaultHttpClient extends BaseHttpClient {
|
|
15
15
|
#delegatePromise;
|
|
16
16
|
constructor(options) {
|
|
17
|
+
super(options);
|
|
17
18
|
this.#delegatePromise = import('@crawlee/impit-client')
|
|
18
19
|
.then(({ ImpitHttpClient }) => new ImpitHttpClient(options))
|
|
19
20
|
.catch(() => {
|
|
@@ -22,6 +23,9 @@ class LazyDefaultHttpClient {
|
|
|
22
23
|
return new FetchHttpClient(options);
|
|
23
24
|
});
|
|
24
25
|
}
|
|
26
|
+
fetch() {
|
|
27
|
+
throw new Error('LazyDefaultHttpClient delegates `sendRequest` entirely; `fetch` is never called.');
|
|
28
|
+
}
|
|
25
29
|
async sendRequest(...args) {
|
|
26
30
|
return (await this.#delegatePromise).sendRequest(...args);
|
|
27
31
|
}
|
|
@@ -39,6 +43,33 @@ const SAFE_MIGRATION_WAIT_MILLIS = 20000;
|
|
|
39
43
|
const deferredCleanupKey = Symbol('deferredCleanup');
|
|
40
44
|
// The request timeout plumbing (the window helper, the context symbols, and the race) lives in its own module.
|
|
41
45
|
export { navigationDeadlineKey, remainingNavigationWindowMillis } from './request-timeout.js';
|
|
46
|
+
const urlPatternSchema = z.union([
|
|
47
|
+
z.string(),
|
|
48
|
+
z.instanceof(RegExp),
|
|
49
|
+
schemas.objectWithKeys(['glob']),
|
|
50
|
+
schemas.objectWithKeys(['regexp']),
|
|
51
|
+
]);
|
|
52
|
+
// `looseObject` (rather than `strictObject`) lets subclasses forward their own extraction-only options
|
|
53
|
+
// (e.g. `selector`) straight through without having to strip them out first.
|
|
54
|
+
const addRequestsOptionsSchema = z.looseObject({
|
|
55
|
+
forefront: z.boolean().optional(),
|
|
56
|
+
cache: z.boolean().optional(),
|
|
57
|
+
waitForAllRequestsToBeAdded: z.boolean().optional(),
|
|
58
|
+
batchSize: schemas.anyNumber.optional(),
|
|
59
|
+
waitBetweenBatchesMillis: schemas.anyNumber.optional(),
|
|
60
|
+
maxNewRequests: schemas.anyNumber.optional(),
|
|
61
|
+
limit: schemas.anyNumber.optional(),
|
|
62
|
+
baseUrl: z.string().optional(),
|
|
63
|
+
userData: schemas.anyObject.optional(),
|
|
64
|
+
label: z.string().optional(),
|
|
65
|
+
sessionId: z.string().optional(),
|
|
66
|
+
skipNavigation: z.boolean().optional(),
|
|
67
|
+
include: schemas.arrayOf(urlPatternSchema, 'URL patterns').min(1).optional(),
|
|
68
|
+
exclude: schemas.arrayOf(urlPatternSchema, 'URL patterns').optional(),
|
|
69
|
+
transformRequestFunction: schemas.anyFunction.optional(),
|
|
70
|
+
strategy: z.enum(EnqueueStrategy).optional(),
|
|
71
|
+
onSkippedRequest: schemas.anyFunction.optional(),
|
|
72
|
+
});
|
|
42
73
|
export class BasicCrawler {
|
|
43
74
|
static CRAWLEE_STATE_KEY = 'CRAWLEE_STATE';
|
|
44
75
|
/**
|
|
@@ -200,72 +231,77 @@ export class BasicCrawler {
|
|
|
200
231
|
identity;
|
|
201
232
|
#contextPipelineOptions;
|
|
202
233
|
static optionsShape = {
|
|
203
|
-
contextPipelineBuilder:
|
|
204
|
-
extendContext:
|
|
205
|
-
requestList:
|
|
206
|
-
requestQueue:
|
|
207
|
-
requestManager:
|
|
234
|
+
contextPipelineBuilder: schemas.anyObject.optional(),
|
|
235
|
+
extendContext: schemas.anyFunction.optional(),
|
|
236
|
+
requestList: validators.requestList.optional(),
|
|
237
|
+
requestQueue: validators.requestQueue.optional(),
|
|
238
|
+
requestManager: validators.requestManager.optional(),
|
|
208
239
|
// Subclasses override this function instead of passing it
|
|
209
240
|
// in constructor, so this validation needs to apply only
|
|
210
241
|
// if the user creates an instance of BasicCrawler directly.
|
|
211
|
-
requestHandler:
|
|
212
|
-
requestHandlerTimeoutSecs:
|
|
213
|
-
errorHandler:
|
|
214
|
-
failedRequestHandler:
|
|
215
|
-
maxRequestRetries:
|
|
216
|
-
sameDomainDelaySecs:
|
|
217
|
-
maxRequestsPerCrawl:
|
|
218
|
-
maxCrawlDepth:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
242
|
+
requestHandler: schemas.anyFunction.optional(),
|
|
243
|
+
requestHandlerTimeoutSecs: schemas.anyNumber.optional(),
|
|
244
|
+
errorHandler: schemas.anyFunction.optional(),
|
|
245
|
+
failedRequestHandler: schemas.anyFunction.optional(),
|
|
246
|
+
maxRequestRetries: schemas.anyNumber.default(3),
|
|
247
|
+
sameDomainDelaySecs: schemas.anyNumber.default(0),
|
|
248
|
+
maxRequestsPerCrawl: schemas.anyNumber.optional(),
|
|
249
|
+
maxCrawlDepth: schemas.anyNumber.optional(),
|
|
250
|
+
// No zod default — subclasses provide their own fallback (e.g. HTTP-optimized pool options).
|
|
251
|
+
taskLoopOptions: schemas.anyObject.optional(),
|
|
252
|
+
concurrencySystem: schemas.anyObject.optional(),
|
|
253
|
+
sessionPool: validators.sessionPool.optional(),
|
|
254
|
+
proxyConfiguration: validators.proxyConfiguration.optional(),
|
|
255
|
+
statusMessageLoggingInterval: schemas.anyNumber.default(10),
|
|
256
|
+
statusMessageCallback: schemas.anyFunction.optional(),
|
|
257
|
+
additionalHttpErrorStatusCodes: schemas.arrayOf(schemas.anyNumber, 'numbers').default(() => []),
|
|
258
|
+
ignoreHttpErrorStatusCodes: schemas.arrayOf(schemas.anyNumber, 'numbers').default(() => []),
|
|
259
|
+
blockedStatusCodes: schemas.arrayOf(schemas.anyNumber, 'numbers').optional(),
|
|
260
|
+
retryOnBlocked: z.boolean().default(false),
|
|
261
|
+
respectRobotsTxtFile: z.union([z.boolean(), schemas.anyObject]).default(false),
|
|
262
|
+
transactionalStorage: z
|
|
263
|
+
.union([z.boolean(), z.strictObject({ requestQueue: z.enum(['deferred', 'writeThrough']).optional() })])
|
|
264
|
+
.optional(),
|
|
265
|
+
onSkippedRequest: schemas.anyFunction.optional(),
|
|
266
|
+
httpClient: schemas.httpClient.optional(),
|
|
267
|
+
configuration: z.instanceof(Configuration).optional(),
|
|
268
|
+
storageBackend: validators.storageBackend.optional(),
|
|
269
|
+
eventManager: z.instanceof(EventManager).optional(),
|
|
270
|
+
logger: validators.logger.optional(),
|
|
239
271
|
// AutoscaledPool shorthands
|
|
240
|
-
minConcurrency:
|
|
241
|
-
maxConcurrency:
|
|
242
|
-
maxRequestsPerMinute:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
minConcurrency: schemas.anyNumber.optional(),
|
|
273
|
+
maxConcurrency: schemas.anyNumber.optional(),
|
|
274
|
+
maxRequestsPerMinute: schemas.anyNumber
|
|
275
|
+
.refine((value) => Number.isInteger(value) || value === Infinity, 'Expected an integer or infinite number')
|
|
276
|
+
.refine((value) => value >= 1, 'Expected a number greater than or equal to 1')
|
|
277
|
+
.optional(),
|
|
278
|
+
keepAlive: z.boolean().optional(),
|
|
279
|
+
statistics: schemas.anyObject.optional(),
|
|
280
|
+
id: z.string().optional(),
|
|
246
281
|
};
|
|
282
|
+
static optionsSchema = z.strictObject(BasicCrawler.optionsShape);
|
|
247
283
|
/**
|
|
248
284
|
* All `BasicCrawler` parameters are passed via an options object.
|
|
249
285
|
*/
|
|
250
286
|
constructor(options = {}) {
|
|
251
|
-
|
|
287
|
+
const parsedOptions = parseArgument(options, BasicCrawler.optionsSchema, 'BasicCrawlerOptions');
|
|
252
288
|
const {
|
|
253
289
|
// oxlint-disable-next-line typescript/no-deprecated -- still accepted and folded into `requestManager` for back-compat
|
|
254
290
|
requestList,
|
|
255
291
|
// oxlint-disable-next-line typescript/no-deprecated -- still accepted and folded into `requestManager` for back-compat
|
|
256
|
-
requestQueue, requestManager, maxRequestRetries
|
|
292
|
+
requestQueue, requestManager, maxRequestRetries, sameDomainDelaySecs, maxRequestsPerCrawl, maxCrawlDepth, taskLoopOptions = {}, concurrencySystem, keepAlive, sessionPool, proxyConfiguration, additionalHttpErrorStatusCodes, ignoreHttpErrorStatusCodes,
|
|
257
293
|
// Service locator options
|
|
258
294
|
configuration, storageBackend, eventManager, logger,
|
|
259
295
|
// AutoscaledPool shorthands
|
|
260
|
-
minConcurrency, maxConcurrency, maxRequestsPerMinute, blockedStatusCodes: blockedStatusCodesInput, retryOnBlocked
|
|
296
|
+
minConcurrency, maxConcurrency, maxRequestsPerMinute, blockedStatusCodes: blockedStatusCodesInput, retryOnBlocked, respectRobotsTxtFile, transactionalStorage, onSkippedRequest, requestHandler, requestHandlerTimeoutSecs, errorHandler, failedRequestHandler, statusMessageLoggingInterval, statusMessageCallback, statistics, httpClient, id, } = parsedOptions;
|
|
261
297
|
// All concurrency configuration lives on the `ConcurrencySystem`, so the shortcuts have nowhere to go once
|
|
262
298
|
// one is supplied - and silently dropping a `maxConcurrency` the user asked for is how crawls end up
|
|
263
299
|
// hammering a site.
|
|
264
300
|
if (concurrencySystem !== undefined &&
|
|
265
301
|
(minConcurrency !== undefined || maxConcurrency !== undefined || maxRequestsPerMinute !== undefined)) {
|
|
266
|
-
throw new
|
|
302
|
+
throw new Error('The `minConcurrency`/`maxConcurrency`/`maxRequestsPerMinute` shortcuts cannot be combined with ' +
|
|
267
303
|
'`concurrencySystem` - they configure the default `ConcurrencySystem` that a supplied one ' +
|
|
268
|
-
'replaces. Pass them to the `ConcurrencySystem` constructor instead.'
|
|
304
|
+
'replaces. Pass them to the `ConcurrencySystem` constructor instead.');
|
|
269
305
|
}
|
|
270
306
|
// Create per-crawler service locator if custom services were provided.
|
|
271
307
|
// This wraps every method on the crawler instance so that calls to the global `serviceLocator`
|
|
@@ -287,8 +323,8 @@ export class BasicCrawler {
|
|
|
287
323
|
try {
|
|
288
324
|
serviceLocatorScope.enterScope();
|
|
289
325
|
this.#contextPipelineOptions = {
|
|
290
|
-
contextPipelineBuilder:
|
|
291
|
-
extendContext:
|
|
326
|
+
contextPipelineBuilder: parsedOptions.contextPipelineBuilder,
|
|
327
|
+
extendContext: parsedOptions.extendContext,
|
|
292
328
|
};
|
|
293
329
|
this.#log = serviceLocator.getLogger().child({ prefix: this.constructor.name });
|
|
294
330
|
// Initialize the Configuration instance to avoid lazy loading in the components
|
|
@@ -589,17 +625,13 @@ export class BasicCrawler {
|
|
|
589
625
|
return { session, proxyInfo: session?.proxyInfo };
|
|
590
626
|
}
|
|
591
627
|
async createContextHelpers({ request, session }) {
|
|
592
|
-
const enqueueLinksWrapper = async (options) => {
|
|
593
|
-
const requestManager = await this.getRequestManager();
|
|
594
|
-
return await this.enqueueLinksWithCrawlDepth(options, request, requestManager);
|
|
595
|
-
};
|
|
596
628
|
const addRequests = async (requests, options = {}) => {
|
|
597
629
|
const newCrawlDepth = request.crawlDepth + 1;
|
|
598
630
|
const requestsGenerator = this.addCrawlDepthRequestGenerator(requests, newCrawlDepth);
|
|
599
|
-
await this.addRequests(requestsGenerator, options);
|
|
631
|
+
return await this.addRequests(requestsGenerator, options);
|
|
600
632
|
};
|
|
601
633
|
const sendRequest = createSendRequest(this.httpClient, request, session);
|
|
602
|
-
return {
|
|
634
|
+
return { addRequests, sendRequest };
|
|
603
635
|
}
|
|
604
636
|
buildFinalContextPipeline() {
|
|
605
637
|
const subclassPipeline = (this.#contextPipelineOptions.contextPipelineBuilder?.() ??
|
|
@@ -996,6 +1028,11 @@ export class BasicCrawler {
|
|
|
996
1028
|
* the batches via `waitBetweenBatchesMillis`. If you want to wait for all batches to be added to the queue, you can use
|
|
997
1029
|
* the `waitForAllRequestsToBeAdded` promise you get in the response object.
|
|
998
1030
|
*
|
|
1031
|
+
* Optionally, the requests can be filtered using `include`/`exclude` glob or regexp patterns and an
|
|
1032
|
+
* enqueue `strategy` (both AND-ed together, same as {@link CrawlingContext.enqueueLinks|`enqueueLinks`}),
|
|
1033
|
+
* relative to `baseUrl`. Unlike `enqueueLinks`, there is no implicit "current page" to anchor the strategy
|
|
1034
|
+
* to, so `strategy` defaults to {@link EnqueueStrategy.All|`all`} here.
|
|
1035
|
+
*
|
|
999
1036
|
* This is an alias for calling `addRequestsBatched()` on the implicit `RequestQueue` for this crawler instance.
|
|
1000
1037
|
*
|
|
1001
1038
|
* @param requests The requests to add
|
|
@@ -1003,55 +1040,95 @@ export class BasicCrawler {
|
|
|
1003
1040
|
*/
|
|
1004
1041
|
async addRequests(requests, options = {}) {
|
|
1005
1042
|
await this.getRequestManager();
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1043
|
+
if (!isIterable(requests) && !isAsyncIterable(requests)) {
|
|
1044
|
+
throw new Error(`Expected an iterable or async iterable, got ${getObjectType(requests)}`);
|
|
1045
|
+
}
|
|
1046
|
+
parseArgument(options, addRequestsOptionsSchema, 'EnqueueUrlsOptions');
|
|
1047
|
+
// `label`/`userData` apply to every request this call produces, so a single upfront validation
|
|
1048
|
+
// against the label's schema covers them all and fails the whole call fast, rather than failing
|
|
1049
|
+
// lazily once the generator below is drained. Skipped when neither is set - each item still gets
|
|
1050
|
+
// its own per-item validation below, and validating an absent label/userData here would spuriously
|
|
1051
|
+
// check them against a registered default-route schema.
|
|
1052
|
+
if (options.label !== undefined || options.userData !== undefined) {
|
|
1053
|
+
await this.validateRequestUserData({ label: options.label, userData: options.userData });
|
|
1054
|
+
}
|
|
1055
|
+
const requestLimit = await this.calculateEnqueuedRequestLimit(options.limit);
|
|
1056
|
+
const strategy = options.strategy ?? EnqueueStrategy.All;
|
|
1057
|
+
const urlExcludePatternObjects = options.exclude?.length
|
|
1058
|
+
? constructUrlPatternObjects(options.exclude)
|
|
1059
|
+
: [];
|
|
1060
|
+
const urlPatternObjects = options.include?.length
|
|
1061
|
+
? constructUrlPatternObjects(options.include)
|
|
1062
|
+
: [];
|
|
1063
|
+
// The strategy always applies, even when `include` patterns are provided - the two are AND-ed together
|
|
1064
|
+
// (a URL must match an `include` pattern *and* satisfy the strategy). This mirrors crawlee-python.
|
|
1065
|
+
const enqueueStrategyPatterns = options.baseUrl
|
|
1066
|
+
? buildEnqueueStrategyPatterns(options.baseUrl, strategy)
|
|
1067
|
+
: [];
|
|
1009
1068
|
const isAllowedBasedOnRobotsTxtFile = this.isAllowedBasedOnRobotsTxtFile.bind(this);
|
|
1010
1069
|
const maxCrawlDepth = this.maxCrawlDepth;
|
|
1011
1070
|
const validateRequestUserData = this.validateRequestUserData.bind(this);
|
|
1012
|
-
|
|
1013
|
-
.is((value) => isIterable(value) || isAsyncIterable(value))
|
|
1014
|
-
.message((value) => `Expected an iterable or async iterable, got ${getObjectType(value)}`));
|
|
1071
|
+
const allSkipped = [];
|
|
1015
1072
|
async function* filteredRequests() {
|
|
1016
1073
|
for await (const request of requests) {
|
|
1017
|
-
const
|
|
1018
|
-
if (
|
|
1019
|
-
|
|
1074
|
+
const [requestOptions] = createRequestOptions([typeof request === 'string' ? request : request], { ...options, strategy });
|
|
1075
|
+
if (!requestOptions) {
|
|
1076
|
+
continue; // invalid URL, silently dropped (matches `createRequestOptions`'s own filtering)
|
|
1077
|
+
}
|
|
1078
|
+
if (maxCrawlDepth !== undefined && requestOptions.crawlDepth > maxCrawlDepth) {
|
|
1079
|
+
allSkipped.push({ url: requestOptions.url, reason: 'depth' });
|
|
1020
1080
|
continue;
|
|
1021
1081
|
}
|
|
1022
|
-
if (await isAllowedBasedOnRobotsTxtFile(url)) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1082
|
+
if (!(await isAllowedBasedOnRobotsTxtFile(requestOptions.url))) {
|
|
1083
|
+
allSkipped.push({ url: requestOptions.url, reason: 'robotsTxt' });
|
|
1084
|
+
continue;
|
|
1025
1085
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1086
|
+
const onSkippedFilterUrl = (url) => allSkipped.push({ url, reason: 'filters' });
|
|
1087
|
+
// Filter by user patterns first (with exclude)...
|
|
1088
|
+
let filtered = filterRequestOptionsByPatterns([requestOptions], urlPatternObjects.length > 0 ? urlPatternObjects : undefined, urlExcludePatternObjects, strategy, onSkippedFilterUrl);
|
|
1089
|
+
// ...then filter by the enqueue strategy (making this an AND check)
|
|
1090
|
+
filtered = filterRequestOptionsByPatterns(filtered, enqueueStrategyPatterns.length > 0 ? enqueueStrategyPatterns : undefined, [], strategy, onSkippedFilterUrl);
|
|
1091
|
+
if (filtered.length === 0) {
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
let [finalOptions] = filtered;
|
|
1095
|
+
if (options.transformRequestFunction) {
|
|
1096
|
+
const transformed = applyRequestTransform([finalOptions], options.transformRequestFunction, (r) => allSkipped.push({ url: r.url, reason: r.skippedReason ?? 'transform' }));
|
|
1097
|
+
if (transformed.length === 0) {
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
[finalOptions] = transformed;
|
|
1028
1101
|
}
|
|
1102
|
+
await validateRequestUserData(finalOptions);
|
|
1103
|
+
yield new Request(finalOptions);
|
|
1029
1104
|
}
|
|
1030
1105
|
}
|
|
1031
1106
|
const result = await this.requestManager.addRequestsBatched(filteredRequests(), {
|
|
1032
|
-
|
|
1107
|
+
forefront: options.forefront,
|
|
1108
|
+
waitForAllRequestsToBeAdded: options.waitForAllRequestsToBeAdded,
|
|
1109
|
+
batchSize: options.batchSize,
|
|
1110
|
+
waitBetweenBatchesMillis: options.waitBetweenBatchesMillis,
|
|
1033
1111
|
maxNewRequests: requestLimit,
|
|
1034
1112
|
});
|
|
1035
|
-
// Report requests skipped due to the maxNewRequests budget (i.e. maxRequestsPerCrawl limit
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
})));
|
|
1113
|
+
// Report requests skipped due to the maxNewRequests budget (i.e. maxRequestsPerCrawl limit, or an
|
|
1114
|
+
// explicit `limit` option)
|
|
1115
|
+
for (const request of result.requestsOverLimit ?? []) {
|
|
1116
|
+
allSkipped.push({ url: typeof request === 'string' ? request : request.url, reason: 'limit' });
|
|
1117
|
+
}
|
|
1118
|
+
if (allSkipped.length > 0) {
|
|
1119
|
+
const skippedRobotsUrls = allSkipped.filter((s) => s.reason === 'robotsTxt').map((s) => s.url);
|
|
1120
|
+
if (skippedRobotsUrls.length > 0) {
|
|
1121
|
+
this.log.warning(`Some requests were skipped because they were disallowed based on the robots.txt file`, { skipped: skippedRobotsUrls });
|
|
1122
|
+
}
|
|
1123
|
+
// Only log the limit message when an explicit `limit` was passed (not the internal
|
|
1124
|
+
// `maxRequestsPerCrawl`-derived one), and only once per call.
|
|
1125
|
+
if (options.limit !== undefined && allSkipped.some((s) => s.reason === 'limit')) {
|
|
1126
|
+
this.log.info(`Skipping requests in this call due to the enqueueLinks limit of ${options.limit}.`);
|
|
1127
|
+
}
|
|
1128
|
+
await Promise.all(allSkipped.map(async ({ url, reason }) => {
|
|
1129
|
+
await this.handleSkippedRequest({ url, reason });
|
|
1130
|
+
await options.onSkippedRequest?.({ url, reason });
|
|
1131
|
+
}));
|
|
1055
1132
|
}
|
|
1056
1133
|
return result;
|
|
1057
1134
|
}
|
|
@@ -1450,52 +1527,6 @@ export class BasicCrawler {
|
|
|
1450
1527
|
}
|
|
1451
1528
|
}
|
|
1452
1529
|
}
|
|
1453
|
-
/**
|
|
1454
|
-
* Wrapper around the crawling context's `enqueueLinks` method:
|
|
1455
|
-
* - Injects `crawlDepth` to each request being added based on the crawling context request.
|
|
1456
|
-
* - Provides defaults for the `enqueueLinks` options based on the crawler configuration.
|
|
1457
|
-
* - These options can be overridden by the user.
|
|
1458
|
-
* @internal
|
|
1459
|
-
*/
|
|
1460
|
-
async enqueueLinksWithCrawlDepth(options, request, requestManager) {
|
|
1461
|
-
const transformRequestFunctionWrapper = (requestOptions) => {
|
|
1462
|
-
requestOptions.crawlDepth = request.crawlDepth + 1;
|
|
1463
|
-
if (this.maxCrawlDepth !== undefined && requestOptions.crawlDepth > this.maxCrawlDepth) {
|
|
1464
|
-
// Setting `skippedReason` before returning `false` ensures that `reportSkippedRequests`
|
|
1465
|
-
// reports `'depth'` as the reason (via `request.skippedReason ?? reason` fallback),
|
|
1466
|
-
// rather than the generic `'transform'` reason.
|
|
1467
|
-
requestOptions.skippedReason = 'depth';
|
|
1468
|
-
return false;
|
|
1469
|
-
}
|
|
1470
|
-
// After injecting the crawlDepth, we call the user-provided transform function, if there is one.
|
|
1471
|
-
return options.transformRequestFunction?.(requestOptions) ?? requestOptions;
|
|
1472
|
-
};
|
|
1473
|
-
// Create a request-scoped callback that logs enqueueLimit once per request handler call
|
|
1474
|
-
// Only log if an explicit limit was passed to enqueueLinks (not the internal maxRequestsPerCrawl-derived limit)
|
|
1475
|
-
let loggedEnqueueLimitForThisRequest = false;
|
|
1476
|
-
const onSkippedRequest = async (skippedOptions) => {
|
|
1477
|
-
if (skippedOptions.reason === 'enqueueLimit') {
|
|
1478
|
-
if (!loggedEnqueueLimitForThisRequest && options.limit !== undefined) {
|
|
1479
|
-
this.log.info(`Skipping URLs in the handler for ${request.url} due to the enqueueLinks limit of ${options.limit}.`);
|
|
1480
|
-
loggedEnqueueLimitForThisRequest = true;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
await this.handleSkippedRequest(skippedOptions);
|
|
1484
|
-
};
|
|
1485
|
-
// `enqueueLinks` applies `options.label`/`options.userData` to every newly enqueued request, so a single
|
|
1486
|
-
// validation against the label's schema covers them all (a no-op unless the router declares a schema).
|
|
1487
|
-
await this.validateRequestUserData({ label: options.label, userData: options.userData });
|
|
1488
|
-
return await enqueueLinks({
|
|
1489
|
-
requestManager,
|
|
1490
|
-
robotsTxtFile: await this.getRobotsTxtFileForUrl(request.url),
|
|
1491
|
-
respectRobotsTxtFile: this.#respectRobotsTxtFile,
|
|
1492
|
-
onSkippedRequest,
|
|
1493
|
-
limit: await this.calculateEnqueuedRequestLimit(options.limit),
|
|
1494
|
-
// Allow user options to override defaults set above ⤴
|
|
1495
|
-
...options,
|
|
1496
|
-
transformRequestFunction: transformRequestFunctionWrapper,
|
|
1497
|
-
});
|
|
1498
|
-
}
|
|
1499
1530
|
/**
|
|
1500
1531
|
* Generator function that yields requests injected with the given crawl depth.
|
|
1501
1532
|
* @internal
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Request as CrawleeRequest } from '@crawlee/core';
|
|
2
|
-
import type { BaseHttpClient
|
|
2
|
+
import type { BaseHttpClient } from '@crawlee/http-client';
|
|
3
|
+
import type { HttpRequestOptions, ISession, SendRequestOptions } from '@crawlee/types';
|
|
3
4
|
/**
|
|
4
5
|
* Prepares a function to be used as the `sendRequest` context helper.
|
|
5
6
|
*
|
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.123",
|
|
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,18 +42,18 @@
|
|
|
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.123",
|
|
46
|
+
"@crawlee/http-client": "4.0.0-beta.123",
|
|
47
|
+
"@crawlee/types": "4.0.0-beta.123",
|
|
48
|
+
"@crawlee/utils": "4.0.0-beta.123",
|
|
49
49
|
"csv-stringify": "^6.5.2",
|
|
50
|
-
"ow": "^2.0.0",
|
|
51
50
|
"tldts": "^7.0.6",
|
|
52
51
|
"tslib": "^2.8.1",
|
|
53
|
-
"type-fest": "^4.41.0"
|
|
52
|
+
"type-fest": "^4.41.0",
|
|
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.123"
|
|
57
57
|
},
|
|
58
58
|
"lerna": {
|
|
59
59
|
"command": {
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "f77648095c6a3f5ed8815c7620ea765db430ae44"
|
|
66
66
|
}
|