recker 1.0.79 → 1.0.80-next.70ea84d
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/browser/browser/mini.d.ts +2 -2
- package/dist/browser/browser/recker-mini.d.ts +8 -8
- package/dist/browser/browser/recker.d.ts +11 -8
- package/dist/browser/browser/recker.js +54 -6
- package/dist/browser/core/client.d.ts +15 -10
- package/dist/browser/core/client.js +54 -38
- package/dist/browser/index.iife.min.js +129 -130
- package/dist/browser/index.min.js +129 -130
- package/dist/browser/index.mini.iife.js +5697 -636
- package/dist/browser/index.mini.iife.min.js +47 -48
- package/dist/browser/index.mini.min.js +47 -48
- package/dist/browser/index.mini.umd.js +5697 -636
- package/dist/browser/index.mini.umd.min.js +47 -48
- package/dist/browser/index.umd.min.js +129 -130
- package/dist/browser/mini.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.d.ts +2 -2
- package/dist/browser/plugins/proxy-rotator.js +6 -28
- package/dist/browser/recker-mini.d.ts +8 -8
- package/dist/browser/recker.d.ts +11 -8
- package/dist/browser/recker.js +54 -6
- package/dist/browser/scrape/document.js +2 -2
- package/dist/browser/scrape/element.js +7 -1
- package/dist/browser/scrape/parser/nodes/html.js +1 -1
- package/dist/browser/scrape/spider.d.ts +52 -0
- package/dist/browser/scrape/spider.js +620 -38
- package/dist/browser/scrape/types.d.ts +2 -0
- package/dist/browser/search/google.d.ts +26 -1
- package/dist/browser/search/google.js +427 -45
- package/dist/browser/seo/analyzer.d.ts +1 -0
- package/dist/browser/seo/analyzer.js +144 -1
- package/dist/browser/seo/index.d.ts +1 -1
- package/dist/browser/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/browser/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/browser/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/browser/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/browser/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/browser/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/browser/seo/keyword-campaign-shared.js +59 -0
- package/dist/browser/seo/keyword-campaign.d.ts +4 -107
- package/dist/browser/seo/keyword-campaign.js +2 -380
- package/dist/browser/seo/keywords.js +5 -22
- package/dist/browser/seo/types.d.ts +19 -0
- package/dist/browser/transport/curl.d.ts +5 -1
- package/dist/browser/transport/curl.js +207 -50
- package/dist/browser/transport/undici.d.ts +4 -3
- package/dist/browser/transport/undici.js +123 -49
- package/dist/browser/types/index.d.ts +9 -3
- package/dist/browser/utils/binary-manager.js +26 -3
- package/dist/browser/utils/block-detector.d.ts +8 -0
- package/dist/browser/utils/block-detector.js +542 -7
- package/dist/cli/commands/hls-runner.js +5 -4
- package/dist/cli/commands/live-runner.js +5 -4
- package/dist/cli/commands/loadtest-runner.js +3 -2
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.js +105 -0
- package/dist/cli/commands/seo-runner.js +9 -7
- package/dist/cli/commands/seo.js +140 -1
- package/dist/cli/commands/serve.js +75 -131
- package/dist/cli/commands/server-runner.js +59 -82
- package/dist/cli/commands/spider-runner.d.ts +37 -1
- package/dist/cli/commands/spider-runner.js +134 -10
- package/dist/cli/commands/spider.d.ts +18 -1
- package/dist/cli/commands/spider.js +457 -27
- package/dist/cli/events/handlers/cli.js +30 -1
- package/dist/cli/events/handlers/tui.js +26 -0
- package/dist/cli/events/types.d.ts +27 -0
- package/dist/cli/handler.d.ts +2 -12
- package/dist/cli/handler.js +20 -15
- package/dist/cli/handlers/protocols.js +39 -12
- package/dist/cli/handlers/search.d.ts +2 -0
- package/dist/cli/handlers/search.js +171 -0
- package/dist/cli/handlers/seo-analyze.d.ts +1 -0
- package/dist/cli/handlers/seo-analyze.js +666 -0
- package/dist/cli/handlers/seo-robots.d.ts +1 -0
- package/dist/cli/handlers/seo-robots.js +76 -0
- package/dist/cli/handlers/seo-serp.d.ts +54 -0
- package/dist/cli/handlers/seo-serp.js +243 -0
- package/dist/cli/handlers/seo-sitemap.d.ts +1 -0
- package/dist/cli/handlers/seo-sitemap.js +55 -0
- package/dist/cli/handlers/seo-spider.d.ts +1 -0
- package/dist/cli/handlers/seo-spider.js +334 -0
- package/dist/cli/handlers/seo.d.ts +8 -4
- package/dist/cli/handlers/seo.js +294 -442
- package/dist/cli/handlers/spider.js +94 -17
- package/dist/cli/handlers/streaming.js +5 -1
- package/dist/cli/index.js +11 -2
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +15 -4
- package/dist/cli/tui/app.js +6 -1
- package/dist/cli/tui/components/rich-response.d.ts +72 -0
- package/dist/cli/tui/components/rich-response.js +117 -0
- package/dist/cli/tui/executor-commands/background.js +30 -24
- package/dist/cli/tui/executor-commands/testing.js +3 -2
- package/dist/cli/tui/hooks/useDomains.d.ts +17 -0
- package/dist/cli/tui/hooks/useHelp.js +15 -2
- package/dist/cli/tui/job-manager.d.ts +4 -4
- package/dist/cli/tui/job-manager.js +5 -1
- package/dist/cli/tui/spider-tui.d.ts +63 -0
- package/dist/cli/tui/spider-tui.js +120 -2
- package/dist/cli/types.d.ts +12 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils/option-helpers.d.ts +10 -0
- package/dist/cli/utils/option-helpers.js +63 -0
- package/dist/cli/utils/score-color.d.ts +11 -0
- package/dist/cli/utils/score-color.js +11 -0
- package/dist/cli/utils/serp-campaign.d.ts +53 -0
- package/dist/cli/utils/serp-campaign.js +53 -0
- package/dist/cli/utils/serp-config.d.ts +26 -0
- package/dist/cli/utils/serp-config.js +125 -0
- package/dist/core/client.d.ts +15 -10
- package/dist/core/client.js +54 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +35 -34
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +20 -2
- package/dist/mcp/contract.d.ts +1 -1
- package/dist/mcp/profiles.js +5 -1
- package/dist/mcp/prompts/index.js +8 -4
- package/dist/mcp/resources/index.js +46 -23
- package/dist/mcp/server.js +9 -6
- package/dist/mcp/tools/protocols.js +9 -2
- package/dist/mcp/tools/registry.js +13 -3
- package/dist/mcp/tools/seo.js +427 -2
- package/dist/mcp/types.d.ts +5 -1
- package/dist/plugins/proxy-rotator.d.ts +2 -2
- package/dist/plugins/proxy-rotator.js +6 -28
- package/dist/raffel/client.d.ts +38 -0
- package/dist/raffel/client.js +282 -0
- package/dist/raffel/index.d.ts +2 -0
- package/dist/raffel/index.js +2 -0
- package/dist/raffel/types.d.ts +40 -0
- package/dist/raffel/types.js +14 -0
- package/dist/recker.d.ts +13 -7
- package/dist/recker.js +58 -6
- package/dist/scrape/document.js +2 -2
- package/dist/scrape/element.js +7 -1
- package/dist/scrape/parser/nodes/html.js +1 -1
- package/dist/scrape/spider.d.ts +52 -0
- package/dist/scrape/spider.js +620 -38
- package/dist/scrape/types.d.ts +2 -0
- package/dist/search/google.d.ts +26 -1
- package/dist/search/google.js +427 -45
- package/dist/search/index.d.ts +1 -1
- package/dist/seo/analyzer.d.ts +1 -0
- package/dist/seo/analyzer.js +144 -1
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/keyword-campaign-analyzer.d.ts +2 -0
- package/dist/seo/keyword-campaign-analyzer.js +538 -0
- package/dist/seo/keyword-campaign-seed-advanced.d.ts +17 -0
- package/dist/seo/keyword-campaign-seed-advanced.js +269 -0
- package/dist/seo/keyword-campaign-seed-core.d.ts +29 -0
- package/dist/seo/keyword-campaign-seed-core.js +525 -0
- package/dist/seo/keyword-campaign-shared.d.ts +165 -0
- package/dist/seo/keyword-campaign-shared.js +59 -0
- package/dist/seo/keyword-campaign.d.ts +4 -107
- package/dist/seo/keyword-campaign.js +2 -380
- package/dist/seo/keywords.js +5 -22
- package/dist/seo/types.d.ts +19 -0
- package/dist/template/index.d.ts +1 -1
- package/dist/template/types.d.ts +0 -2
- package/dist/testing/index.d.ts +1 -22
- package/dist/testing/index.js +1 -11
- package/dist/transport/curl.d.ts +5 -1
- package/dist/transport/curl.js +207 -50
- package/dist/transport/undici.d.ts +4 -3
- package/dist/transport/undici.js +123 -49
- package/dist/types/index.d.ts +9 -3
- package/dist/utils/binary-manager.js +26 -3
- package/dist/utils/block-detector.d.ts +8 -0
- package/dist/utils/block-detector.js +542 -7
- package/dist/version.js +1 -1
- package/package.json +12 -1
- package/dist/testing/mock-dns-server.d.ts +0 -69
- package/dist/testing/mock-dns-server.js +0 -269
- package/dist/testing/mock-ftp-server.d.ts +0 -89
- package/dist/testing/mock-ftp-server.js +0 -562
- package/dist/testing/mock-hls-server.d.ts +0 -80
- package/dist/testing/mock-hls-server.js +0 -381
- package/dist/testing/mock-http-server.d.ts +0 -124
- package/dist/testing/mock-http-server.js +0 -343
- package/dist/testing/mock-proxy-server.d.ts +0 -108
- package/dist/testing/mock-proxy-server.js +0 -615
- package/dist/testing/mock-sse-server.d.ts +0 -76
- package/dist/testing/mock-sse-server.js +0 -291
- package/dist/testing/mock-telnet-server.d.ts +0 -59
- package/dist/testing/mock-telnet-server.js +0 -274
- package/dist/testing/mock-udp-server.d.ts +0 -43
- package/dist/testing/mock-udp-server.js +0 -188
- package/dist/testing/mock-websocket-server.d.ts +0 -76
- package/dist/testing/mock-websocket-server.js +0 -334
- package/dist/testing/mock-whois-server.d.ts +0 -56
- package/dist/testing/mock-whois-server.js +0 -234
- package/dist/testing/proxy-certs.d.ts +0 -19
- package/dist/testing/proxy-certs.js +0 -208
package/dist/search/google.js
CHANGED
|
@@ -2,9 +2,8 @@ import { createClient } from '../core/client.js';
|
|
|
2
2
|
import { ValidationError } from '../core/errors.js';
|
|
3
3
|
import { HttpRequest } from '../core/request.js';
|
|
4
4
|
import { ScrapeDocument } from '../scrape/document.js';
|
|
5
|
-
import { detectBlock } from '../utils/block-detector.js';
|
|
5
|
+
import { detectBlock, detectCaptcha, } from '../utils/block-detector.js';
|
|
6
6
|
import { getRandomUserAgent } from '../utils/user-agent.js';
|
|
7
|
-
const GOOGLE_SEARCH_BASE_URL = 'https://www.google.com/search';
|
|
8
7
|
const GOOGLE_SEARCH_ORIGIN = 'https://www.google.com';
|
|
9
8
|
const GOOGLE_RESULT_SNIPPET_SELECTOR_ORDER = [
|
|
10
9
|
'[data-sncf="1"]',
|
|
@@ -23,6 +22,18 @@ const GOOGLE_RESULT_LINK_SELECTORS = [
|
|
|
23
22
|
'a[href^="http://www.google.com/url?"]',
|
|
24
23
|
];
|
|
25
24
|
const GOOGLE_RESULT_CONTAINER_SELECTORS = '[data-hveid], [data-ved], div[class*="g"], div[class*="MjjY"], div[class*="tF2Cxc"], [class*="xpd"]';
|
|
25
|
+
const GOOGLE_BROWSER_PROFILE_GS_LCRP_VALUES = [
|
|
26
|
+
'EgZjaHJvbWUyBggAEEUYOdIBCDIwNzlqMGoxqAIAsAIA',
|
|
27
|
+
'EgZjaHJvbWUyBggAEEUYOdIBCDIwMjlqMGoxqAIAsAIA',
|
|
28
|
+
'EgZjaHJvbWUyBggAEEUYOdIBCDIwMzNqMGoxqAIAsAIA',
|
|
29
|
+
];
|
|
30
|
+
const GOOGLE_BROWSER_PROFILE_AQS_VALUES = [
|
|
31
|
+
'EgNQQxIEGAM%3D',
|
|
32
|
+
'EgZjaHJvbWUyBggAEEUYOdIB',
|
|
33
|
+
'EgYIARABGBQyGAM%3D',
|
|
34
|
+
];
|
|
35
|
+
const GOOGLE_SEARCH_PARAM_IE_VALUES = ['UTF-8', 'utf-8', 'UTF-8', 'utf-8'];
|
|
36
|
+
const GOOGLE_BROWSER_SEI_LENGTH = 16;
|
|
26
37
|
const GOOGLE_AD_CONTAINER_CLASS_HINTS = [
|
|
27
38
|
'ad',
|
|
28
39
|
'ads',
|
|
@@ -47,6 +58,47 @@ const GOOGLE_AD_TEXT_HINTS = [
|
|
|
47
58
|
'anunciante',
|
|
48
59
|
];
|
|
49
60
|
const COUNTRY_CODE_PATTERN = /^[a-z]{2}$/;
|
|
61
|
+
const GOOGLE_SEARCH_HOST_OVERRIDES = {
|
|
62
|
+
br: 'google.com.br',
|
|
63
|
+
de: 'google.de',
|
|
64
|
+
fr: 'google.fr',
|
|
65
|
+
es: 'google.es',
|
|
66
|
+
it: 'google.it',
|
|
67
|
+
in: 'google.co.in',
|
|
68
|
+
uk: 'google.co.uk',
|
|
69
|
+
gb: 'google.co.uk',
|
|
70
|
+
us: 'google.com',
|
|
71
|
+
au: 'google.com.au',
|
|
72
|
+
ca: 'google.ca',
|
|
73
|
+
mx: 'google.com.mx',
|
|
74
|
+
ar: 'google.com.ar',
|
|
75
|
+
};
|
|
76
|
+
const GOOGLE_DEFAULT_LANGUAGE_BY_COUNTRY = {
|
|
77
|
+
br: 'pt-BR',
|
|
78
|
+
pt: 'pt-PT',
|
|
79
|
+
pt_br: 'pt-BR',
|
|
80
|
+
de: 'de',
|
|
81
|
+
fr: 'fr',
|
|
82
|
+
es: 'es',
|
|
83
|
+
it: 'it',
|
|
84
|
+
in: 'en-IN',
|
|
85
|
+
mx: 'es',
|
|
86
|
+
ar: 'es',
|
|
87
|
+
au: 'en',
|
|
88
|
+
ca: 'en',
|
|
89
|
+
gb: 'en',
|
|
90
|
+
uk: 'en',
|
|
91
|
+
};
|
|
92
|
+
const SERP_BLOCK_CONFIDENCE = 0.6;
|
|
93
|
+
const URL_DOMAIN_MARKET_MAP = {
|
|
94
|
+
'co.uk': 'uk',
|
|
95
|
+
'com.br': 'br',
|
|
96
|
+
'co.br': 'br',
|
|
97
|
+
'com.au': 'au',
|
|
98
|
+
'co.in': 'in',
|
|
99
|
+
'com.ar': 'ar',
|
|
100
|
+
'com.mx': 'mx',
|
|
101
|
+
};
|
|
50
102
|
const COUNTRY_ALIASES = {
|
|
51
103
|
us: 'us',
|
|
52
104
|
usa: 'us',
|
|
@@ -85,6 +137,39 @@ const COUNTRY_ALIASES_NORMALIZED = Object.entries(COUNTRY_ALIASES).reduce((acc,
|
|
|
85
137
|
acc[key.toLowerCase().replace(/[^a-z]/g, '_')] = value;
|
|
86
138
|
return acc;
|
|
87
139
|
}, {});
|
|
140
|
+
const SERP_SEARCH_SOURCES = ['google'];
|
|
141
|
+
function normalizeNumberTiming(value) {
|
|
142
|
+
if (typeof value !== 'number')
|
|
143
|
+
return undefined;
|
|
144
|
+
if (!Number.isFinite(value))
|
|
145
|
+
return undefined;
|
|
146
|
+
if (value <= 0)
|
|
147
|
+
return undefined;
|
|
148
|
+
return Math.round(value);
|
|
149
|
+
}
|
|
150
|
+
function toSearchTimings(timings) {
|
|
151
|
+
if (!timings || typeof timings !== 'object')
|
|
152
|
+
return undefined;
|
|
153
|
+
const parsed = timings;
|
|
154
|
+
const normalized = {
|
|
155
|
+
dns: normalizeNumberTiming(parsed.dns),
|
|
156
|
+
tcp: normalizeNumberTiming(parsed.tcp),
|
|
157
|
+
tls: normalizeNumberTiming(parsed.tls),
|
|
158
|
+
ttfb: normalizeNumberTiming(parsed.firstByte),
|
|
159
|
+
content: normalizeNumberTiming(parsed.content),
|
|
160
|
+
total: normalizeNumberTiming(parsed.total),
|
|
161
|
+
};
|
|
162
|
+
return Object.values(normalized).some(value => value !== undefined) ? normalized : undefined;
|
|
163
|
+
}
|
|
164
|
+
function detectSearchCaptcha(status, headers, body) {
|
|
165
|
+
const result = detectCaptcha({ status, headers }, body);
|
|
166
|
+
return {
|
|
167
|
+
detected: result.detected,
|
|
168
|
+
confidence: result.confidence,
|
|
169
|
+
provider: result.provider,
|
|
170
|
+
description: result.description,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
88
173
|
function cleanText(value) {
|
|
89
174
|
return value.replace(/\s+/g, ' ').trim();
|
|
90
175
|
}
|
|
@@ -99,6 +184,41 @@ function toParamValue(value) {
|
|
|
99
184
|
const trimmed = String(value).trim();
|
|
100
185
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
101
186
|
}
|
|
187
|
+
function pickRandomProfileValue(values) {
|
|
188
|
+
if (values.length === 0)
|
|
189
|
+
return '';
|
|
190
|
+
const index = Math.floor(Math.random() * values.length);
|
|
191
|
+
return values[index];
|
|
192
|
+
}
|
|
193
|
+
function generateSeiHint() {
|
|
194
|
+
const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
195
|
+
const chars = [];
|
|
196
|
+
for (let i = 0; i < GOOGLE_BROWSER_SEI_LENGTH; i++) {
|
|
197
|
+
chars.push(alphabet[Math.floor(Math.random() * alphabet.length)] ?? 'a');
|
|
198
|
+
}
|
|
199
|
+
return chars.join('');
|
|
200
|
+
}
|
|
201
|
+
function normalizeHumanSearchProfile(value) {
|
|
202
|
+
if (typeof value !== 'string') {
|
|
203
|
+
return 'off';
|
|
204
|
+
}
|
|
205
|
+
const normalized = value.trim().toLowerCase();
|
|
206
|
+
if (!normalized || normalized === 'off' || normalized === 'false' || normalized === '0') {
|
|
207
|
+
return 'off';
|
|
208
|
+
}
|
|
209
|
+
if (normalized === 'chrome' || normalized === 'browser' || normalized === 'human') {
|
|
210
|
+
return 'chrome';
|
|
211
|
+
}
|
|
212
|
+
return 'off';
|
|
213
|
+
}
|
|
214
|
+
function normalizeSearchSource(value) {
|
|
215
|
+
if (typeof value !== 'string')
|
|
216
|
+
return 'google';
|
|
217
|
+
const normalized = value.trim().toLowerCase();
|
|
218
|
+
return SERP_SEARCH_SOURCES.includes(normalized)
|
|
219
|
+
? normalized
|
|
220
|
+
: 'google';
|
|
221
|
+
}
|
|
102
222
|
function pick(...values) {
|
|
103
223
|
for (const value of values) {
|
|
104
224
|
if (value !== undefined) {
|
|
@@ -138,6 +258,84 @@ function resolveCountryCode(country, legacyGl) {
|
|
|
138
258
|
}
|
|
139
259
|
return legacyGl ? legacyGl.trim().toLowerCase() : '';
|
|
140
260
|
}
|
|
261
|
+
function parseCountryFromHostname(hostname) {
|
|
262
|
+
const normalized = hostname
|
|
263
|
+
.toLowerCase()
|
|
264
|
+
.replace(/^www\./, '')
|
|
265
|
+
.trim();
|
|
266
|
+
const parts = normalized.split('.');
|
|
267
|
+
if (parts.length < 2)
|
|
268
|
+
return undefined;
|
|
269
|
+
const suffix2 = parts.slice(-2).join('.');
|
|
270
|
+
const mapped2 = URL_DOMAIN_MARKET_MAP[suffix2];
|
|
271
|
+
if (mapped2)
|
|
272
|
+
return mapped2;
|
|
273
|
+
const last = parts[parts.length - 1];
|
|
274
|
+
if (COUNTRY_CODE_PATTERN.test(last))
|
|
275
|
+
return last;
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
function resolveGoogleSearchOrigin(gl) {
|
|
279
|
+
if (!gl)
|
|
280
|
+
return GOOGLE_SEARCH_ORIGIN;
|
|
281
|
+
const normalized = gl.toLowerCase();
|
|
282
|
+
const host = GOOGLE_SEARCH_HOST_OVERRIDES[normalized] ?? `google.${normalized}`;
|
|
283
|
+
return `https://www.${host}`;
|
|
284
|
+
}
|
|
285
|
+
function normalizeSearchOrigin(origin) {
|
|
286
|
+
try {
|
|
287
|
+
const parsed = new URL(origin);
|
|
288
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
if (origin.startsWith('http://') || origin.startsWith('https://'))
|
|
292
|
+
return origin;
|
|
293
|
+
return `https://www.google.com`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function pickGoogleSearchOrigins(primary, country) {
|
|
297
|
+
const unique = [];
|
|
298
|
+
const push = (value) => {
|
|
299
|
+
if (!value)
|
|
300
|
+
return;
|
|
301
|
+
if (!unique.includes(value))
|
|
302
|
+
unique.push(value);
|
|
303
|
+
};
|
|
304
|
+
const normalizedPrimary = normalizeSearchOrigin(primary);
|
|
305
|
+
const canonical = 'https://www.google.com';
|
|
306
|
+
push(normalizedPrimary);
|
|
307
|
+
push(canonical);
|
|
308
|
+
const countryHost = country ? GOOGLE_SEARCH_HOST_OVERRIDES[country.toLowerCase()] : '';
|
|
309
|
+
if (countryHost) {
|
|
310
|
+
push(`https://www.${countryHost}`);
|
|
311
|
+
}
|
|
312
|
+
if (countryHost && `https://www.${countryHost}` !== canonical) {
|
|
313
|
+
push('https://www.google.com');
|
|
314
|
+
}
|
|
315
|
+
if (normalizedPrimary.startsWith('https://')) {
|
|
316
|
+
push(normalizedPrimary.replace(/^https:/, 'http:'));
|
|
317
|
+
}
|
|
318
|
+
return unique;
|
|
319
|
+
}
|
|
320
|
+
export function inferSearchLocaleFromUrl(targetUrl) {
|
|
321
|
+
try {
|
|
322
|
+
const parsed = new URL(targetUrl);
|
|
323
|
+
const country = parseCountryFromHostname(parsed.hostname);
|
|
324
|
+
if (!country)
|
|
325
|
+
return undefined;
|
|
326
|
+
const resolved = normalizeCountryCode(country);
|
|
327
|
+
if (!resolved)
|
|
328
|
+
return undefined;
|
|
329
|
+
return {
|
|
330
|
+
country: resolved,
|
|
331
|
+
gl: resolved,
|
|
332
|
+
hl: GOOGLE_DEFAULT_LANGUAGE_BY_COUNTRY[resolved],
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
141
339
|
function normalizeOptions(query, options = {}) {
|
|
142
340
|
const normalizedQuery = cleanText(query);
|
|
143
341
|
if (!normalizedQuery) {
|
|
@@ -147,6 +345,9 @@ function normalizeOptions(query, options = {}) {
|
|
|
147
345
|
});
|
|
148
346
|
}
|
|
149
347
|
const resolvedTransport = (options.transport ?? 'auto');
|
|
348
|
+
const gl = resolveCountryCode(options.country, options.gl);
|
|
349
|
+
const searchOrigin = resolveGoogleSearchOrigin(gl);
|
|
350
|
+
const source = normalizeSearchSource(options.source);
|
|
150
351
|
return {
|
|
151
352
|
...options,
|
|
152
353
|
as_q: pick(options.as_q, options.asQ) ?? '',
|
|
@@ -159,16 +360,29 @@ function normalizeOptions(query, options = {}) {
|
|
|
159
360
|
as_rights: pick(options.as_rights, options.asRights) ?? '',
|
|
160
361
|
as_nlo: toParamValue(pick(options.as_nlo, options.asNlo)) ?? '',
|
|
161
362
|
as_nhi: toParamValue(pick(options.as_nhi, options.asNhi)) ?? '',
|
|
162
|
-
gl
|
|
363
|
+
gl,
|
|
364
|
+
humanProfile: normalizeHumanSearchProfile(options.humanProfile),
|
|
365
|
+
source,
|
|
163
366
|
transport: resolvedTransport,
|
|
164
367
|
includeRawHtml: options.includeRawHtml ?? false,
|
|
368
|
+
searchOrigin,
|
|
369
|
+
searchBaseUrl: `${searchOrigin}/search`,
|
|
165
370
|
};
|
|
166
371
|
}
|
|
167
372
|
function buildSearchUrl(query, options) {
|
|
168
373
|
const params = new URLSearchParams();
|
|
169
374
|
params.set('q', query);
|
|
170
|
-
params.set('
|
|
171
|
-
params.set('
|
|
375
|
+
params.set('oq', query);
|
|
376
|
+
params.set('sourceid', 'chrome');
|
|
377
|
+
params.set('ie', pickRandomProfileValue(GOOGLE_SEARCH_PARAM_IE_VALUES));
|
|
378
|
+
params.set('oe', pickRandomProfileValue(GOOGLE_SEARCH_PARAM_IE_VALUES));
|
|
379
|
+
if (options.humanProfile === 'chrome') {
|
|
380
|
+
const qs = query.trim().toLowerCase();
|
|
381
|
+
params.set('aqs', pickRandomProfileValue(GOOGLE_BROWSER_PROFILE_AQS_VALUES));
|
|
382
|
+
params.set('gs_lcrp', pickRandomProfileValue(GOOGLE_BROWSER_PROFILE_GS_LCRP_VALUES));
|
|
383
|
+
params.set('sei', generateSeiHint());
|
|
384
|
+
params.set('client', 'chrome');
|
|
385
|
+
}
|
|
172
386
|
if (options.as_q)
|
|
173
387
|
params.set('as_q', options.as_q);
|
|
174
388
|
if (options.asEpq)
|
|
@@ -221,17 +435,26 @@ function buildSearchUrl(query, options) {
|
|
|
221
435
|
}
|
|
222
436
|
}
|
|
223
437
|
}
|
|
224
|
-
return `${
|
|
438
|
+
return `${options.searchBaseUrl}?${params.toString()}`;
|
|
225
439
|
}
|
|
226
|
-
function normalizeRequestHeaders(inputHeaders, userAgent) {
|
|
440
|
+
function normalizeRequestHeaders(inputHeaders, userAgent, referer, locale) {
|
|
441
|
+
const normalizedLocale = (locale || 'en-US').replace('_', '-');
|
|
442
|
+
const acceptLanguage = `${normalizedLocale},en-US;q=0.8,en;q=0.6`;
|
|
227
443
|
const headers = new Headers({
|
|
228
|
-
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
229
|
-
'accept-language':
|
|
444
|
+
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
|
445
|
+
'accept-language': acceptLanguage,
|
|
230
446
|
'cache-control': 'max-age=0',
|
|
231
|
-
'sec-ch-ua
|
|
447
|
+
'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"',
|
|
232
448
|
'sec-ch-ua-platform': '"Windows"',
|
|
449
|
+
'sec-ch-ua-mobile': '?0',
|
|
450
|
+
'sec-fetch-dest': 'document',
|
|
451
|
+
'sec-fetch-mode': 'navigate',
|
|
452
|
+
'sec-fetch-site': 'same-origin',
|
|
453
|
+
'sec-fetch-user': '?1',
|
|
454
|
+
'upgrade-insecure-requests': '1',
|
|
233
455
|
'user-agent': userAgent,
|
|
234
|
-
referer
|
|
456
|
+
referer,
|
|
457
|
+
cookie: 'SOCS=CAI',
|
|
235
458
|
});
|
|
236
459
|
if (inputHeaders) {
|
|
237
460
|
const incoming = new Headers(inputHeaders);
|
|
@@ -243,6 +466,24 @@ function normalizeRequestHeaders(inputHeaders, userAgent) {
|
|
|
243
466
|
});
|
|
244
467
|
return merged;
|
|
245
468
|
}
|
|
469
|
+
function isUsefulSearchResponse(parsed, fetchResult) {
|
|
470
|
+
if (parsed.results.length > 0)
|
|
471
|
+
return true;
|
|
472
|
+
if (fetchResult.block?.blocked && fetchResult.block.confidence >= SERP_BLOCK_CONFIDENCE)
|
|
473
|
+
return false;
|
|
474
|
+
if (fetchResult.captcha?.detected && fetchResult.captcha.confidence >= SERP_BLOCK_CONFIDENCE)
|
|
475
|
+
return false;
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
function isHardSearchBlock(parsed, fetchResult) {
|
|
479
|
+
if (fetchResult.captcha?.detected && fetchResult.captcha.confidence >= SERP_BLOCK_CONFIDENCE)
|
|
480
|
+
return true;
|
|
481
|
+
if (fetchResult.block?.blocked && fetchResult.block.confidence >= SERP_BLOCK_CONFIDENCE)
|
|
482
|
+
return true;
|
|
483
|
+
if (parsed.results.length === 0 && fetchResult.block?.reason === 'captcha')
|
|
484
|
+
return true;
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
246
487
|
async function hasImpersonateBinary() {
|
|
247
488
|
try {
|
|
248
489
|
const { hasImpersonate } = await import('../utils/binary-manager.js');
|
|
@@ -254,6 +495,7 @@ async function hasImpersonateBinary() {
|
|
|
254
495
|
}
|
|
255
496
|
async function fetchWithCurl(url, headers, timeout) {
|
|
256
497
|
const { CurlTransport } = await import('../transport/curl.js');
|
|
498
|
+
const requestStart = performance.now();
|
|
257
499
|
const transport = new CurlTransport();
|
|
258
500
|
const request = new HttpRequest(url, {
|
|
259
501
|
method: 'GET',
|
|
@@ -261,22 +503,32 @@ async function fetchWithCurl(url, headers, timeout) {
|
|
|
261
503
|
timeout,
|
|
262
504
|
});
|
|
263
505
|
const response = await transport.dispatch(request);
|
|
506
|
+
const requestEnd = performance.now();
|
|
264
507
|
const html = await response.text();
|
|
265
|
-
|
|
508
|
+
const parsedTimings = toSearchTimings(response.timings);
|
|
509
|
+
const timings = parsedTimings ?? {
|
|
510
|
+
total: Math.round(requestEnd - requestStart),
|
|
511
|
+
};
|
|
512
|
+
return {
|
|
513
|
+
html,
|
|
514
|
+
status: response.status,
|
|
515
|
+
responseHeaders: response.headers,
|
|
516
|
+
timings,
|
|
517
|
+
};
|
|
266
518
|
}
|
|
267
519
|
async function fetchSearchResults(url, options) {
|
|
268
|
-
const headers = normalizeRequestHeaders(options.headers, options.userAgent ?? getRandomUserAgent('desktop.chrome'));
|
|
520
|
+
const headers = normalizeRequestHeaders(options.headers, options.userAgent ?? getRandomUserAgent('desktop.chrome'), options.searchOrigin, options.hl);
|
|
269
521
|
const requestTimeout = options.timeout;
|
|
270
|
-
const impersonateAvailable =
|
|
271
|
-
if (options.transport === 'curl' && !impersonateAvailable) {
|
|
272
|
-
throw new ValidationError('Transport "curl" requires curl-impersonate; install it with `rek setup`', {
|
|
273
|
-
field: 'transport',
|
|
274
|
-
value: options.transport,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
522
|
+
const impersonateAvailable = await hasImpersonateBinary();
|
|
277
523
|
if (options.transport === 'curl') {
|
|
524
|
+
if (!impersonateAvailable) {
|
|
525
|
+
throw new ValidationError('Transport "curl" requires curl-impersonate; install it with `rek setup`', {
|
|
526
|
+
field: 'transport',
|
|
527
|
+
value: options.transport,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
278
530
|
const directResponse = await fetchWithCurl(url, headers, requestTimeout);
|
|
279
|
-
const directBlock = detectBlock({ status: directResponse.status, headers:
|
|
531
|
+
const directBlock = detectBlock({ status: directResponse.status, headers: directResponse.responseHeaders }, directResponse.html);
|
|
280
532
|
return {
|
|
281
533
|
html: directResponse.html,
|
|
282
534
|
status: directResponse.status,
|
|
@@ -284,13 +536,19 @@ async function fetchSearchResults(url, options) {
|
|
|
284
536
|
fallbackUsed: false,
|
|
285
537
|
impersonateAvailable,
|
|
286
538
|
block: directBlock,
|
|
539
|
+
captcha: detectSearchCaptcha(directResponse.status, directResponse.responseHeaders, directResponse.html),
|
|
540
|
+
timings: directResponse.timings,
|
|
287
541
|
};
|
|
288
542
|
}
|
|
289
543
|
const client = createClient({ timeout: requestTimeout });
|
|
290
544
|
const performUndiciRequest = async () => {
|
|
291
|
-
const response = await client.get(url, {
|
|
545
|
+
const response = await client.get(url, {
|
|
546
|
+
headers,
|
|
547
|
+
throwHttpErrors: false,
|
|
548
|
+
});
|
|
292
549
|
const html = await response.text();
|
|
293
550
|
const block = detectBlock({ status: response.status, headers: response.headers }, html);
|
|
551
|
+
const captcha = detectSearchCaptcha(response.status, response.headers, html);
|
|
294
552
|
return {
|
|
295
553
|
html,
|
|
296
554
|
status: response.status,
|
|
@@ -298,18 +556,39 @@ async function fetchSearchResults(url, options) {
|
|
|
298
556
|
fallbackUsed: false,
|
|
299
557
|
impersonateAvailable,
|
|
300
558
|
block,
|
|
559
|
+
captcha,
|
|
560
|
+
timings: toSearchTimings(response.timings),
|
|
301
561
|
};
|
|
302
562
|
};
|
|
303
|
-
if (options.transport === 'undici') {
|
|
304
|
-
return performUndiciRequest();
|
|
305
|
-
}
|
|
306
563
|
if (!impersonateAvailable) {
|
|
307
564
|
return performUndiciRequest();
|
|
308
565
|
}
|
|
566
|
+
const shouldFallbackToUndici = (block, captcha) => {
|
|
567
|
+
if (block.blocked && block.confidence >= SERP_BLOCK_CONFIDENCE)
|
|
568
|
+
return true;
|
|
569
|
+
if (captcha.detected && captcha.confidence >= SERP_BLOCK_CONFIDENCE)
|
|
570
|
+
return true;
|
|
571
|
+
if (block.reason === 'captcha' || block.reason === 'rate-limit')
|
|
572
|
+
return true;
|
|
573
|
+
return false;
|
|
574
|
+
};
|
|
575
|
+
const getSearchResponseRisk = (response) => ((response.block?.confidence ?? 0) + (response.captcha?.confidence ?? 0));
|
|
576
|
+
const shouldKeepPrimaryImpersonateResponse = (primary, fallback) => (getSearchResponseRisk(primary) <= getSearchResponseRisk(fallback));
|
|
309
577
|
try {
|
|
310
578
|
const primaryImpersonateResponse = await fetchWithCurl(url, headers, requestTimeout);
|
|
311
|
-
const primaryImpersonateBlock = detectBlock({ status: primaryImpersonateResponse.status, headers:
|
|
312
|
-
|
|
579
|
+
const primaryImpersonateBlock = detectBlock({ status: primaryImpersonateResponse.status, headers: primaryImpersonateResponse.responseHeaders }, primaryImpersonateResponse.html);
|
|
580
|
+
const primaryImpersonateCaptcha = detectSearchCaptcha(primaryImpersonateResponse.status, primaryImpersonateResponse.responseHeaders, primaryImpersonateResponse.html);
|
|
581
|
+
const primaryResult = {
|
|
582
|
+
html: primaryImpersonateResponse.html,
|
|
583
|
+
status: primaryImpersonateResponse.status,
|
|
584
|
+
transport: 'curl',
|
|
585
|
+
fallbackUsed: false,
|
|
586
|
+
impersonateAvailable,
|
|
587
|
+
block: primaryImpersonateBlock,
|
|
588
|
+
captcha: primaryImpersonateCaptcha,
|
|
589
|
+
timings: primaryImpersonateResponse.timings,
|
|
590
|
+
};
|
|
591
|
+
if (!shouldFallbackToUndici(primaryImpersonateBlock, primaryImpersonateCaptcha)) {
|
|
313
592
|
return {
|
|
314
593
|
html: primaryImpersonateResponse.html,
|
|
315
594
|
status: primaryImpersonateResponse.status,
|
|
@@ -317,8 +596,20 @@ async function fetchSearchResults(url, options) {
|
|
|
317
596
|
fallbackUsed: false,
|
|
318
597
|
impersonateAvailable,
|
|
319
598
|
block: primaryImpersonateBlock,
|
|
599
|
+
captcha: primaryImpersonateCaptcha,
|
|
600
|
+
timings: primaryImpersonateResponse.timings,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
const fallback = await performUndiciRequest();
|
|
604
|
+
if (!shouldKeepPrimaryImpersonateResponse(primaryResult, fallback)) {
|
|
605
|
+
return {
|
|
606
|
+
...fallback,
|
|
607
|
+
transport: fallback.transport,
|
|
608
|
+
fallbackUsed: true,
|
|
609
|
+
impersonateAvailable,
|
|
320
610
|
};
|
|
321
611
|
}
|
|
612
|
+
return primaryResult;
|
|
322
613
|
}
|
|
323
614
|
catch {
|
|
324
615
|
}
|
|
@@ -330,19 +621,36 @@ async function fetchSearchResults(url, options) {
|
|
|
330
621
|
impersonateAvailable,
|
|
331
622
|
};
|
|
332
623
|
}
|
|
333
|
-
function
|
|
624
|
+
function isGoogleOwnHost(hostname) {
|
|
625
|
+
const lower = hostname.toLowerCase();
|
|
626
|
+
return (lower === 'www.google.com' ||
|
|
627
|
+
lower === 'google.com' ||
|
|
628
|
+
lower.endsWith('.google.com') ||
|
|
629
|
+
lower.endsWith('.googleapis.com') ||
|
|
630
|
+
lower.endsWith('.googleusercontent.com') ||
|
|
631
|
+
lower.endsWith('.googlesyndication.com') ||
|
|
632
|
+
lower.endsWith('.doubleclick.net') ||
|
|
633
|
+
lower.endsWith('.gstatic.com') ||
|
|
634
|
+
lower.startsWith('www.google.') ||
|
|
635
|
+
/^([a-z0-9-]+\.)*google(\.[a-z]{2,})+$/.test(lower));
|
|
636
|
+
}
|
|
637
|
+
function resolveSearchResultUrl(rawHref, searchOrigin) {
|
|
334
638
|
try {
|
|
335
639
|
const normalized = rawHref.startsWith('//') ? `https:${rawHref}` : rawHref;
|
|
336
|
-
const parsed = new URL(normalized,
|
|
640
|
+
const parsed = new URL(normalized, searchOrigin);
|
|
337
641
|
const rawResultUrl = parsed.searchParams.get('q') ?? parsed.searchParams.get('url');
|
|
338
|
-
if (
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return
|
|
345
|
-
|
|
642
|
+
if (rawResultUrl) {
|
|
643
|
+
const candidate = new URL(decodeURIComponent(rawResultUrl), searchOrigin);
|
|
644
|
+
if (!candidate.protocol.startsWith('http'))
|
|
645
|
+
return null;
|
|
646
|
+
if (candidate.pathname === '/search' && candidate.hostname.includes('google.'))
|
|
647
|
+
return null;
|
|
648
|
+
return candidate.toString();
|
|
649
|
+
}
|
|
650
|
+
if (parsed.protocol.startsWith('http') && !isGoogleOwnHost(parsed.hostname)) {
|
|
651
|
+
return parsed.toString();
|
|
652
|
+
}
|
|
653
|
+
return null;
|
|
346
654
|
}
|
|
347
655
|
catch {
|
|
348
656
|
return null;
|
|
@@ -490,8 +798,8 @@ function parseResultStats(text) {
|
|
|
490
798
|
const parsed = Number.parseInt(match[1], 10);
|
|
491
799
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
492
800
|
}
|
|
493
|
-
function parseSearchPage(html, options) {
|
|
494
|
-
const doc = ScrapeDocument.createSync(html, { baseUrl:
|
|
801
|
+
function parseSearchPage(html, options, searchOrigin) {
|
|
802
|
+
const doc = ScrapeDocument.createSync(html, { baseUrl: searchOrigin });
|
|
495
803
|
const results = [];
|
|
496
804
|
const seen = new Set();
|
|
497
805
|
const maxResults = options.maxResults ? Number(options.maxResults) : undefined;
|
|
@@ -501,7 +809,7 @@ function parseSearchPage(html, options) {
|
|
|
501
809
|
const rawHref = anchor.attr('href');
|
|
502
810
|
if (!rawHref)
|
|
503
811
|
continue;
|
|
504
|
-
const resultUrl = resolveSearchResultUrl(rawHref);
|
|
812
|
+
const resultUrl = resolveSearchResultUrl(rawHref, searchOrigin);
|
|
505
813
|
if (!resultUrl || seen.has(resultUrl))
|
|
506
814
|
continue;
|
|
507
815
|
const titleText = (() => {
|
|
@@ -551,6 +859,49 @@ function parseSearchPage(html, options) {
|
|
|
551
859
|
break;
|
|
552
860
|
}
|
|
553
861
|
}
|
|
862
|
+
for (const anchor of doc.selectAll('a[href^="https://"], a[href^="http://"]')) {
|
|
863
|
+
if (typeof maxResults === 'number' && Number.isFinite(maxResults) && results.length >= maxResults)
|
|
864
|
+
break;
|
|
865
|
+
const rawHref = anchor.attr('href');
|
|
866
|
+
if (!rawHref)
|
|
867
|
+
continue;
|
|
868
|
+
const resultUrl = resolveSearchResultUrl(rawHref, searchOrigin);
|
|
869
|
+
if (!resultUrl || seen.has(resultUrl))
|
|
870
|
+
continue;
|
|
871
|
+
const titleText = cleanText(anchor.find('h3').text());
|
|
872
|
+
if (!titleText)
|
|
873
|
+
continue;
|
|
874
|
+
const resultContainer = anchor.parents(GOOGLE_RESULT_CONTAINER_SELECTORS).first();
|
|
875
|
+
const placement = detectResultPlacement(anchor, resultContainer);
|
|
876
|
+
const snippet = (() => {
|
|
877
|
+
for (const selector of GOOGLE_RESULT_SNIPPET_SELECTOR_ORDER) {
|
|
878
|
+
const snippetNode = resultContainer.find(selector).first();
|
|
879
|
+
const snippetText = cleanText(snippetNode.text());
|
|
880
|
+
if (looksLikeSnippet(snippetText, titleText))
|
|
881
|
+
return snippetText;
|
|
882
|
+
}
|
|
883
|
+
const fallbackElements = resultContainer.find('span,div').toArray();
|
|
884
|
+
for (const fallbackEl of fallbackElements) {
|
|
885
|
+
const fallbackText = cleanText(fallbackEl.text());
|
|
886
|
+
if (looksLikeSnippet(fallbackText, titleText))
|
|
887
|
+
return fallbackText;
|
|
888
|
+
}
|
|
889
|
+
const rootFallback = cleanText(resultContainer.text());
|
|
890
|
+
if (looksLikeSnippet(rootFallback, titleText))
|
|
891
|
+
return rootFallback.slice(0, 240);
|
|
892
|
+
return undefined;
|
|
893
|
+
})();
|
|
894
|
+
results.push({
|
|
895
|
+
rank: results.length + 1,
|
|
896
|
+
title: titleText,
|
|
897
|
+
url: resultUrl,
|
|
898
|
+
snippet,
|
|
899
|
+
displayedUrl: extractDisplayedUrl(resultUrl, anchor.text()),
|
|
900
|
+
placement: placement.placement,
|
|
901
|
+
placementHint: placement.placementHint,
|
|
902
|
+
});
|
|
903
|
+
seen.add(resultUrl);
|
|
904
|
+
}
|
|
554
905
|
const nextPageRaw = (() => {
|
|
555
906
|
const candidate = doc.selectFirst('a#pnnext, a[aria-label="Next"], a[id="pnnext"]').first();
|
|
556
907
|
if (candidate && candidate.length) {
|
|
@@ -558,7 +909,7 @@ function parseSearchPage(html, options) {
|
|
|
558
909
|
if (!href)
|
|
559
910
|
return undefined;
|
|
560
911
|
try {
|
|
561
|
-
return new URL(href,
|
|
912
|
+
return new URL(href, searchOrigin).toString();
|
|
562
913
|
}
|
|
563
914
|
catch {
|
|
564
915
|
return undefined;
|
|
@@ -591,12 +942,41 @@ function parseSearchPage(html, options) {
|
|
|
591
942
|
}
|
|
592
943
|
export async function searchGoogleAdvanced(query, options = {}) {
|
|
593
944
|
const normalized = normalizeOptions(query, options);
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
945
|
+
const originCandidates = pickGoogleSearchOrigins(normalized.searchOrigin, normalized.gl);
|
|
946
|
+
let fetchResult;
|
|
947
|
+
let parsed;
|
|
948
|
+
let selectedSearchUrl = '';
|
|
949
|
+
for (let index = 0; index < originCandidates.length; index += 1) {
|
|
950
|
+
const origin = originCandidates[index];
|
|
951
|
+
const isFinalCandidate = index === originCandidates.length - 1;
|
|
952
|
+
const candidateOptions = {
|
|
953
|
+
...normalized,
|
|
954
|
+
searchOrigin: origin,
|
|
955
|
+
searchBaseUrl: `${origin}/search`,
|
|
956
|
+
};
|
|
957
|
+
const candidateUrl = buildSearchUrl(query, candidateOptions);
|
|
958
|
+
const candidateResult = await fetchSearchResults(candidateUrl, candidateOptions);
|
|
959
|
+
const candidateParsed = parseSearchPage(candidateResult.html, candidateOptions, candidateOptions.searchOrigin);
|
|
960
|
+
fetchResult = candidateResult;
|
|
961
|
+
parsed = candidateParsed;
|
|
962
|
+
selectedSearchUrl = candidateUrl;
|
|
963
|
+
if (isUsefulSearchResponse(candidateParsed, candidateResult) || isFinalCandidate) {
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
if (isHardSearchBlock(candidateParsed, candidateResult)) {
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
if (candidateParsed.results.length === 0) {
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (!fetchResult || !parsed) {
|
|
974
|
+
throw new ValidationError('Failed to execute Google search attempts');
|
|
975
|
+
}
|
|
597
976
|
const response = {
|
|
598
977
|
query: normalized.query,
|
|
599
|
-
|
|
978
|
+
source: normalized.source,
|
|
979
|
+
searchUrl: selectedSearchUrl,
|
|
600
980
|
results: parsed.results,
|
|
601
981
|
transport: {
|
|
602
982
|
requested: normalized.transport,
|
|
@@ -605,7 +985,9 @@ export async function searchGoogleAdvanced(query, options = {}) {
|
|
|
605
985
|
impersonateAvailable: fetchResult.impersonateAvailable,
|
|
606
986
|
},
|
|
607
987
|
status: fetchResult.status,
|
|
988
|
+
captcha: fetchResult.captcha,
|
|
608
989
|
block: fetchResult.block,
|
|
990
|
+
timings: fetchResult.timings,
|
|
609
991
|
nextPageUrl: parsed.nextPageUrl,
|
|
610
992
|
nextPageStart: parsed.nextPageStart,
|
|
611
993
|
resultStats: parsed.resultStats,
|
package/dist/search/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { searchGoogleAdvanced } from './google.js';
|
|
2
|
-
export type { GoogleSearchAdvancedOptions, GoogleSearchResult, GoogleSearchResultPlacement, GoogleSearchResponse, SearchTransportDetails, } from './google.js';
|
|
2
|
+
export type { GoogleSearchAdvancedOptions, GoogleSearchResult, GoogleSearchResultPlacement, GoogleSearchResponse, SerpSearchSource, SearchTransportDetails, } from './google.js';
|
|
3
3
|
export type { SearchTransport } from './google.js';
|
package/dist/seo/analyzer.d.ts
CHANGED