@crawlee/linkedom 4.0.0-beta.98 → 4.0.0-rc.0

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,16 +1,14 @@
1
- import type { BasicCrawlingContext, CrawlingContext, EnqueueLinksOptions, ErrorHandler, GetUserDataFromRequest, HttpCrawlerOptions, InternalHttpCrawlingContext, InternalHttpHook, IRequestManager, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas, SkippedRequestCallback } from '@crawlee/http';
1
+ import type { AddRequestsBatchedResult, CrawlingContext, EnqueueLinksOptions, ErrorHandler, ExtractLinksOptions, GetUserDataFromRequest, HttpCrawlerOptions, InternalHttpCrawlingContext, InternalHttpHook, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/http';
2
2
  import { HttpCrawler } from '@crawlee/http';
3
3
  import type { Dictionary } from '@crawlee/types';
4
- import { type CheerioRoot, type RobotsTxtFile } from '@crawlee/utils';
4
+ import { type CheerioRoot } from '@crawlee/utils/internal';
5
5
  import * as cheerio from 'cheerio';
6
6
  export type LinkeDOMErrorHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
7
7
  JSONData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
8
8
  ContextExtension = Dictionary<never>> = ErrorHandler<CrawlingContext, LinkeDOMCrawlingContext<UserData, JSONData> & ContextExtension>;
9
9
  export interface LinkeDOMCrawlerOptions<ContextExtension = Dictionary<never>, ExtendedContext extends LinkeDOMCrawlingContext = LinkeDOMCrawlingContext & ContextExtension, UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
10
10
  JSONData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
11
- Routes extends Record<keyof Routes, Dictionary> = Record<string, UserData>> extends HttpCrawlerOptions<LinkeDOMCrawlingContext<UserData, JSONData>, ContextExtension, ExtendedContext, Routes> {
12
- }
13
- export interface LinkeDOMCrawlerEnqueueLinksOptions extends Omit<EnqueueLinksOptions, 'urls' | 'requestManager'> {
11
+ Routes extends Record<keyof Routes, Dictionary> = Record<string, UserData>, StatisticStateExtension extends object = {}> extends HttpCrawlerOptions<LinkeDOMCrawlingContext<UserData, JSONData>, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
14
12
  }
15
13
  export type LinkeDOMHook<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
16
14
  JSONData extends Dictionary = any> = InternalHttpHook<LinkeDOMCrawlingContext<UserData, JSONData>>;
@@ -45,6 +43,14 @@ JSONData extends Dictionary = any> extends InternalHttpCrawlingContext<UserData,
45
43
  * ```
46
44
  */
47
45
  parseWithCheerio(selector?: string, timeoutMs?: number): Promise<CheerioRoot>;
46
+ /**
47
+ * Extracts URLs from the parsed DOM, without adding them to the request queue.
48
+ */
49
+ extractLinks(options?: ExtractLinksOptions): Promise<string[]>;
50
+ /**
51
+ * Helper function for extracting URLs from the parsed DOM and adding them to the request queue.
52
+ */
53
+ enqueueLinks(options?: EnqueueLinksOptions): Promise<AddRequestsBatchedResult>;
48
54
  }
49
55
  export type LinkeDOMRequestHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
50
56
  JSONData extends Dictionary = any> = RequestHandler<LinkeDOMCrawlingContext<UserData, JSONData>>;
@@ -120,40 +126,23 @@ JSONData extends Dictionary = any> = RequestHandler<LinkeDOMCrawlingContext<User
120
126
  * ```
121
127
  * @category Crawlers
122
128
  */
123
- export declare class LinkeDOMCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends LinkeDOMCrawlingContext = LinkeDOMCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<LinkeDOMCrawlingContext['request']>>> extends HttpCrawler<LinkeDOMCrawlingContext, ContextExtension, ExtendedContext, Routes> {
124
- private static parser;
125
- constructor(options: LinkeDOMCrawlerOptions<ContextExtension, ExtendedContext, any, any, Routes>);
129
+ export declare class LinkeDOMCrawler<ContextExtension = Dictionary<never>, ExtendedContext extends LinkeDOMCrawlingContext = LinkeDOMCrawlingContext & ContextExtension, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<LinkeDOMCrawlingContext['request']>>, StatisticStateExtension extends object = {}> extends HttpCrawler<LinkeDOMCrawlingContext, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
130
+ #private;
131
+ constructor(options?: LinkeDOMCrawlerOptions<ContextExtension, ExtendedContext, any, any, Routes, StatisticStateExtension>);
126
132
  // @ts-ignore optional peer dependency or compatibility with es2022
127
133
  protected buildContextPipeline(): import("@crawlee/http").ContextPipeline<CrawlingContext<Dictionary>, InternalHttpCrawlingContext<any, any> & {
128
134
  readonly window: Window;
129
135
  readonly body: string;
130
136
  readonly document: Document;
131
137
  } & {
132
- enqueueLinks: (enqueueOptions?: LinkeDOMCrawlerEnqueueLinksOptions) => Promise<unknown>;
138
+ extractLinks: (options?: ExtractLinksOptions) => Promise<string[]>;
139
+ enqueueLinks: (options?: EnqueueLinksOptions) => Promise<AddRequestsBatchedResult>;
133
140
  waitForSelector(selector: string, timeoutMs?: number): Promise<void>;
134
141
  parseWithCheerio(selector?: string, _timeoutMs?: number): Promise<cheerio.CheerioAPI>;
135
142
  }>;
136
143
  private parseContent;
137
144
  private addHelpers;
138
145
  }
139
- interface EnqueueLinksInternalOptions {
140
- options?: EnqueueLinksOptions;
141
- window: Window | null;
142
- requestManager: IRequestManager;
143
- robotsTxtFile?: RobotsTxtFile;
144
- onSkippedRequest?: SkippedRequestCallback;
145
- originalRequestUrl: string;
146
- finalRequestUrl?: string;
147
- }
148
- interface BoundEnqueueLinksInternalOptions {
149
- enqueueLinks: BasicCrawlingContext['enqueueLinks'];
150
- options?: EnqueueLinksOptions;
151
- window: Window | null;
152
- originalRequestUrl: string;
153
- finalRequestUrl?: string;
154
- }
155
- /** @internal */
156
- export declare function linkedomCrawlerEnqueueLinks(options: EnqueueLinksInternalOptions | BoundEnqueueLinksInternalOptions): Promise<unknown>;
157
146
  /**
158
147
  * Creates new {@link Router} instance that works based on request labels.
159
148
  * This instance can then serve as a `requestHandler` of your {@link LinkeDOMCrawler}.
@@ -181,4 +170,3 @@ export declare function linkedomCrawlerEnqueueLinks(options: EnqueueLinksInterna
181
170
  export declare function createLinkeDOMRouter<Context extends LinkeDOMCrawlingContext = LinkeDOMCrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>>(routes?: RouterRoutes<Context, Routes>): RouterHandler<Context, Routes>;
182
171
  export declare function createLinkeDOMRouter<Context extends LinkeDOMCrawlingContext = LinkeDOMCrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, Record<string, UserData>>): RouterHandler<Context, Record<string, UserData>>;
183
172
  export declare function createLinkeDOMRouter<Context extends LinkeDOMCrawlingContext = LinkeDOMCrawlingContext, const Schemas extends RouteSchemas = RouteSchemas>(schemas: Schemas): RouterHandler<Context, RoutesFromSchemas<Schemas>>;
184
- export {};
@@ -1,4 +1,4 @@
1
- import { enqueueLinks, HttpCrawler, NavigationSkippedError, resolveBaseUrlForEnqueueLinksFiltering, Router, tryAbsoluteURL, } from '@crawlee/http';
1
+ import { EnqueueStrategy, HttpCrawler, NavigationSkippedError, resolveBaseUrlForEnqueueLinksFiltering, Router, tryAbsoluteURL, } from '@crawlee/http';
2
2
  import { sleep } from '@crawlee/utils';
3
3
  import * as cheerio from 'cheerio';
4
4
  import { DOMParser } from 'linkedom/cached';
@@ -75,8 +75,8 @@ import { DOMParser } from 'linkedom/cached';
75
75
  * @category Crawlers
76
76
  */
77
77
  export class LinkeDOMCrawler extends HttpCrawler {
78
- static parser = new DOMParser();
79
- constructor(options) {
78
+ static #parser = new DOMParser();
79
+ constructor(options = {}) {
80
80
  const { contextPipelineBuilder, ...rest } = options;
81
81
  super({
82
82
  ...rest,
@@ -94,7 +94,7 @@ export class LinkeDOMCrawler extends HttpCrawler {
94
94
  async parseContent(crawlingContext) {
95
95
  try {
96
96
  const isXml = crawlingContext.contentType.type.includes('xml');
97
- const document = LinkeDOMCrawler.parser.parseFromString(crawlingContext.body.toString(), isXml ? 'text/xml' : 'text/html');
97
+ const document = LinkeDOMCrawler.#parser.parseFromString(crawlingContext.body.toString(), isXml ? 'text/xml' : 'text/html');
98
98
  return {
99
99
  window: document.defaultView,
100
100
  get body() {
@@ -124,19 +124,27 @@ export class LinkeDOMCrawler extends HttpCrawler {
124
124
  }
125
125
  }
126
126
  async addHelpers(crawlingContext) {
127
+ const addRequests = crawlingContext.addRequests;
128
+ const extractLinks = async (options) => {
129
+ if (!crawlingContext.window) {
130
+ throw new Error('Cannot extract links because the DOM is not available.');
131
+ }
132
+ return extractUrlsFromWindow(crawlingContext.window, options?.selector ?? 'a', options?.baseUrl ?? crawlingContext.request.loadedUrl ?? crawlingContext.request.url);
133
+ };
127
134
  return {
128
- enqueueLinks: async (enqueueOptions) => {
129
- return linkedomCrawlerEnqueueLinks({
130
- options: {
131
- ...enqueueOptions,
132
- limit: await this.calculateEnqueuedRequestLimit(enqueueOptions?.limit),
133
- },
134
- window: document.defaultView,
135
- requestManager: await this.getRequestManager(),
136
- robotsTxtFile: await this.getRobotsTxtFileForUrl(crawlingContext.request.url),
137
- onSkippedRequest: this.handleSkippedRequest,
138
- originalRequestUrl: crawlingContext.request.url,
135
+ extractLinks,
136
+ enqueueLinks: async (options = {}) => {
137
+ const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
138
+ enqueueStrategy: options.strategy,
139
139
  finalRequestUrl: crawlingContext.request.loadedUrl,
140
+ originalRequestUrl: crawlingContext.request.url,
141
+ userProvidedBaseUrl: options.baseUrl,
142
+ });
143
+ const urls = await extractLinks(options);
144
+ return addRequests(urls, {
145
+ ...options,
146
+ baseUrl,
147
+ strategy: options.strategy ?? EnqueueStrategy.SameHostname,
140
148
  });
141
149
  },
142
150
  async waitForSelector(selector, timeoutMs = 5_000) {
@@ -160,39 +168,6 @@ export class LinkeDOMCrawler extends HttpCrawler {
160
168
  };
161
169
  }
162
170
  }
163
- /** @internal */
164
- function containsEnqueueLinks(options) {
165
- return !!options.enqueueLinks;
166
- }
167
- /** @internal */
168
- export async function linkedomCrawlerEnqueueLinks(options) {
169
- const { options: enqueueLinksOptions, window, originalRequestUrl, finalRequestUrl } = options;
170
- if (!window) {
171
- throw new Error('Cannot enqueue links because the DOM is not available.');
172
- }
173
- const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
174
- enqueueStrategy: enqueueLinksOptions?.strategy,
175
- finalRequestUrl,
176
- originalRequestUrl,
177
- userProvidedBaseUrl: enqueueLinksOptions?.baseUrl,
178
- });
179
- const urls = extractUrlsFromWindow(window, enqueueLinksOptions?.selector ?? 'a', enqueueLinksOptions?.baseUrl ?? finalRequestUrl ?? originalRequestUrl);
180
- if (containsEnqueueLinks(options)) {
181
- return options.enqueueLinks({
182
- urls,
183
- baseUrl,
184
- ...enqueueLinksOptions,
185
- });
186
- }
187
- return enqueueLinks({
188
- requestManager: options.requestManager,
189
- robotsTxtFile: options.robotsTxtFile,
190
- onSkippedRequest: options.onSkippedRequest,
191
- urls,
192
- baseUrl,
193
- ...enqueueLinksOptions,
194
- });
195
- }
196
171
  /**
197
172
  * Extracts URLs from a given Window object.
198
173
  * @ignore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/linkedom",
3
- "version": "4.0.0-beta.98",
3
+ "version": "4.0.0-rc.0",
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"
@@ -49,12 +49,11 @@
49
49
  "dependencies": {
50
50
  "@apify/timeout": "^0.4.4",
51
51
  "@apify/utilities": "^2.15.5",
52
- "@crawlee/http": "4.0.0-beta.98",
53
- "@crawlee/types": "4.0.0-beta.98",
54
- "@crawlee/utils": "4.0.0-beta.98",
52
+ "@crawlee/http": "4.0.0-rc.0",
53
+ "@crawlee/types": "4.0.0-rc.0",
54
+ "@crawlee/utils": "4.0.0-rc.0",
55
55
  "cheerio": "^1.0.0",
56
56
  "linkedom": "^0.18.10",
57
- "ow": "^2.0.0",
58
57
  "tslib": "^2.8.1"
59
58
  },
60
59
  "lerna": {
@@ -64,5 +63,5 @@
64
63
  }
65
64
  }
66
65
  },
67
- "gitHead": "3b8cd86b13e253ab5fc71e631e12a68f7465cee5"
66
+ "gitHead": "79ab33dacdacb83e0197e6516d145f3aceef80c7"
68
67
  }