@crawlee/linkedom 4.0.0-beta.99 → 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.
- package/internals/linkedom-crawler.d.ts +16 -28
- package/internals/linkedom-crawler.js +23 -48
- package/package.json +5 -6
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
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
|
|
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']
|
|
124
|
-
private
|
|
125
|
-
constructor(options
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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-
|
|
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-
|
|
53
|
-
"@crawlee/types": "4.0.0-
|
|
54
|
-
"@crawlee/utils": "4.0.0-
|
|
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": "
|
|
66
|
+
"gitHead": "79ab33dacdacb83e0197e6516d145f3aceef80c7"
|
|
68
67
|
}
|