@crawlee/linkedom 4.0.0-beta.167 → 4.0.0-beta.169
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/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { CrawlingContext, DOMCrawlingContext, ErrorHandler, GetUserDataFromRequest, HttpCrawlerOptions, InternalHttpHook, RequestHandler, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/http';
|
|
2
|
+
import { DOMCrawler } from '@crawlee/http';
|
|
3
3
|
import type { Dictionary } from '@crawlee/types';
|
|
4
|
-
import type {
|
|
4
|
+
import type { LinkeDOMParseResult } from './linkedom-parser.js';
|
|
5
5
|
export type LinkeDOMErrorHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
6
6
|
JSONData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
7
7
|
ContextExtension = Dictionary<never>> = ErrorHandler<CrawlingContext, LinkeDOMCrawlingContext<UserData, JSONData> & ContextExtension>;
|
|
@@ -12,44 +12,7 @@ Routes extends Record<keyof Routes, Dictionary> = Record<string, UserData>, Stat
|
|
|
12
12
|
export type LinkeDOMHook<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
13
13
|
JSONData extends Dictionary = any> = InternalHttpHook<LinkeDOMCrawlingContext<UserData, JSONData>>;
|
|
14
14
|
export interface LinkeDOMCrawlingContext<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
15
|
-
JSONData extends Dictionary = any> extends
|
|
16
|
-
window: Window;
|
|
17
|
-
document: Document;
|
|
18
|
-
/**
|
|
19
|
-
* Wait for an element matching the selector to appear.
|
|
20
|
-
* Timeout defaults to 5s.
|
|
21
|
-
*
|
|
22
|
-
* **Example usage:**
|
|
23
|
-
* ```ts
|
|
24
|
-
* async requestHandler({ waitForSelector, parseWithCheerio }) {
|
|
25
|
-
* await waitForSelector('article h1');
|
|
26
|
-
* const $ = await parseWithCheerio();
|
|
27
|
-
* const title = $('title').text();
|
|
28
|
-
* });
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
waitForSelector(selector: string, timeoutMs?: number): Promise<void>;
|
|
32
|
-
/**
|
|
33
|
-
* Returns Cheerio handle, allowing to work with the data same way as with {@link CheerioCrawler}.
|
|
34
|
-
* When provided with the `selector` argument, it will first look for the selector with a 5s timeout.
|
|
35
|
-
*
|
|
36
|
-
* **Example usage:**
|
|
37
|
-
* ```javascript
|
|
38
|
-
* async requestHandler({ parseWithCheerio }) {
|
|
39
|
-
* const $ = await parseWithCheerio();
|
|
40
|
-
* const title = $('title').text();
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
parseWithCheerio(selector?: string, timeoutMs?: number): Promise<CheerioAPI>;
|
|
45
|
-
/**
|
|
46
|
-
* Extracts URLs from the parsed DOM, without adding them to the request queue.
|
|
47
|
-
*/
|
|
48
|
-
extractLinks(options?: ExtractLinksOptions): Promise<string[]>;
|
|
49
|
-
/**
|
|
50
|
-
* Helper function for extracting URLs from the parsed DOM and adding them to the request queue.
|
|
51
|
-
*/
|
|
52
|
-
enqueueLinks(options?: EnqueueLinksOptions): Promise<AddRequestsBatchedResult>;
|
|
15
|
+
JSONData extends Dictionary = any> extends DOMCrawlingContext<LinkeDOMParseResult, UserData, JSONData> {
|
|
53
16
|
}
|
|
54
17
|
export type LinkeDOMRequestHandler<UserData extends Dictionary = any, // with default to Dictionary we cant use a typed router in untyped crawler
|
|
55
18
|
JSONData extends Dictionary = any> = RequestHandler<LinkeDOMCrawlingContext<UserData, JSONData>>;
|
|
@@ -125,12 +88,8 @@ JSONData extends Dictionary = any> = RequestHandler<LinkeDOMCrawlingContext<User
|
|
|
125
88
|
* ```
|
|
126
89
|
* @category Crawlers
|
|
127
90
|
*/
|
|
128
|
-
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
|
|
129
|
-
#private;
|
|
91
|
+
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 DOMCrawler<LinkeDOMParseResult, ContextExtension, ExtendedContext, Routes, StatisticStateExtension> {
|
|
130
92
|
constructor(options?: LinkeDOMCrawlerOptions<ContextExtension, ExtendedContext, any, any, Routes, StatisticStateExtension>);
|
|
131
|
-
protected buildContextPipeline(): ContextPipeline<CrawlingContext, LinkeDOMCrawlingContext>;
|
|
132
|
-
private parseContent;
|
|
133
|
-
private addHelpers;
|
|
134
93
|
}
|
|
135
94
|
/**
|
|
136
95
|
* Creates new {@link Router} instance that works based on request labels.
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { tryAbsoluteURL } from '@crawlee/utils/internal';
|
|
4
|
-
import * as cheerio from 'cheerio';
|
|
5
|
-
import { DOMParser } from 'linkedom/cached';
|
|
1
|
+
import { DOMCrawler, Router } from '@crawlee/http';
|
|
2
|
+
import { linkedomParser } from './linkedom-parser.js';
|
|
6
3
|
/**
|
|
7
4
|
* Provides a framework for the parallel crawling of web pages using plain HTTP requests and
|
|
8
5
|
* [linkedom](https://www.npmjs.com/package/linkedom) LinkeDOM implementation.
|
|
@@ -75,115 +72,10 @@ import { DOMParser } from 'linkedom/cached';
|
|
|
75
72
|
* ```
|
|
76
73
|
* @category Crawlers
|
|
77
74
|
*/
|
|
78
|
-
export class LinkeDOMCrawler extends
|
|
79
|
-
static #parser = new DOMParser();
|
|
75
|
+
export class LinkeDOMCrawler extends DOMCrawler {
|
|
80
76
|
constructor(options = {}) {
|
|
81
|
-
|
|
82
|
-
super({
|
|
83
|
-
...rest,
|
|
84
|
-
contextPipelineBuilder: contextPipelineBuilder ?? (() => this.buildContextPipeline()),
|
|
85
|
-
});
|
|
77
|
+
super({ ...options, parser: linkedomParser() });
|
|
86
78
|
}
|
|
87
|
-
buildContextPipeline() {
|
|
88
|
-
return super
|
|
89
|
-
.buildContextPipeline()
|
|
90
|
-
.compose({
|
|
91
|
-
action: async (context) => this.parseContent(context),
|
|
92
|
-
})
|
|
93
|
-
.compose({ action: async (context) => this.addHelpers(context) });
|
|
94
|
-
}
|
|
95
|
-
async parseContent(crawlingContext) {
|
|
96
|
-
try {
|
|
97
|
-
const isXml = crawlingContext.contentType.type.includes('xml');
|
|
98
|
-
const document = LinkeDOMCrawler.#parser.parseFromString(crawlingContext.body.toString(), isXml ? 'text/xml' : 'text/html');
|
|
99
|
-
return {
|
|
100
|
-
window: document.defaultView,
|
|
101
|
-
get body() {
|
|
102
|
-
return document.documentElement.outerHTML;
|
|
103
|
-
},
|
|
104
|
-
get document() {
|
|
105
|
-
// See comment about typing in LinkeDOMCrawlingContext definition
|
|
106
|
-
return document;
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
if (err instanceof NavigationSkippedError) {
|
|
112
|
-
return {
|
|
113
|
-
get window() {
|
|
114
|
-
throw new NavigationSkippedError('The `window` property is not available - `skipNavigation` was used', { cause: err });
|
|
115
|
-
},
|
|
116
|
-
get body() {
|
|
117
|
-
throw new NavigationSkippedError('The `body` property is not available - `skipNavigation` was used', { cause: err });
|
|
118
|
-
},
|
|
119
|
-
get document() {
|
|
120
|
-
throw new NavigationSkippedError('The `document` property is not available - `skipNavigation` was used', { cause: err });
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
throw err;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
async addHelpers(crawlingContext) {
|
|
128
|
-
const addRequests = crawlingContext.addRequests;
|
|
129
|
-
const extractLinks = async (options) => {
|
|
130
|
-
if (!crawlingContext.window) {
|
|
131
|
-
throw new Error('Cannot extract links because the DOM is not available.');
|
|
132
|
-
}
|
|
133
|
-
return extractUrlsFromWindow(crawlingContext.window, options?.selector ?? 'a', options?.baseUrl ?? crawlingContext.request.loadedUrl ?? crawlingContext.request.url);
|
|
134
|
-
};
|
|
135
|
-
return {
|
|
136
|
-
extractLinks,
|
|
137
|
-
enqueueLinks: async (options = {}) => {
|
|
138
|
-
const baseUrl = resolveBaseUrlForEnqueueLinksFiltering({
|
|
139
|
-
enqueueStrategy: options.strategy,
|
|
140
|
-
finalRequestUrl: crawlingContext.request.loadedUrl,
|
|
141
|
-
originalRequestUrl: crawlingContext.request.url,
|
|
142
|
-
userProvidedBaseUrl: options.baseUrl,
|
|
143
|
-
});
|
|
144
|
-
const urls = await extractLinks(options);
|
|
145
|
-
return addRequests(urls, {
|
|
146
|
-
...options,
|
|
147
|
-
baseUrl,
|
|
148
|
-
strategy: options.strategy ?? EnqueueStrategy.SameHostname,
|
|
149
|
-
});
|
|
150
|
-
},
|
|
151
|
-
async waitForSelector(selector, timeoutMs = 5_000) {
|
|
152
|
-
const $ = cheerio.load(crawlingContext.body);
|
|
153
|
-
if ($(selector).get().length === 0) {
|
|
154
|
-
if (timeoutMs) {
|
|
155
|
-
await sleep(50);
|
|
156
|
-
await this.waitForSelector(selector, Math.max(timeoutMs - 50, 0));
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
throw new Error(`Selector '${selector}' not found.`);
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
async parseWithCheerio(selector, _timeoutMs = 5_000) {
|
|
163
|
-
const $ = cheerio.load(crawlingContext.body);
|
|
164
|
-
if (selector && $(selector).get().length === 0) {
|
|
165
|
-
throw new Error(`Selector '${selector}' not found.`);
|
|
166
|
-
}
|
|
167
|
-
return $;
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Extracts URLs from a given Window object.
|
|
174
|
-
* @ignore
|
|
175
|
-
*/
|
|
176
|
-
function extractUrlsFromWindow(window, selector, baseUrl) {
|
|
177
|
-
return Array.from(window.document.querySelectorAll(selector))
|
|
178
|
-
.map((e) => e.href)
|
|
179
|
-
.filter((href) => href !== undefined && href !== '')
|
|
180
|
-
.map((href) => {
|
|
181
|
-
if (href === undefined) {
|
|
182
|
-
return undefined;
|
|
183
|
-
}
|
|
184
|
-
return tryAbsoluteURL(href, baseUrl);
|
|
185
|
-
})
|
|
186
|
-
.filter((href) => href !== undefined && href !== '');
|
|
187
79
|
}
|
|
188
80
|
export function createLinkeDOMRouter(routesOrSchemas) {
|
|
189
81
|
return Router.create(routesOrSchemas);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DOMParser } from '@crawlee/http';
|
|
2
|
+
export interface LinkeDOMParseResult {
|
|
3
|
+
window: Window;
|
|
4
|
+
document: Document;
|
|
5
|
+
body: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A {@link DOMParser} backed by [linkedom](https://www.npmjs.com/package/linkedom). Pass it to a
|
|
9
|
+
* {@link DOMCrawler} to get the crawling context {@link LinkeDOMCrawler} provides.
|
|
10
|
+
*/
|
|
11
|
+
export declare function linkedomParser(): DOMParser<LinkeDOMParseResult>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { tryAbsoluteURL } from '@crawlee/utils/internal';
|
|
2
|
+
import { DOMParser as LinkeDOMParser } from 'linkedom/cached';
|
|
3
|
+
/**
|
|
4
|
+
* A {@link DOMParser} backed by [linkedom](https://www.npmjs.com/package/linkedom). Pass it to a
|
|
5
|
+
* {@link DOMCrawler} to get the crawling context {@link LinkeDOMCrawler} provides.
|
|
6
|
+
*/
|
|
7
|
+
export function linkedomParser() {
|
|
8
|
+
const parser = new LinkeDOMParser();
|
|
9
|
+
return {
|
|
10
|
+
placeholderMembers: { window: true, document: true, body: true },
|
|
11
|
+
parse(context) {
|
|
12
|
+
const isXml = context.contentType.type.includes('xml');
|
|
13
|
+
const document = parser.parseFromString(context.body.toString(), isXml ? 'text/xml' : 'text/html');
|
|
14
|
+
return {
|
|
15
|
+
window: document.defaultView,
|
|
16
|
+
get body() {
|
|
17
|
+
return document.documentElement.outerHTML;
|
|
18
|
+
},
|
|
19
|
+
get document() {
|
|
20
|
+
// See comment about typing in LinkeDOMParseResult definition
|
|
21
|
+
return document;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
extractLinks: ({ window }, selector, baseUrl) => extractUrlsFromWindow(window, selector, baseUrl),
|
|
26
|
+
select: ({ document }, selector) => document.querySelectorAll(selector),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Extracts URLs from a given Window object.
|
|
31
|
+
* @ignore
|
|
32
|
+
*/
|
|
33
|
+
function extractUrlsFromWindow(window, selector, baseUrl) {
|
|
34
|
+
return Array.from(window.document.querySelectorAll(selector))
|
|
35
|
+
.map((e) => e.href)
|
|
36
|
+
.filter((href) => href !== undefined && href !== '')
|
|
37
|
+
.map((href) => {
|
|
38
|
+
if (href === undefined) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return tryAbsoluteURL(href, baseUrl);
|
|
42
|
+
})
|
|
43
|
+
.filter((href) => href !== undefined && href !== '');
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crawlee/linkedom",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.169",
|
|
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,9 +49,9 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apify/timeout": "^1.0.1",
|
|
51
51
|
"@apify/utilities": "^3.0.1",
|
|
52
|
-
"@crawlee/http": "4.0.0-beta.
|
|
53
|
-
"@crawlee/types": "4.0.0-beta.
|
|
54
|
-
"@crawlee/utils": "4.0.0-beta.
|
|
52
|
+
"@crawlee/http": "4.0.0-beta.169",
|
|
53
|
+
"@crawlee/types": "4.0.0-beta.169",
|
|
54
|
+
"@crawlee/utils": "4.0.0-beta.169",
|
|
55
55
|
"cheerio": "^1.0.0",
|
|
56
56
|
"linkedom": "^0.18.10",
|
|
57
57
|
"tslib": "^2.8.1"
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "a42dc93fa0ef68180d83ab12af362082690f73e2"
|
|
67
67
|
}
|