@detiq/crawler 1.1.9 → 1.1.11
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/dist/accessibility-auditor.js +54 -74
- package/dist/ai-login.js +227 -249
- package/dist/auth-token-rotator.js +57 -67
- package/dist/auth.js +299 -336
- package/dist/browser-extension.js +104 -142
- package/dist/captcha-solver.js +98 -125
- package/dist/color-contrast.js +77 -89
- package/dist/content-chunker.js +126 -126
- package/dist/content-dedup.js +54 -71
- package/dist/cookie-consent.js +68 -83
- package/dist/cookie-jar.js +53 -60
- package/dist/crawler.js +4494 -5353
- package/dist/custom-js-injector.js +45 -61
- package/dist/dark-mode-capture.js +67 -81
- package/dist/dead-link-detector.js +47 -55
- package/dist/element-hash.js +8 -12
- package/dist/email-otp.js +154 -195
- package/dist/engine-router.js +87 -98
- package/dist/export/bulk-export.js +70 -55
- package/dist/export/vector-export.js +77 -105
- package/dist/extractors/advanced.js +249 -266
- package/dist/extractors/artifact-envelope.js +59 -58
- package/dist/failure-analysis-agent.js +37 -33
- package/dist/feature-flag-detector.js +51 -66
- package/dist/form-detector.js +55 -75
- package/dist/frontier.js +76 -101
- package/dist/gap-pass.js +68 -89
- package/dist/graphql-detector.js +62 -72
- package/dist/http-engine.js +282 -330
- package/dist/incremental-diff.js +54 -87
- package/dist/index.js +67 -28
- package/dist/input-fuzzer.js +93 -97
- package/dist/interactive-probe.js +715 -827
- package/dist/job-queue.js +2151 -2169
- package/dist/js-coverage.js +52 -56
- package/dist/js-router-detector.js +86 -117
- package/dist/language-detector.js +51 -63
- package/dist/link-classifier.js +32 -50
- package/dist/live-view.js +91 -108
- package/dist/locator-extractor.js +151 -187
- package/dist/logger.js +9 -7
- package/dist/markdown-extract.js +48 -61
- package/dist/native-crawler.js +780 -946
- package/dist/network-observer.js +135 -156
- package/dist/network-throttle.js +29 -40
- package/dist/openapi-generator.js +77 -92
- package/dist/pdf-extractor.js +72 -90
- package/dist/performance-metrics.js +80 -102
- package/dist/phash.js +125 -144
- package/dist/pii-scrubber.js +65 -68
- package/dist/playwright-recorder.js +70 -58
- package/dist/policies/crawl-safety.js +31 -34
- package/dist/profiles/domain-profile.js +130 -125
- package/dist/proxy.js +146 -172
- package/dist/rate-limiter.js +58 -65
- package/dist/redirect-tracker.js +60 -64
- package/dist/redis-frontier.js +116 -136
- package/dist/redis-queue.js +157 -144
- package/dist/response-cache.js +127 -140
- package/dist/responsive-detector.js +42 -46
- package/dist/retry-policy.js +46 -50
- package/dist/robots-txt.js +69 -83
- package/dist/screen-reader.js +45 -70
- package/dist/screenshot-masker.js +59 -77
- package/dist/seo-checker.js +88 -107
- package/dist/session-capture.js +192 -212
- package/dist/session-export.js +107 -133
- package/dist/sitemap-writer.js +38 -41
- package/dist/sitemap.js +141 -202
- package/dist/skyvern.js +123 -148
- package/dist/sqs-queue.js +42 -42
- package/dist/sso-token.js +74 -90
- package/dist/stealth/human-behavior.js +156 -173
- package/dist/telemetry.js +68 -82
- package/dist/test-executor.js +309 -335
- package/dist/tls-fingerprint.js +77 -88
- package/dist/url-pattern-completer.js +71 -90
- package/dist/visual-diff-verdict.js +23 -38
- package/dist/webhook-notifier.js +71 -69
- package/dist/websocket-detector.js +56 -68
- package/dist/worker.js +29 -38
- package/package.json +1 -1
- package/src/job-queue.ts +136 -0
|
@@ -1,282 +1,265 @@
|
|
|
1
|
-
import { parseHTML } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return { matches: [], count: 0, method: 'css-selector-error' };
|
|
15
|
-
}
|
|
1
|
+
import { parseHTML } from "linkedom";
|
|
2
|
+
function extractByCssSelector(html, selector, attr) {
|
|
3
|
+
try {
|
|
4
|
+
const { document } = parseHTML(html);
|
|
5
|
+
const nodes = Array.from(document.querySelectorAll(selector));
|
|
6
|
+
const matches = nodes.map((n) => {
|
|
7
|
+
if (attr) return n.getAttribute(attr) ?? "";
|
|
8
|
+
return n.textContent?.trim() ?? "";
|
|
9
|
+
}).filter(Boolean);
|
|
10
|
+
return { matches, count: matches.length, method: "css-selector" };
|
|
11
|
+
} catch (e) {
|
|
12
|
+
return { matches: [], count: 0, method: "css-selector-error" };
|
|
13
|
+
}
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
node = result.iterateNext?.();
|
|
28
|
-
}
|
|
29
|
-
return { matches, count: matches.length, method: 'xpath' };
|
|
15
|
+
function extractByXPath(html, xpath) {
|
|
16
|
+
try {
|
|
17
|
+
const { document, XPathResult } = parseHTML(html);
|
|
18
|
+
const result = document.evaluate(xpath, document, null, XPathResult?.ANY_TYPE ?? 0, null);
|
|
19
|
+
const matches = [];
|
|
20
|
+
let node = result.iterateNext?.();
|
|
21
|
+
while (node) {
|
|
22
|
+
const text = node.textContent?.trim() ?? node.nodeValue?.trim() ?? "";
|
|
23
|
+
if (text) matches.push(text);
|
|
24
|
+
node = result.iterateNext?.();
|
|
30
25
|
}
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
return { matches, count: matches.length, method: "xpath" };
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return { matches: [], count: 0, method: "xpath-error" };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function extractByRegex(text, pattern, flags) {
|
|
32
|
+
try {
|
|
33
|
+
const re = new RegExp(pattern, (flags ?? "g").includes("g") ? flags : (flags ?? "") + "g");
|
|
34
|
+
const matches = [];
|
|
35
|
+
let m;
|
|
36
|
+
while ((m = re.exec(text)) !== null) {
|
|
37
|
+
matches.push(m[1] ?? m[0]);
|
|
38
|
+
if (!re.global) break;
|
|
33
39
|
}
|
|
40
|
+
return { matches, count: matches.length, method: "regex" };
|
|
41
|
+
} catch {
|
|
42
|
+
return { matches: [], count: 0, method: "regex-error" };
|
|
43
|
+
}
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
function extractTables(html) {
|
|
46
|
+
try {
|
|
47
|
+
const { document } = parseHTML(html);
|
|
48
|
+
const tables = Array.from(document.querySelectorAll("table"));
|
|
49
|
+
return tables.map((table) => {
|
|
50
|
+
const headers = [];
|
|
51
|
+
const rows = [];
|
|
52
|
+
const ths = [...table.querySelectorAll("thead th, thead td, tr:first-child th")];
|
|
53
|
+
if (ths.length > 0) {
|
|
54
|
+
ths.forEach((th, i) => headers.push(th.textContent?.trim() || `col${i + 1}`));
|
|
55
|
+
}
|
|
56
|
+
const trs = [...table.querySelectorAll("tbody tr, tr")];
|
|
57
|
+
for (const tr of trs) {
|
|
58
|
+
const cells = [...tr.querySelectorAll("td, th")];
|
|
59
|
+
if (cells.length === 0) continue;
|
|
60
|
+
if (headers.length === 0) {
|
|
61
|
+
cells.forEach((_, i) => headers.push(`col${i + 1}`));
|
|
44
62
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
const row = {};
|
|
64
|
+
cells.forEach((cell, i) => {
|
|
65
|
+
const key = headers[i] || `col${i + 1}`;
|
|
66
|
+
row[key] = cell.textContent?.trim() ?? "";
|
|
67
|
+
});
|
|
68
|
+
if (Object.values(row).some((v) => v)) rows.push(row);
|
|
69
|
+
}
|
|
70
|
+
return rows;
|
|
71
|
+
}).filter((t) => t.length > 0);
|
|
72
|
+
} catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
50
75
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
function extractMetadata(html, url) {
|
|
77
|
+
const meta = {
|
|
78
|
+
title: null,
|
|
79
|
+
description: null,
|
|
80
|
+
keywords: null,
|
|
81
|
+
ogTitle: null,
|
|
82
|
+
ogDescription: null,
|
|
83
|
+
ogImage: null,
|
|
84
|
+
ogUrl: null,
|
|
85
|
+
ogType: null,
|
|
86
|
+
ogSiteName: null,
|
|
87
|
+
ogLocale: null,
|
|
88
|
+
twitterCard: null,
|
|
89
|
+
twitterTitle: null,
|
|
90
|
+
twitterDescription: null,
|
|
91
|
+
twitterImage: null,
|
|
92
|
+
twitterSite: null,
|
|
93
|
+
canonical: null,
|
|
94
|
+
robots: null,
|
|
95
|
+
hreflang: [],
|
|
96
|
+
themeColor: null,
|
|
97
|
+
favicon: null,
|
|
98
|
+
jsonLd: [],
|
|
99
|
+
schemaTypes: [],
|
|
100
|
+
lang: null
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
const { document } = parseHTML(html);
|
|
104
|
+
meta.title = document.title ?? document.querySelector("title")?.textContent?.trim() ?? null;
|
|
105
|
+
meta.lang = document.documentElement?.getAttribute("lang") ?? null;
|
|
106
|
+
const metas = [...document.querySelectorAll("meta")];
|
|
107
|
+
for (const m of metas) {
|
|
108
|
+
const name = m.getAttribute("name")?.toLowerCase() ?? "";
|
|
109
|
+
const prop = m.getAttribute("property")?.toLowerCase() ?? "";
|
|
110
|
+
const content = m.getAttribute("content") ?? "";
|
|
111
|
+
if (name === "description") meta.description = content;
|
|
112
|
+
if (name === "keywords") meta.keywords = content;
|
|
113
|
+
if (name === "robots") meta.robots = content;
|
|
114
|
+
if (name === "twitter:card") meta.twitterCard = content;
|
|
115
|
+
if (name === "twitter:title") meta.twitterTitle = content;
|
|
116
|
+
if (name === "twitter:description") meta.twitterDescription = content;
|
|
117
|
+
if (name === "twitter:image") meta.twitterImage = content;
|
|
118
|
+
if (name === "twitter:site") meta.twitterSite = content;
|
|
119
|
+
if (name === "theme-color") meta.themeColor = content;
|
|
120
|
+
if (prop === "og:title") meta.ogTitle = content;
|
|
121
|
+
if (prop === "og:description") meta.ogDescription = content;
|
|
122
|
+
if (prop === "og:image") meta.ogImage = content;
|
|
123
|
+
if (prop === "og:url") meta.ogUrl = content;
|
|
124
|
+
if (prop === "og:type") meta.ogType = content;
|
|
125
|
+
if (prop === "og:site_name") meta.ogSiteName = content;
|
|
126
|
+
if (prop === "og:locale") meta.ogLocale = content;
|
|
80
127
|
}
|
|
81
|
-
|
|
82
|
-
|
|
128
|
+
const canonical = document.querySelector('link[rel="canonical"]');
|
|
129
|
+
if (canonical) meta.canonical = canonical.getAttribute("href") ?? null;
|
|
130
|
+
const faviconEl = document.querySelector('link[rel="icon"], link[rel="shortcut icon"]');
|
|
131
|
+
if (faviconEl) meta.favicon = faviconEl.getAttribute("href") ?? null;
|
|
132
|
+
const hreflangEls = [...document.querySelectorAll('link[rel="alternate"][hreflang]')];
|
|
133
|
+
for (const el of hreflangEls) {
|
|
134
|
+
const lang = el.getAttribute("hreflang") ?? "";
|
|
135
|
+
const href = el.getAttribute("href") ?? "";
|
|
136
|
+
if (lang && href) meta.hreflang.push({ lang, href });
|
|
83
137
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
hreflang: [], themeColor: null, favicon: null,
|
|
94
|
-
jsonLd: [], schemaTypes: [], lang: null,
|
|
95
|
-
};
|
|
96
|
-
try {
|
|
97
|
-
const { document } = parseHTML(html);
|
|
98
|
-
meta.title = document.title ?? document.querySelector('title')?.textContent?.trim() ?? null;
|
|
99
|
-
meta.lang = document.documentElement?.getAttribute('lang') ?? null;
|
|
100
|
-
const metas = [...document.querySelectorAll('meta')];
|
|
101
|
-
for (const m of metas) {
|
|
102
|
-
const name = m.getAttribute('name')?.toLowerCase() ?? '';
|
|
103
|
-
const prop = m.getAttribute('property')?.toLowerCase() ?? '';
|
|
104
|
-
const content = m.getAttribute('content') ?? '';
|
|
105
|
-
if (name === 'description')
|
|
106
|
-
meta.description = content;
|
|
107
|
-
if (name === 'keywords')
|
|
108
|
-
meta.keywords = content;
|
|
109
|
-
if (name === 'robots')
|
|
110
|
-
meta.robots = content;
|
|
111
|
-
if (name === 'twitter:card')
|
|
112
|
-
meta.twitterCard = content;
|
|
113
|
-
if (name === 'twitter:title')
|
|
114
|
-
meta.twitterTitle = content;
|
|
115
|
-
if (name === 'twitter:description')
|
|
116
|
-
meta.twitterDescription = content;
|
|
117
|
-
if (name === 'twitter:image')
|
|
118
|
-
meta.twitterImage = content;
|
|
119
|
-
if (name === 'twitter:site')
|
|
120
|
-
meta.twitterSite = content;
|
|
121
|
-
if (name === 'theme-color')
|
|
122
|
-
meta.themeColor = content;
|
|
123
|
-
if (prop === 'og:title')
|
|
124
|
-
meta.ogTitle = content;
|
|
125
|
-
if (prop === 'og:description')
|
|
126
|
-
meta.ogDescription = content;
|
|
127
|
-
if (prop === 'og:image')
|
|
128
|
-
meta.ogImage = content;
|
|
129
|
-
if (prop === 'og:url')
|
|
130
|
-
meta.ogUrl = content;
|
|
131
|
-
if (prop === 'og:type')
|
|
132
|
-
meta.ogType = content;
|
|
133
|
-
if (prop === 'og:site_name')
|
|
134
|
-
meta.ogSiteName = content;
|
|
135
|
-
if (prop === 'og:locale')
|
|
136
|
-
meta.ogLocale = content;
|
|
137
|
-
}
|
|
138
|
-
const canonical = document.querySelector('link[rel="canonical"]');
|
|
139
|
-
if (canonical)
|
|
140
|
-
meta.canonical = canonical.getAttribute('href') ?? null;
|
|
141
|
-
const faviconEl = document.querySelector('link[rel="icon"], link[rel="shortcut icon"]');
|
|
142
|
-
if (faviconEl)
|
|
143
|
-
meta.favicon = faviconEl.getAttribute('href') ?? null;
|
|
144
|
-
const hreflangEls = [...document.querySelectorAll('link[rel="alternate"][hreflang]')];
|
|
145
|
-
for (const el of hreflangEls) {
|
|
146
|
-
const lang = el.getAttribute('hreflang') ?? '';
|
|
147
|
-
const href = el.getAttribute('href') ?? '';
|
|
148
|
-
if (lang && href)
|
|
149
|
-
meta.hreflang.push({ lang, href });
|
|
150
|
-
}
|
|
151
|
-
const jsonLdScripts = [...document.querySelectorAll('script[type="application/ld+json"]')];
|
|
152
|
-
for (const s of jsonLdScripts) {
|
|
153
|
-
try {
|
|
154
|
-
const parsed = JSON.parse(s.textContent ?? '{}');
|
|
155
|
-
meta.jsonLd.push(parsed);
|
|
156
|
-
const types = Array.isArray(parsed) ? parsed.map((p) => p['@type']).flat() : [parsed['@type']];
|
|
157
|
-
meta.schemaTypes.push(...types.filter(Boolean));
|
|
158
|
-
}
|
|
159
|
-
catch { /* skip invalid JSON-LD */ }
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
catch { /* non-fatal */ }
|
|
163
|
-
return meta;
|
|
164
|
-
}
|
|
165
|
-
export function extractStructuredProduct(jsonLd) {
|
|
166
|
-
for (const item of jsonLd) {
|
|
167
|
-
try {
|
|
168
|
-
const type = item['@type'];
|
|
169
|
-
const isProduct = Array.isArray(type)
|
|
170
|
-
? type.some((t) => typeof t === 'string' && t.toLowerCase() === 'product')
|
|
171
|
-
: typeof type === 'string' && type.toLowerCase() === 'product';
|
|
172
|
-
if (!isProduct)
|
|
173
|
-
continue;
|
|
174
|
-
const i = item;
|
|
175
|
-
const offers = Array.isArray(i.offers) ? i.offers[0] : i.offers;
|
|
176
|
-
const rawPrice = offers?.price;
|
|
177
|
-
const price = rawPrice != null ? parseFloat(rawPrice) : NaN;
|
|
178
|
-
const rawRating = i.aggregateRating?.ratingValue;
|
|
179
|
-
const rating = rawRating != null ? parseFloat(rawRating) : NaN;
|
|
180
|
-
const rawReviewCount = i.aggregateRating?.reviewCount;
|
|
181
|
-
const reviewCount = rawReviewCount != null ? parseInt(rawReviewCount, 10) : NaN;
|
|
182
|
-
const rawAvailability = offers?.availability;
|
|
183
|
-
const availability = rawAvailability
|
|
184
|
-
? rawAvailability.replace(/^https?:\/\/schema\.org\//, '')
|
|
185
|
-
: null;
|
|
186
|
-
const rawImage = i.image;
|
|
187
|
-
const image = Array.isArray(rawImage) ? (rawImage[0] ?? null) : (rawImage ?? null);
|
|
188
|
-
const brand = i.brand?.name ?? i.brand ?? null;
|
|
189
|
-
return {
|
|
190
|
-
name: i.name ?? null,
|
|
191
|
-
description: i.description ?? null,
|
|
192
|
-
brand: typeof brand === 'string' ? brand : null,
|
|
193
|
-
sku: i.sku ?? null,
|
|
194
|
-
price: isNaN(price) ? null : price,
|
|
195
|
-
priceCurrency: offers?.priceCurrency ?? null,
|
|
196
|
-
availability,
|
|
197
|
-
rating: isNaN(rating) ? null : rating,
|
|
198
|
-
reviewCount: isNaN(reviewCount) ? null : reviewCount,
|
|
199
|
-
image,
|
|
200
|
-
url: i.url ?? null,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
catch { /* skip malformed item */ }
|
|
138
|
+
const jsonLdScripts = [...document.querySelectorAll('script[type="application/ld+json"]')];
|
|
139
|
+
for (const s of jsonLdScripts) {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(s.textContent ?? "{}");
|
|
142
|
+
meta.jsonLd.push(parsed);
|
|
143
|
+
const types = Array.isArray(parsed) ? parsed.map((p) => p["@type"]).flat() : [parsed["@type"]];
|
|
144
|
+
meta.schemaTypes.push(...types.filter(Boolean));
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
204
147
|
}
|
|
205
|
-
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
return meta;
|
|
206
151
|
}
|
|
207
|
-
|
|
152
|
+
function extractStructuredProduct(jsonLd) {
|
|
153
|
+
for (const item of jsonLd) {
|
|
208
154
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
155
|
+
const type = item["@type"];
|
|
156
|
+
const isProduct = Array.isArray(type) ? type.some((t) => typeof t === "string" && t.toLowerCase() === "product") : typeof type === "string" && type.toLowerCase() === "product";
|
|
157
|
+
if (!isProduct) continue;
|
|
158
|
+
const i = item;
|
|
159
|
+
const offers = Array.isArray(i.offers) ? i.offers[0] : i.offers;
|
|
160
|
+
const rawPrice = offers?.price;
|
|
161
|
+
const price = rawPrice != null ? parseFloat(rawPrice) : NaN;
|
|
162
|
+
const rawRating = i.aggregateRating?.ratingValue;
|
|
163
|
+
const rating = rawRating != null ? parseFloat(rawRating) : NaN;
|
|
164
|
+
const rawReviewCount = i.aggregateRating?.reviewCount;
|
|
165
|
+
const reviewCount = rawReviewCount != null ? parseInt(rawReviewCount, 10) : NaN;
|
|
166
|
+
const rawAvailability = offers?.availability;
|
|
167
|
+
const availability = rawAvailability ? rawAvailability.replace(/^https?:\/\/schema\.org\//, "") : null;
|
|
168
|
+
const rawImage = i.image;
|
|
169
|
+
const image = Array.isArray(rawImage) ? rawImage[0] ?? null : rawImage ?? null;
|
|
170
|
+
const brand = i.brand?.name ?? i.brand ?? null;
|
|
171
|
+
return {
|
|
172
|
+
name: i.name ?? null,
|
|
173
|
+
description: i.description ?? null,
|
|
174
|
+
brand: typeof brand === "string" ? brand : null,
|
|
175
|
+
sku: i.sku ?? null,
|
|
176
|
+
price: isNaN(price) ? null : price,
|
|
177
|
+
priceCurrency: offers?.priceCurrency ?? null,
|
|
178
|
+
availability,
|
|
179
|
+
rating: isNaN(rating) ? null : rating,
|
|
180
|
+
reviewCount: isNaN(reviewCount) ? null : reviewCount,
|
|
181
|
+
image,
|
|
182
|
+
url: i.url ?? null
|
|
183
|
+
};
|
|
184
|
+
} catch {
|
|
232
185
|
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
233
188
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
189
|
+
function extractImages(html, baseUrl) {
|
|
190
|
+
try {
|
|
191
|
+
const { document } = parseHTML(html);
|
|
192
|
+
const base = new URL(baseUrl);
|
|
193
|
+
return Array.from(document.querySelectorAll("img")).map((img) => {
|
|
194
|
+
const src = img.getAttribute("src") ?? img.getAttribute("data-src") ?? "";
|
|
195
|
+
if (!src) return null;
|
|
196
|
+
try {
|
|
197
|
+
return {
|
|
198
|
+
src: new URL(src, base).href,
|
|
199
|
+
alt: img.getAttribute("alt") ?? "",
|
|
200
|
+
width: img.getAttribute("width") ? parseInt(img.getAttribute("width")) : void 0,
|
|
201
|
+
height: img.getAttribute("height") ? parseInt(img.getAttribute("height")) : void 0
|
|
202
|
+
};
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}).filter((x) => x !== null);
|
|
207
|
+
} catch {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function extractMediaFiles(html, baseUrl) {
|
|
212
|
+
const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|ico|avif)(\?.*)?$/i;
|
|
213
|
+
const VIDEO_EXT = /\.(mp4|webm|ogv|mov|avi)(\?.*)?$/i;
|
|
214
|
+
const AUDIO_EXT = /\.(mp3|ogg|wav|flac|aac)(\?.*)?$/i;
|
|
215
|
+
const DOC_EXT = /\.(pdf|docx?|xlsx?|pptx?|csv|zip)(\?.*)?$/i;
|
|
216
|
+
function classify(url, tag) {
|
|
217
|
+
if (tag === "img" || IMAGE_EXT.test(url)) return "image";
|
|
218
|
+
if (tag === "video" || VIDEO_EXT.test(url)) return "video";
|
|
219
|
+
if (tag === "audio" || AUDIO_EXT.test(url)) return "audio";
|
|
220
|
+
if (DOC_EXT.test(url)) return "document";
|
|
221
|
+
return "document";
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const { document } = parseHTML(html);
|
|
225
|
+
const base = new URL(baseUrl);
|
|
226
|
+
const results = [];
|
|
227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
228
|
+
const selectors = ["img[src],video[src],audio[src],source[src]", "src"];
|
|
229
|
+
const attrPairs = [
|
|
230
|
+
["img", "src"],
|
|
231
|
+
["video", "src"],
|
|
232
|
+
["audio", "src"],
|
|
233
|
+
["source", "src"],
|
|
234
|
+
["a", "href"]
|
|
235
|
+
];
|
|
236
|
+
for (const [sel, attr] of attrPairs) {
|
|
237
|
+
for (const el of [...document.querySelectorAll(sel)]) {
|
|
238
|
+
const raw = el.getAttribute(attr) ?? "";
|
|
239
|
+
if (!raw) continue;
|
|
240
|
+
try {
|
|
241
|
+
const resolved = new URL(raw, base).href;
|
|
242
|
+
if (seen.has(resolved)) continue;
|
|
243
|
+
if (sel === "a" && !DOC_EXT.test(resolved) && !IMAGE_EXT.test(resolved) && !VIDEO_EXT.test(resolved) && !AUDIO_EXT.test(resolved)) continue;
|
|
244
|
+
seen.add(resolved);
|
|
245
|
+
results.push({ url: resolved, type: classify(resolved, sel), tag: sel });
|
|
246
|
+
} catch {
|
|
276
247
|
}
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
catch {
|
|
280
|
-
return [];
|
|
248
|
+
}
|
|
281
249
|
}
|
|
250
|
+
return results;
|
|
251
|
+
} catch {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
282
254
|
}
|
|
255
|
+
export {
|
|
256
|
+
extractByCssSelector,
|
|
257
|
+
extractByRegex,
|
|
258
|
+
extractByXPath,
|
|
259
|
+
extractImages,
|
|
260
|
+
extractMediaFiles,
|
|
261
|
+
extractMetadata,
|
|
262
|
+
extractStructuredProduct,
|
|
263
|
+
extractTables
|
|
264
|
+
};
|
|
265
|
+
//# sourceMappingURL=advanced.js.map
|