mcp-scraper 0.35.1 → 0.37.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/README.md +9 -9
- package/dist/bin/api-server.cjs +25071 -19082
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +51 -7
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +48 -5
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +995 -222
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +8 -8
- package/dist/bin/paa-harvest.cjs +125 -70
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +4 -4
- package/dist/chunk-345BQXZH.js +712 -0
- package/dist/chunk-345BQXZH.js.map +1 -0
- package/dist/chunk-3LWYPAU5.js +7 -0
- package/dist/chunk-3LWYPAU5.js.map +1 -0
- package/dist/{chunk-M2S27J6Z.js → chunk-44HZLHDV.js} +10 -1
- package/dist/chunk-44HZLHDV.js.map +1 -0
- package/dist/{chunk-62DQAWPF.js → chunk-CSCD2HNS.js} +498 -43
- package/dist/chunk-CSCD2HNS.js.map +1 -0
- package/dist/{chunk-ZID3WQID.js → chunk-FQI5PFE7.js} +9 -71
- package/dist/chunk-FQI5PFE7.js.map +1 -0
- package/dist/{chunk-3HBPKR5G.js → chunk-FSAXLDB3.js} +3 -3
- package/dist/chunk-G3P3ZDB4.js +69 -0
- package/dist/chunk-G3P3ZDB4.js.map +1 -0
- package/dist/{chunk-YRGSEY5L.js → chunk-G7KAVJ3F.js} +2 -2
- package/dist/{chunk-YRGSEY5L.js.map → chunk-G7KAVJ3F.js.map} +1 -1
- package/dist/{chunk-NPMW5HUS.js → chunk-JK2FRDAP.js} +930 -244
- package/dist/chunk-JK2FRDAP.js.map +1 -0
- package/dist/{chunk-XVVNKASZ.js → chunk-LOPKN3YL.js} +118 -73
- package/dist/chunk-LOPKN3YL.js.map +1 -0
- package/dist/{chunk-4ZB3X6BQ.js → chunk-MA5JBAUZ.js} +16 -2
- package/dist/{chunk-4ZB3X6BQ.js.map → chunk-MA5JBAUZ.js.map} +1 -1
- package/dist/chunk-PUJFYJXB.js +684 -0
- package/dist/chunk-PUJFYJXB.js.map +1 -0
- package/dist/{chunk-BWXLTWF7.js → chunk-PWPUKR5U.js} +9 -5
- package/dist/chunk-PWPUKR5U.js.map +1 -0
- package/dist/chunk-Q35WZJJK.js +499 -0
- package/dist/chunk-Q35WZJJK.js.map +1 -0
- package/dist/chunk-QZXKQB7Y.js +414 -0
- package/dist/chunk-QZXKQB7Y.js.map +1 -0
- package/dist/{db-YAI5AQOI.js → db-N3YECFWR.js} +12 -2
- package/dist/{extract-bundle-ONWZVV55.js → extract-bundle-346R6MXD.js} +284 -98
- package/dist/extract-bundle-346R6MXD.js.map +1 -0
- package/dist/index.cjs +129 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +4 -4
- package/dist/location-data-repository-O2VII3ON.js +35 -0
- package/dist/{server-GKUTC73B.js → server-QKDDEBVQ.js} +7325 -3762
- package/dist/server-QKDDEBVQ.js.map +1 -0
- package/dist/site-extract-repository-I3VM6WXN.js +62 -0
- package/dist/site-extract-repository-I3VM6WXN.js.map +1 -0
- package/dist/{worker-645BZPEK.js → worker-TTFXPPDK.js} +7 -7
- package/docs/hosted-location-data.md +108 -0
- package/docs/mcp-tool-craft-lint.generated.md +6 -3
- package/docs/mcp-tool-manifest.generated.json +1447 -240
- package/docs/mcp-tool-quality-spec.md +1 -1
- package/docs/specs/connected-services-control-plane-decoupling-spec.md +1044 -0
- package/docs/specs/kernel-stealth-captcha-test-matrix.md +278 -0
- package/docs/specs/multimodal-image-memory-architecture-spec.md +1022 -0
- package/docs/specs/unified-credit-and-scheduled-execution-billing-spec.md +36 -27
- package/package.json +7 -6
- package/dist/chunk-62DQAWPF.js.map +0 -1
- package/dist/chunk-BWXLTWF7.js.map +0 -1
- package/dist/chunk-M2S27J6Z.js.map +0 -1
- package/dist/chunk-NPMW5HUS.js.map +0 -1
- package/dist/chunk-R7EETU7Z.js +0 -419
- package/dist/chunk-R7EETU7Z.js.map +0 -1
- package/dist/chunk-U44TPRST.js +0 -130
- package/dist/chunk-U44TPRST.js.map +0 -1
- package/dist/chunk-XVVNKASZ.js.map +0 -1
- package/dist/chunk-YR4LJ6AQ.js +0 -7
- package/dist/chunk-YR4LJ6AQ.js.map +0 -1
- package/dist/chunk-YV2FUEBX.js +0 -851
- package/dist/chunk-YV2FUEBX.js.map +0 -1
- package/dist/chunk-ZID3WQID.js.map +0 -1
- package/dist/extract-bundle-ONWZVV55.js.map +0 -1
- package/dist/server-GKUTC73B.js.map +0 -1
- package/dist/site-extract-repository-L6BHWVDU.js +0 -30
- /package/dist/{chunk-3HBPKR5G.js.map → chunk-FSAXLDB3.js.map} +0 -0
- /package/dist/{db-YAI5AQOI.js.map → db-N3YECFWR.js.map} +0 -0
- /package/dist/{site-extract-repository-L6BHWVDU.js.map → location-data-repository-O2VII3ON.js.map} +0 -0
- /package/dist/{worker-645BZPEK.js.map → worker-TTFXPPDK.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -341,6 +341,16 @@ var HEADLESS_OPS = /* @__PURE__ */ new Set(["serp", "maps_search", "yt_channel",
|
|
|
341
341
|
var FAL_WIZPER_USD_PER_COMPUTE_SEC = 111e-5;
|
|
342
342
|
var FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR = 4;
|
|
343
343
|
var DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS = 4e-4;
|
|
344
|
+
function envRate(name, fallback) {
|
|
345
|
+
const raw = process.env[name]?.trim();
|
|
346
|
+
if (!raw) return fallback;
|
|
347
|
+
const parsed = Number(raw);
|
|
348
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
349
|
+
}
|
|
350
|
+
var NANGO_USD_PER_CONNECTION_MONTH = envRate("NANGO_USD_PER_CONNECTION_MONTH", 1);
|
|
351
|
+
var NANGO_USD_PER_FUNCTION_RUN = envRate("NANGO_USD_PER_FUNCTION_RUN", 1e-4);
|
|
352
|
+
var NANGO_USD_PER_PROXY_REQUEST = envRate("NANGO_USD_PER_PROXY_REQUEST", 1e-4);
|
|
353
|
+
var NANGO_USD_PER_COMPUTE_SEC = envRate("NANGO_USD_PER_COMPUTE_SEC", 2e-4);
|
|
344
354
|
function kernelCostUsd(ms, headful) {
|
|
345
355
|
const sec = Math.max(0, ms) / 1e3;
|
|
346
356
|
return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC);
|
|
@@ -354,6 +364,10 @@ function vendorCostUsd(vendor, units) {
|
|
|
354
364
|
if (vendor === "openrouter") return Math.max(0, units);
|
|
355
365
|
if (vendor === "mcp_memory_video") return Math.max(0, units);
|
|
356
366
|
if (vendor === "mcp_memory_ai") return Math.max(0, units);
|
|
367
|
+
if (vendor === "nango_connection") return Math.max(0, units) * NANGO_USD_PER_CONNECTION_MONTH;
|
|
368
|
+
if (vendor === "nango_function_run") return Math.max(0, units) * NANGO_USD_PER_FUNCTION_RUN;
|
|
369
|
+
if (vendor === "nango_proxy_request") return Math.max(0, units) * NANGO_USD_PER_PROXY_REQUEST;
|
|
370
|
+
if (vendor === "nango_compute") return Math.max(0, units) * NANGO_USD_PER_COMPUTE_SEC;
|
|
357
371
|
return 0;
|
|
358
372
|
}
|
|
359
373
|
|
|
@@ -553,8 +567,6 @@ var MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)
|
|
|
553
567
|
var DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS = 180;
|
|
554
568
|
var KERNEL_BROWSER_CLOSE_TIMEOUT_MS = 3e3;
|
|
555
569
|
var KERNEL_SESSION_DELETE_TIMEOUT_MS = 5e3;
|
|
556
|
-
var DEFAULT_SERP_CHALLENGE_SOLVE_WAIT_MS = 2500;
|
|
557
|
-
var SERP_CHALLENGE_SOLVE_POLL_MS = 500;
|
|
558
570
|
function positiveIntFromEnv(name, fallback) {
|
|
559
571
|
const raw = process.env[name];
|
|
560
572
|
if (!raw) return fallback;
|
|
@@ -926,8 +938,7 @@ var BrowserDriver = class {
|
|
|
926
938
|
}
|
|
927
939
|
if (await this.isSerpChallengeVisible()) {
|
|
928
940
|
await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true });
|
|
929
|
-
|
|
930
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
941
|
+
throw new CaptchaError(this.captchaMessage());
|
|
931
942
|
}
|
|
932
943
|
const fastFound = await this.page.waitForSelector(PAASelectors.item, { timeout: 4e3 }).catch(() => null);
|
|
933
944
|
if (fastFound) {
|
|
@@ -936,8 +947,7 @@ var BrowserDriver = class {
|
|
|
936
947
|
}
|
|
937
948
|
if (await this.isSerpChallengeVisible()) {
|
|
938
949
|
await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true });
|
|
939
|
-
|
|
940
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
950
|
+
throw new CaptchaError(this.captchaMessage());
|
|
941
951
|
}
|
|
942
952
|
for (let i = 1; i <= 6; i++) {
|
|
943
953
|
await this.page.evaluate((f) => {
|
|
@@ -965,23 +975,6 @@ var BrowserDriver = class {
|
|
|
965
975
|
}).catch(() => "");
|
|
966
976
|
return /recaptcha|unusual traffic|are you a robot|about this page|detected unusual traffic/i.test(bodySnippet);
|
|
967
977
|
}
|
|
968
|
-
async waitForSerpChallengeToClear(navDebug, requestedUrl) {
|
|
969
|
-
if (!this.kernelClient || !this.page) return false;
|
|
970
|
-
const waitMs = positiveIntFromEnv("SERP_CHALLENGE_SOLVE_WAIT_MS", DEFAULT_SERP_CHALLENGE_SOLVE_WAIT_MS);
|
|
971
|
-
const deadline = Date.now() + waitMs;
|
|
972
|
-
while (Date.now() < deadline) {
|
|
973
|
-
await this.page.waitForTimeout(Math.min(SERP_CHALLENGE_SOLVE_POLL_MS, Math.max(250, deadline - Date.now())));
|
|
974
|
-
if (!await this.isSerpChallengeVisible()) {
|
|
975
|
-
await this.page.waitForTimeout(1e3);
|
|
976
|
-
if (!await this.isSerpChallengeVisible()) {
|
|
977
|
-
await this.updateSerpNavigationDebug(navDebug, requestedUrl, { hasPaa: null, captchaDetected: false });
|
|
978
|
-
return true;
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
await this.updateSerpNavigationDebug(navDebug, requestedUrl, { hasPaa: false, captchaDetected: true });
|
|
983
|
-
return false;
|
|
984
|
-
}
|
|
985
978
|
async updateSerpNavigationDebug(navDebug, requestedUrl, state) {
|
|
986
979
|
if (!navDebug || !this.page) return;
|
|
987
980
|
try {
|
|
@@ -1030,8 +1023,7 @@ var BrowserDriver = class {
|
|
|
1030
1023
|
try {
|
|
1031
1024
|
await this.page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
1032
1025
|
if (/google\.[^/]+\/search\?/i.test(url) && await this.isSerpChallengeVisible()) {
|
|
1033
|
-
|
|
1034
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
1026
|
+
throw new CaptchaError(this.captchaMessage());
|
|
1035
1027
|
}
|
|
1036
1028
|
} catch (err) {
|
|
1037
1029
|
if (err instanceof CaptchaError) throw err;
|
|
@@ -1261,6 +1253,27 @@ function normalizeLocation(input) {
|
|
|
1261
1253
|
return input;
|
|
1262
1254
|
}
|
|
1263
1255
|
|
|
1256
|
+
// src/serp-location-query.ts
|
|
1257
|
+
function normalizedTerms(value) {
|
|
1258
|
+
return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
1259
|
+
}
|
|
1260
|
+
function includesTermSequence(query, phrase) {
|
|
1261
|
+
const queryTerms = normalizedTerms(query);
|
|
1262
|
+
const phraseTerms = normalizedTerms(phrase);
|
|
1263
|
+
if (phraseTerms.length === 0 || phraseTerms.length > queryTerms.length) return false;
|
|
1264
|
+
return queryTerms.some((_, start) => phraseTerms.every((term, index) => queryTerms[start + index] === term));
|
|
1265
|
+
}
|
|
1266
|
+
function cityFromCanonicalLocation(location, fallback) {
|
|
1267
|
+
return location.split(",", 1)[0]?.trim() || fallback.split(",", 1)[0]?.trim() || fallback.trim();
|
|
1268
|
+
}
|
|
1269
|
+
function buildLocalizedSerpQuery(query, location, canonicalLocation) {
|
|
1270
|
+
const normalizedQuery = query.trim();
|
|
1271
|
+
const requestedLocation = location?.trim();
|
|
1272
|
+
if (!requestedLocation || !canonicalLocation) return normalizedQuery;
|
|
1273
|
+
const city = cityFromCanonicalLocation(canonicalLocation, requestedLocation);
|
|
1274
|
+
return includesTermSequence(normalizedQuery, city) ? normalizedQuery : `${normalizedQuery} ${requestedLocation}`;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1264
1277
|
// src/serp-location-debug.ts
|
|
1265
1278
|
var STATE_TO_CODE = {
|
|
1266
1279
|
alabama: "AL",
|
|
@@ -1514,6 +1527,11 @@ async function extractAISurfacesFromDocument(config) {
|
|
|
1514
1527
|
if (!el) return "";
|
|
1515
1528
|
return (el.innerText ?? el.textContent ?? "").trim();
|
|
1516
1529
|
}
|
|
1530
|
+
function hasSubstantiveContent(text) {
|
|
1531
|
+
if (!text) return false;
|
|
1532
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1533
|
+
return normalized.length >= 16 && normalized.split(" ").filter(Boolean).length >= 2;
|
|
1534
|
+
}
|
|
1517
1535
|
function hasAIOverviewLabel(el) {
|
|
1518
1536
|
const heading = el.querySelector(selectors.aio.heading);
|
|
1519
1537
|
if (textOf(heading) === "AI Overview") return true;
|
|
@@ -1676,19 +1694,22 @@ async function extractAISurfacesFromDocument(config) {
|
|
|
1676
1694
|
aioCitations = extractCitations(aioRoot);
|
|
1677
1695
|
}
|
|
1678
1696
|
const aimRoot = document.querySelector(selectors.aim.root);
|
|
1679
|
-
const
|
|
1697
|
+
const aimRootDetected = surface === "aim" && !!aimRoot;
|
|
1680
1698
|
const aimContainer = aimRoot?.closest(selectors.aim.wrapper) ?? aimRoot;
|
|
1681
|
-
const
|
|
1699
|
+
const rawAimText = aimRootDetected ? cleanText(aimContainer) : null;
|
|
1700
|
+
const aimDetected = aimRootDetected && hasSubstantiveContent(rawAimText);
|
|
1701
|
+
const aimText = aimDetected ? rawAimText : null;
|
|
1682
1702
|
const aimCitations = aimDetected ? extractCitations(aimContainer) : [];
|
|
1703
|
+
const aiOverviewDetected = !!aioRoot && hasSubstantiveContent(aioText);
|
|
1683
1704
|
return {
|
|
1684
1705
|
surface,
|
|
1685
1706
|
aiOverview: {
|
|
1686
|
-
detected:
|
|
1687
|
-
text: aioText,
|
|
1688
|
-
citations: aioCitations,
|
|
1689
|
-
expanded: aioExpanded,
|
|
1690
|
-
fullyExpanded: aioFullyExpanded,
|
|
1691
|
-
sections: aioSections
|
|
1707
|
+
detected: aiOverviewDetected,
|
|
1708
|
+
text: aiOverviewDetected ? aioText : null,
|
|
1709
|
+
citations: aiOverviewDetected ? aioCitations : [],
|
|
1710
|
+
expanded: aiOverviewDetected ? aioExpanded : false,
|
|
1711
|
+
fullyExpanded: aiOverviewDetected ? aioFullyExpanded : false,
|
|
1712
|
+
sections: aiOverviewDetected ? aioSections : []
|
|
1692
1713
|
},
|
|
1693
1714
|
aiMode: {
|
|
1694
1715
|
detected: aimDetected && aimText !== null,
|
|
@@ -2462,8 +2483,10 @@ var PAAExtractor = class {
|
|
|
2462
2483
|
this.throwIfAborted(signal);
|
|
2463
2484
|
const canonicalLocation = options.location ? normalizeLocation(options.location) : null;
|
|
2464
2485
|
const uule = canonicalLocation ? encodeUule(canonicalLocation) : null;
|
|
2486
|
+
const executedQuery = buildLocalizedSerpQuery(options.query, options.location, canonicalLocation);
|
|
2487
|
+
const executionOptions = executedQuery === options.query ? options : { ...options, query: executedQuery };
|
|
2465
2488
|
const { hasPaa } = await this.driver.navigateToSERP(
|
|
2466
|
-
|
|
2489
|
+
executionOptions.query,
|
|
2467
2490
|
uule,
|
|
2468
2491
|
options.gl,
|
|
2469
2492
|
options.hl,
|
|
@@ -2488,8 +2511,7 @@ var PAAExtractor = class {
|
|
|
2488
2511
|
let locationEvidence2 = options.debug ? inferSerpLocationEvidence(canonicalLocation, organicResults2, localPack2) : void 0;
|
|
2489
2512
|
let allOrganic2 = organicResults2;
|
|
2490
2513
|
if ((options.pages ?? 1) >= 2) {
|
|
2491
|
-
const p2params = new URLSearchParams({ q:
|
|
2492
|
-
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2514
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2493
2515
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2494
2516
|
if (uule) p2params.set("uule", uule);
|
|
2495
2517
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2501,7 +2523,7 @@ var PAAExtractor = class {
|
|
|
2501
2523
|
}
|
|
2502
2524
|
}
|
|
2503
2525
|
const stats2 = {
|
|
2504
|
-
seed:
|
|
2526
|
+
seed: executionOptions.query,
|
|
2505
2527
|
totalQuestions: 0,
|
|
2506
2528
|
maxDepthReached: 0,
|
|
2507
2529
|
durationMs: Date.now() - startMs,
|
|
@@ -2509,14 +2531,14 @@ var PAAExtractor = class {
|
|
|
2509
2531
|
};
|
|
2510
2532
|
this.reporter.onComplete(stats2);
|
|
2511
2533
|
return {
|
|
2512
|
-
seed:
|
|
2534
|
+
seed: executionOptions.query,
|
|
2513
2535
|
location: options.location ?? null,
|
|
2514
2536
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2515
2537
|
diagnostics: {
|
|
2516
2538
|
completionStatus: "serp_only",
|
|
2517
2539
|
problem: null,
|
|
2518
2540
|
completeness: { ...this.completeness },
|
|
2519
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2541
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence2) } : {}
|
|
2520
2542
|
},
|
|
2521
2543
|
totalQuestions: 0,
|
|
2522
2544
|
surface: aiSurfaces2.surface,
|
|
@@ -2549,8 +2571,7 @@ var PAAExtractor = class {
|
|
|
2549
2571
|
let noPaaOrganic = organicResults;
|
|
2550
2572
|
let locationEvidence2 = initialLocationEvidence;
|
|
2551
2573
|
if ((options.pages ?? 1) >= 2) {
|
|
2552
|
-
const p2params = new URLSearchParams({ q:
|
|
2553
|
-
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2574
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2554
2575
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2555
2576
|
if (uule) p2params.set("uule", uule);
|
|
2556
2577
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2563,7 +2584,7 @@ var PAAExtractor = class {
|
|
|
2563
2584
|
}
|
|
2564
2585
|
const aiSurfaces2 = await this.extractAISurfaces(page, options);
|
|
2565
2586
|
const stats2 = {
|
|
2566
|
-
seed:
|
|
2587
|
+
seed: executionOptions.query,
|
|
2567
2588
|
totalQuestions: 0,
|
|
2568
2589
|
maxDepthReached: 0,
|
|
2569
2590
|
durationMs: Date.now() - startMs,
|
|
@@ -2571,14 +2592,14 @@ var PAAExtractor = class {
|
|
|
2571
2592
|
};
|
|
2572
2593
|
this.reporter.onComplete(stats2);
|
|
2573
2594
|
return {
|
|
2574
|
-
seed:
|
|
2595
|
+
seed: executionOptions.query,
|
|
2575
2596
|
location: options.location ?? null,
|
|
2576
2597
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2577
2598
|
diagnostics: {
|
|
2578
2599
|
completionStatus: "no_paa",
|
|
2579
2600
|
problem: null,
|
|
2580
2601
|
completeness: { ...this.completeness },
|
|
2581
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2602
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence2) } : {}
|
|
2582
2603
|
},
|
|
2583
2604
|
totalQuestions: 0,
|
|
2584
2605
|
surface: aiSurfaces2.surface,
|
|
@@ -2595,10 +2616,10 @@ var PAAExtractor = class {
|
|
|
2595
2616
|
stats: stats2
|
|
2596
2617
|
};
|
|
2597
2618
|
}
|
|
2598
|
-
const flat = await this.runBFS(page,
|
|
2619
|
+
const flat = await this.runBFS(page, executionOptions, signal);
|
|
2599
2620
|
this.throwIfAborted(signal);
|
|
2600
2621
|
const aiSurfaces = await this.extractAISurfaces(page, options);
|
|
2601
|
-
const shortVidsParams = new URLSearchParams({ q:
|
|
2622
|
+
const shortVidsParams = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", udm: ShortVideoSelectors.udm });
|
|
2602
2623
|
if (uule) shortVidsParams.set("uule", uule);
|
|
2603
2624
|
let shortVideos = [];
|
|
2604
2625
|
try {
|
|
@@ -2617,7 +2638,7 @@ var PAAExtractor = class {
|
|
|
2617
2638
|
let allOrganic = organicResults;
|
|
2618
2639
|
let locationEvidence = initialLocationEvidence;
|
|
2619
2640
|
if ((options.pages ?? 1) >= 2) {
|
|
2620
|
-
const p2params = new URLSearchParams({ q:
|
|
2641
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2621
2642
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2622
2643
|
if (uule) p2params.set("uule", uule);
|
|
2623
2644
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2629,9 +2650,9 @@ var PAAExtractor = class {
|
|
|
2629
2650
|
}
|
|
2630
2651
|
}
|
|
2631
2652
|
const allVideos = [...videos, ...shortVideos];
|
|
2632
|
-
const tree = this.buildTree(flat,
|
|
2653
|
+
const tree = this.buildTree(flat, executionOptions.query);
|
|
2633
2654
|
const stats = {
|
|
2634
|
-
seed:
|
|
2655
|
+
seed: executionOptions.query,
|
|
2635
2656
|
totalQuestions: flat.length,
|
|
2636
2657
|
maxDepthReached: flat.reduce((m, r) => Math.max(m, r.depth), 0),
|
|
2637
2658
|
durationMs: Date.now() - startMs,
|
|
@@ -2639,7 +2660,7 @@ var PAAExtractor = class {
|
|
|
2639
2660
|
};
|
|
2640
2661
|
this.reporter.onComplete(stats);
|
|
2641
2662
|
return {
|
|
2642
|
-
seed:
|
|
2663
|
+
seed: executionOptions.query,
|
|
2643
2664
|
location: options.location ?? null,
|
|
2644
2665
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2645
2666
|
diagnostics: {
|
|
@@ -2647,7 +2668,7 @@ var PAAExtractor = class {
|
|
|
2647
2668
|
problem: null,
|
|
2648
2669
|
completeness: { ...this.completeness },
|
|
2649
2670
|
...diagnosticWarnings.length > 0 ? { warnings: diagnosticWarnings } : {},
|
|
2650
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2671
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}
|
|
2651
2672
|
},
|
|
2652
2673
|
totalQuestions: flat.length,
|
|
2653
2674
|
surface: aiSurfaces.surface,
|
|
@@ -3374,8 +3395,33 @@ function classifyAttemptError(err) {
|
|
|
3374
3395
|
return /timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message) ? "timeout" : "error";
|
|
3375
3396
|
}
|
|
3376
3397
|
function classifyAttemptResult(result) {
|
|
3398
|
+
if (result.diagnostics.degradedResult) return "degraded_result";
|
|
3377
3399
|
return result.diagnostics?.completionStatus ?? (result.totalQuestions > 0 ? "paa_found" : "no_paa");
|
|
3378
3400
|
}
|
|
3401
|
+
function resultHasUsableContent(result) {
|
|
3402
|
+
return result.flat.length > 0 || result.organicResults.length > 0 || result.localPack.length > 0 || result.videos.length > 0 || result.forums.length > 0 || result.whatPeopleSaying.length > 0 || result.aiOverview.detected || result.aiMode.detected;
|
|
3403
|
+
}
|
|
3404
|
+
function withResultQuality(result) {
|
|
3405
|
+
const hasUsableContent = resultHasUsableContent(result);
|
|
3406
|
+
const hasWarnings = (result.diagnostics.warnings?.length ?? 0) > 0;
|
|
3407
|
+
const resultQuality = hasUsableContent ? hasWarnings ? "partial" : "complete" : "degraded";
|
|
3408
|
+
const degradedResult = resultQuality === "degraded";
|
|
3409
|
+
result.diagnostics = {
|
|
3410
|
+
...result.diagnostics,
|
|
3411
|
+
resultQuality,
|
|
3412
|
+
degradedResult,
|
|
3413
|
+
degradationReasons: degradedResult ? ["empty_primary_serp"] : [],
|
|
3414
|
+
retryRecommended: degradedResult
|
|
3415
|
+
};
|
|
3416
|
+
return result;
|
|
3417
|
+
}
|
|
3418
|
+
function degradedResultMessage(result) {
|
|
3419
|
+
const reasons = result.diagnostics.degradationReasons ?? [];
|
|
3420
|
+
if (reasons.includes("empty_primary_serp")) {
|
|
3421
|
+
return "No usable primary SERP surface was captured; the result may be incomplete.";
|
|
3422
|
+
}
|
|
3423
|
+
return "The harvest result was marked degraded.";
|
|
3424
|
+
}
|
|
3379
3425
|
function errorMessage(err) {
|
|
3380
3426
|
return err instanceof Error ? err.message : String(err);
|
|
3381
3427
|
}
|
|
@@ -3545,60 +3591,60 @@ async function harvest(rawOptions) {
|
|
|
3545
3591
|
const attempt = baseCtx ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal)) : await extractOnce(attemptOptions, signal);
|
|
3546
3592
|
if (attempt.error) {
|
|
3547
3593
|
const err = attempt.error;
|
|
3548
|
-
const
|
|
3549
|
-
if ((
|
|
3594
|
+
const outcome2 = classifyAttemptError(err);
|
|
3595
|
+
if ((outcome2 === "captcha" || outcome2 === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
3550
3596
|
proxyFailures++;
|
|
3551
3597
|
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
3552
3598
|
}
|
|
3553
|
-
const
|
|
3554
|
-
if (
|
|
3599
|
+
const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome2));
|
|
3600
|
+
if (outcome2 === "captcha") {
|
|
3555
3601
|
console.warn(JSON.stringify({
|
|
3556
3602
|
event: "harvest_attempt_captcha",
|
|
3557
3603
|
attempt_number: attemptNumber,
|
|
3558
3604
|
max_attempts: maxAttempts,
|
|
3559
3605
|
message: errorMessage(err),
|
|
3560
|
-
will_retry:
|
|
3606
|
+
will_retry: willRetry2
|
|
3561
3607
|
}));
|
|
3562
|
-
} else if (
|
|
3608
|
+
} else if (willRetry2) {
|
|
3563
3609
|
console.warn(JSON.stringify({
|
|
3564
3610
|
event: "harvest_attempt_proxy_retry",
|
|
3565
3611
|
attempt_number: attemptNumber,
|
|
3566
3612
|
max_attempts: maxAttempts,
|
|
3567
|
-
outcome,
|
|
3613
|
+
outcome: outcome2,
|
|
3568
3614
|
message: errorMessage(err),
|
|
3569
|
-
will_retry:
|
|
3615
|
+
will_retry: willRetry2
|
|
3570
3616
|
}));
|
|
3571
3617
|
}
|
|
3572
3618
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3573
3619
|
type: "finished",
|
|
3574
3620
|
attemptNumber,
|
|
3575
3621
|
maxAttempts,
|
|
3576
|
-
outcome,
|
|
3622
|
+
outcome: outcome2,
|
|
3577
3623
|
kernelSessionId: attempt.cleanup.kernelSessionId,
|
|
3578
3624
|
questionCount: 0,
|
|
3579
3625
|
durationMs: Date.now() - startedAtMs,
|
|
3580
3626
|
error: errorMessage(err),
|
|
3581
|
-
willRetry,
|
|
3627
|
+
willRetry: willRetry2,
|
|
3582
3628
|
cleanup: attempt.cleanup,
|
|
3583
3629
|
debug: attempt.debug,
|
|
3584
3630
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3585
3631
|
});
|
|
3586
3632
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3587
3633
|
lastError = err;
|
|
3588
|
-
if (
|
|
3634
|
+
if (willRetry2) continue;
|
|
3589
3635
|
break;
|
|
3590
3636
|
}
|
|
3591
3637
|
const result = attempt.result;
|
|
3592
3638
|
if (!result) throw new Error("Harvest attempt completed without a result");
|
|
3593
3639
|
if (shouldRetryLocationMismatch(result, proxyMode)) {
|
|
3594
3640
|
const err = new LocationMismatchError(locationMismatchMessage(result));
|
|
3595
|
-
const
|
|
3641
|
+
const willRetry2 = i < maxAttempts - 1;
|
|
3596
3642
|
console.warn(JSON.stringify({
|
|
3597
3643
|
event: "harvest_attempt_location_mismatch",
|
|
3598
3644
|
attempt_number: attemptNumber,
|
|
3599
3645
|
max_attempts: maxAttempts,
|
|
3600
3646
|
message: err.message,
|
|
3601
|
-
will_retry:
|
|
3647
|
+
will_retry: willRetry2
|
|
3602
3648
|
}));
|
|
3603
3649
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3604
3650
|
type: "finished",
|
|
@@ -3609,32 +3655,45 @@ async function harvest(rawOptions) {
|
|
|
3609
3655
|
questionCount: result.totalQuestions,
|
|
3610
3656
|
durationMs: Date.now() - startedAtMs,
|
|
3611
3657
|
error: err.message,
|
|
3612
|
-
willRetry,
|
|
3658
|
+
willRetry: willRetry2,
|
|
3613
3659
|
cleanup: attempt.cleanup,
|
|
3614
3660
|
debug: attempt.debug,
|
|
3615
3661
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3616
3662
|
});
|
|
3617
3663
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3618
3664
|
lastError = err;
|
|
3619
|
-
if (
|
|
3665
|
+
if (willRetry2) continue;
|
|
3620
3666
|
break;
|
|
3621
3667
|
}
|
|
3622
|
-
const finalResult = stripInternalDebug(result, requestedDebug);
|
|
3668
|
+
const finalResult = withResultQuality(stripInternalDebug(result, requestedDebug));
|
|
3669
|
+
const outcome = classifyAttemptResult(finalResult);
|
|
3670
|
+
const willRetry = outcome === "degraded_result" && i < maxAttempts - 1;
|
|
3671
|
+
const resultError = outcome === "degraded_result" ? degradedResultMessage(finalResult) : null;
|
|
3672
|
+
if (willRetry) {
|
|
3673
|
+
console.warn(JSON.stringify({
|
|
3674
|
+
event: "harvest_attempt_degraded_result",
|
|
3675
|
+
attempt_number: attemptNumber,
|
|
3676
|
+
max_attempts: maxAttempts,
|
|
3677
|
+
degradation_reasons: finalResult.diagnostics.degradationReasons,
|
|
3678
|
+
will_retry: true
|
|
3679
|
+
}));
|
|
3680
|
+
}
|
|
3623
3681
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3624
3682
|
type: "finished",
|
|
3625
3683
|
attemptNumber,
|
|
3626
3684
|
maxAttempts,
|
|
3627
|
-
outcome
|
|
3685
|
+
outcome,
|
|
3628
3686
|
kernelSessionId: attempt.cleanup.kernelSessionId,
|
|
3629
3687
|
questionCount: finalResult.totalQuestions,
|
|
3630
3688
|
durationMs: Date.now() - startedAtMs,
|
|
3631
|
-
error:
|
|
3632
|
-
willRetry
|
|
3689
|
+
error: resultError,
|
|
3690
|
+
willRetry,
|
|
3633
3691
|
cleanup: attempt.cleanup,
|
|
3634
3692
|
debug: attempt.debug,
|
|
3635
3693
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3636
3694
|
});
|
|
3637
3695
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3696
|
+
if (willRetry) continue;
|
|
3638
3697
|
if (attemptOptions.format === "json" || attemptOptions.format === "both") {
|
|
3639
3698
|
await serializer.writeJSON(finalResult, attemptOptions.outputDir);
|
|
3640
3699
|
}
|