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
|
@@ -7,19 +7,19 @@ import {
|
|
|
7
7
|
registerMemoryMcpTools,
|
|
8
8
|
registerPaaExtractorMcpTools,
|
|
9
9
|
registerSerpIntelligenceCaptureTools
|
|
10
|
-
} from "../chunk-
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
10
|
+
} from "../chunk-JK2FRDAP.js";
|
|
11
|
+
import "../chunk-345BQXZH.js";
|
|
12
|
+
import "../chunk-MA5JBAUZ.js";
|
|
13
13
|
import {
|
|
14
14
|
renderInstallTerminal
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-G7KAVJ3F.js";
|
|
16
16
|
import "../chunk-CB5C3BPB.js";
|
|
17
17
|
import {
|
|
18
18
|
PACKAGE_VERSION
|
|
19
|
-
} from "../chunk-
|
|
20
|
-
import "../chunk-
|
|
21
|
-
import "../chunk-
|
|
22
|
-
import "../chunk-
|
|
19
|
+
} from "../chunk-3LWYPAU5.js";
|
|
20
|
+
import "../chunk-PWPUKR5U.js";
|
|
21
|
+
import "../chunk-44HZLHDV.js";
|
|
22
|
+
import "../chunk-CSCD2HNS.js";
|
|
23
23
|
|
|
24
24
|
// bin/mcp-stdio-server.ts
|
|
25
25
|
import { readFileSync } from "fs";
|
package/dist/bin/paa-harvest.cjs
CHANGED
|
@@ -328,6 +328,16 @@ function currentCostContext() {
|
|
|
328
328
|
var KERNEL_HEADLESS_USD_PER_SEC = 166667e-10;
|
|
329
329
|
var KERNEL_HEADFUL_USD_PER_SEC = 1333336e-10;
|
|
330
330
|
var HEADLESS_OPS = /* @__PURE__ */ new Set(["serp", "maps_search", "yt_channel", "yt_transcription", "fb_search", "fb_ad", "instagram"]);
|
|
331
|
+
function envRate(name, fallback) {
|
|
332
|
+
const raw = process.env[name]?.trim();
|
|
333
|
+
if (!raw) return fallback;
|
|
334
|
+
const parsed = Number(raw);
|
|
335
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
336
|
+
}
|
|
337
|
+
var NANGO_USD_PER_CONNECTION_MONTH = envRate("NANGO_USD_PER_CONNECTION_MONTH", 1);
|
|
338
|
+
var NANGO_USD_PER_FUNCTION_RUN = envRate("NANGO_USD_PER_FUNCTION_RUN", 1e-4);
|
|
339
|
+
var NANGO_USD_PER_PROXY_REQUEST = envRate("NANGO_USD_PER_PROXY_REQUEST", 1e-4);
|
|
340
|
+
var NANGO_USD_PER_COMPUTE_SEC = envRate("NANGO_USD_PER_COMPUTE_SEC", 2e-4);
|
|
331
341
|
function kernelCostUsd(ms, headful) {
|
|
332
342
|
const sec = Math.max(0, ms) / 1e3;
|
|
333
343
|
return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC);
|
|
@@ -502,8 +512,6 @@ var MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)
|
|
|
502
512
|
var DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS = 180;
|
|
503
513
|
var KERNEL_BROWSER_CLOSE_TIMEOUT_MS = 3e3;
|
|
504
514
|
var KERNEL_SESSION_DELETE_TIMEOUT_MS = 5e3;
|
|
505
|
-
var DEFAULT_SERP_CHALLENGE_SOLVE_WAIT_MS = 2500;
|
|
506
|
-
var SERP_CHALLENGE_SOLVE_POLL_MS = 500;
|
|
507
515
|
function positiveIntFromEnv(name, fallback) {
|
|
508
516
|
const raw = process.env[name];
|
|
509
517
|
if (!raw) return fallback;
|
|
@@ -875,8 +883,7 @@ var BrowserDriver = class {
|
|
|
875
883
|
}
|
|
876
884
|
if (await this.isSerpChallengeVisible()) {
|
|
877
885
|
await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true });
|
|
878
|
-
|
|
879
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
886
|
+
throw new CaptchaError(this.captchaMessage());
|
|
880
887
|
}
|
|
881
888
|
const fastFound = await this.page.waitForSelector(PAASelectors.item, { timeout: 4e3 }).catch(() => null);
|
|
882
889
|
if (fastFound) {
|
|
@@ -885,8 +892,7 @@ var BrowserDriver = class {
|
|
|
885
892
|
}
|
|
886
893
|
if (await this.isSerpChallengeVisible()) {
|
|
887
894
|
await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true });
|
|
888
|
-
|
|
889
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
895
|
+
throw new CaptchaError(this.captchaMessage());
|
|
890
896
|
}
|
|
891
897
|
for (let i = 1; i <= 6; i++) {
|
|
892
898
|
await this.page.evaluate((f) => {
|
|
@@ -914,23 +920,6 @@ var BrowserDriver = class {
|
|
|
914
920
|
}).catch(() => "");
|
|
915
921
|
return /recaptcha|unusual traffic|are you a robot|about this page|detected unusual traffic/i.test(bodySnippet);
|
|
916
922
|
}
|
|
917
|
-
async waitForSerpChallengeToClear(navDebug, requestedUrl) {
|
|
918
|
-
if (!this.kernelClient || !this.page) return false;
|
|
919
|
-
const waitMs = positiveIntFromEnv("SERP_CHALLENGE_SOLVE_WAIT_MS", DEFAULT_SERP_CHALLENGE_SOLVE_WAIT_MS);
|
|
920
|
-
const deadline = Date.now() + waitMs;
|
|
921
|
-
while (Date.now() < deadline) {
|
|
922
|
-
await this.page.waitForTimeout(Math.min(SERP_CHALLENGE_SOLVE_POLL_MS, Math.max(250, deadline - Date.now())));
|
|
923
|
-
if (!await this.isSerpChallengeVisible()) {
|
|
924
|
-
await this.page.waitForTimeout(1e3);
|
|
925
|
-
if (!await this.isSerpChallengeVisible()) {
|
|
926
|
-
await this.updateSerpNavigationDebug(navDebug, requestedUrl, { hasPaa: null, captchaDetected: false });
|
|
927
|
-
return true;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
await this.updateSerpNavigationDebug(navDebug, requestedUrl, { hasPaa: false, captchaDetected: true });
|
|
932
|
-
return false;
|
|
933
|
-
}
|
|
934
923
|
async updateSerpNavigationDebug(navDebug, requestedUrl, state) {
|
|
935
924
|
if (!navDebug || !this.page) return;
|
|
936
925
|
try {
|
|
@@ -979,8 +968,7 @@ var BrowserDriver = class {
|
|
|
979
968
|
try {
|
|
980
969
|
await this.page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
981
970
|
if (/google\.[^/]+\/search\?/i.test(url) && await this.isSerpChallengeVisible()) {
|
|
982
|
-
|
|
983
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
971
|
+
throw new CaptchaError(this.captchaMessage());
|
|
984
972
|
}
|
|
985
973
|
} catch (err) {
|
|
986
974
|
if (err instanceof CaptchaError) throw err;
|
|
@@ -1210,6 +1198,27 @@ function normalizeLocation(input) {
|
|
|
1210
1198
|
return input;
|
|
1211
1199
|
}
|
|
1212
1200
|
|
|
1201
|
+
// src/serp-location-query.ts
|
|
1202
|
+
function normalizedTerms(value) {
|
|
1203
|
+
return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
1204
|
+
}
|
|
1205
|
+
function includesTermSequence(query, phrase) {
|
|
1206
|
+
const queryTerms = normalizedTerms(query);
|
|
1207
|
+
const phraseTerms = normalizedTerms(phrase);
|
|
1208
|
+
if (phraseTerms.length === 0 || phraseTerms.length > queryTerms.length) return false;
|
|
1209
|
+
return queryTerms.some((_, start) => phraseTerms.every((term, index) => queryTerms[start + index] === term));
|
|
1210
|
+
}
|
|
1211
|
+
function cityFromCanonicalLocation(location, fallback) {
|
|
1212
|
+
return location.split(",", 1)[0]?.trim() || fallback.split(",", 1)[0]?.trim() || fallback.trim();
|
|
1213
|
+
}
|
|
1214
|
+
function buildLocalizedSerpQuery(query, location, canonicalLocation) {
|
|
1215
|
+
const normalizedQuery = query.trim();
|
|
1216
|
+
const requestedLocation = location?.trim();
|
|
1217
|
+
if (!requestedLocation || !canonicalLocation) return normalizedQuery;
|
|
1218
|
+
const city = cityFromCanonicalLocation(canonicalLocation, requestedLocation);
|
|
1219
|
+
return includesTermSequence(normalizedQuery, city) ? normalizedQuery : `${normalizedQuery} ${requestedLocation}`;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1213
1222
|
// src/serp-location-debug.ts
|
|
1214
1223
|
var STATE_TO_CODE = {
|
|
1215
1224
|
alabama: "AL",
|
|
@@ -1463,6 +1472,11 @@ async function extractAISurfacesFromDocument(config) {
|
|
|
1463
1472
|
if (!el) return "";
|
|
1464
1473
|
return (el.innerText ?? el.textContent ?? "").trim();
|
|
1465
1474
|
}
|
|
1475
|
+
function hasSubstantiveContent(text) {
|
|
1476
|
+
if (!text) return false;
|
|
1477
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1478
|
+
return normalized.length >= 16 && normalized.split(" ").filter(Boolean).length >= 2;
|
|
1479
|
+
}
|
|
1466
1480
|
function hasAIOverviewLabel(el) {
|
|
1467
1481
|
const heading = el.querySelector(selectors.aio.heading);
|
|
1468
1482
|
if (textOf(heading) === "AI Overview") return true;
|
|
@@ -1625,19 +1639,22 @@ async function extractAISurfacesFromDocument(config) {
|
|
|
1625
1639
|
aioCitations = extractCitations(aioRoot);
|
|
1626
1640
|
}
|
|
1627
1641
|
const aimRoot = document.querySelector(selectors.aim.root);
|
|
1628
|
-
const
|
|
1642
|
+
const aimRootDetected = surface === "aim" && !!aimRoot;
|
|
1629
1643
|
const aimContainer = aimRoot?.closest(selectors.aim.wrapper) ?? aimRoot;
|
|
1630
|
-
const
|
|
1644
|
+
const rawAimText = aimRootDetected ? cleanText(aimContainer) : null;
|
|
1645
|
+
const aimDetected = aimRootDetected && hasSubstantiveContent(rawAimText);
|
|
1646
|
+
const aimText = aimDetected ? rawAimText : null;
|
|
1631
1647
|
const aimCitations = aimDetected ? extractCitations(aimContainer) : [];
|
|
1648
|
+
const aiOverviewDetected = !!aioRoot && hasSubstantiveContent(aioText);
|
|
1632
1649
|
return {
|
|
1633
1650
|
surface,
|
|
1634
1651
|
aiOverview: {
|
|
1635
|
-
detected:
|
|
1636
|
-
text: aioText,
|
|
1637
|
-
citations: aioCitations,
|
|
1638
|
-
expanded: aioExpanded,
|
|
1639
|
-
fullyExpanded: aioFullyExpanded,
|
|
1640
|
-
sections: aioSections
|
|
1652
|
+
detected: aiOverviewDetected,
|
|
1653
|
+
text: aiOverviewDetected ? aioText : null,
|
|
1654
|
+
citations: aiOverviewDetected ? aioCitations : [],
|
|
1655
|
+
expanded: aiOverviewDetected ? aioExpanded : false,
|
|
1656
|
+
fullyExpanded: aiOverviewDetected ? aioFullyExpanded : false,
|
|
1657
|
+
sections: aiOverviewDetected ? aioSections : []
|
|
1641
1658
|
},
|
|
1642
1659
|
aiMode: {
|
|
1643
1660
|
detected: aimDetected && aimText !== null,
|
|
@@ -2411,8 +2428,10 @@ var PAAExtractor = class {
|
|
|
2411
2428
|
this.throwIfAborted(signal);
|
|
2412
2429
|
const canonicalLocation = options.location ? normalizeLocation(options.location) : null;
|
|
2413
2430
|
const uule = canonicalLocation ? encodeUule(canonicalLocation) : null;
|
|
2431
|
+
const executedQuery = buildLocalizedSerpQuery(options.query, options.location, canonicalLocation);
|
|
2432
|
+
const executionOptions = executedQuery === options.query ? options : { ...options, query: executedQuery };
|
|
2414
2433
|
const { hasPaa } = await this.driver.navigateToSERP(
|
|
2415
|
-
|
|
2434
|
+
executionOptions.query,
|
|
2416
2435
|
uule,
|
|
2417
2436
|
options.gl,
|
|
2418
2437
|
options.hl,
|
|
@@ -2437,8 +2456,7 @@ var PAAExtractor = class {
|
|
|
2437
2456
|
let locationEvidence2 = options.debug ? inferSerpLocationEvidence(canonicalLocation, organicResults2, localPack2) : void 0;
|
|
2438
2457
|
let allOrganic2 = organicResults2;
|
|
2439
2458
|
if ((options.pages ?? 1) >= 2) {
|
|
2440
|
-
const p2params = new URLSearchParams({ q:
|
|
2441
|
-
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2459
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2442
2460
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2443
2461
|
if (uule) p2params.set("uule", uule);
|
|
2444
2462
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2450,7 +2468,7 @@ var PAAExtractor = class {
|
|
|
2450
2468
|
}
|
|
2451
2469
|
}
|
|
2452
2470
|
const stats2 = {
|
|
2453
|
-
seed:
|
|
2471
|
+
seed: executionOptions.query,
|
|
2454
2472
|
totalQuestions: 0,
|
|
2455
2473
|
maxDepthReached: 0,
|
|
2456
2474
|
durationMs: Date.now() - startMs,
|
|
@@ -2458,14 +2476,14 @@ var PAAExtractor = class {
|
|
|
2458
2476
|
};
|
|
2459
2477
|
this.reporter.onComplete(stats2);
|
|
2460
2478
|
return {
|
|
2461
|
-
seed:
|
|
2479
|
+
seed: executionOptions.query,
|
|
2462
2480
|
location: options.location ?? null,
|
|
2463
2481
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2464
2482
|
diagnostics: {
|
|
2465
2483
|
completionStatus: "serp_only",
|
|
2466
2484
|
problem: null,
|
|
2467
2485
|
completeness: { ...this.completeness },
|
|
2468
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2486
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence2) } : {}
|
|
2469
2487
|
},
|
|
2470
2488
|
totalQuestions: 0,
|
|
2471
2489
|
surface: aiSurfaces2.surface,
|
|
@@ -2498,8 +2516,7 @@ var PAAExtractor = class {
|
|
|
2498
2516
|
let noPaaOrganic = organicResults;
|
|
2499
2517
|
let locationEvidence2 = initialLocationEvidence;
|
|
2500
2518
|
if ((options.pages ?? 1) >= 2) {
|
|
2501
|
-
const p2params = new URLSearchParams({ q:
|
|
2502
|
-
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2519
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2503
2520
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2504
2521
|
if (uule) p2params.set("uule", uule);
|
|
2505
2522
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2512,7 +2529,7 @@ var PAAExtractor = class {
|
|
|
2512
2529
|
}
|
|
2513
2530
|
const aiSurfaces2 = await this.extractAISurfaces(page, options);
|
|
2514
2531
|
const stats2 = {
|
|
2515
|
-
seed:
|
|
2532
|
+
seed: executionOptions.query,
|
|
2516
2533
|
totalQuestions: 0,
|
|
2517
2534
|
maxDepthReached: 0,
|
|
2518
2535
|
durationMs: Date.now() - startMs,
|
|
@@ -2520,14 +2537,14 @@ var PAAExtractor = class {
|
|
|
2520
2537
|
};
|
|
2521
2538
|
this.reporter.onComplete(stats2);
|
|
2522
2539
|
return {
|
|
2523
|
-
seed:
|
|
2540
|
+
seed: executionOptions.query,
|
|
2524
2541
|
location: options.location ?? null,
|
|
2525
2542
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2526
2543
|
diagnostics: {
|
|
2527
2544
|
completionStatus: "no_paa",
|
|
2528
2545
|
problem: null,
|
|
2529
2546
|
completeness: { ...this.completeness },
|
|
2530
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2547
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence2) } : {}
|
|
2531
2548
|
},
|
|
2532
2549
|
totalQuestions: 0,
|
|
2533
2550
|
surface: aiSurfaces2.surface,
|
|
@@ -2544,10 +2561,10 @@ var PAAExtractor = class {
|
|
|
2544
2561
|
stats: stats2
|
|
2545
2562
|
};
|
|
2546
2563
|
}
|
|
2547
|
-
const flat = await this.runBFS(page,
|
|
2564
|
+
const flat = await this.runBFS(page, executionOptions, signal);
|
|
2548
2565
|
this.throwIfAborted(signal);
|
|
2549
2566
|
const aiSurfaces = await this.extractAISurfaces(page, options);
|
|
2550
|
-
const shortVidsParams = new URLSearchParams({ q:
|
|
2567
|
+
const shortVidsParams = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", udm: ShortVideoSelectors.udm });
|
|
2551
2568
|
if (uule) shortVidsParams.set("uule", uule);
|
|
2552
2569
|
let shortVideos = [];
|
|
2553
2570
|
try {
|
|
@@ -2566,7 +2583,7 @@ var PAAExtractor = class {
|
|
|
2566
2583
|
let allOrganic = organicResults;
|
|
2567
2584
|
let locationEvidence = initialLocationEvidence;
|
|
2568
2585
|
if ((options.pages ?? 1) >= 2) {
|
|
2569
|
-
const p2params = new URLSearchParams({ q:
|
|
2586
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2570
2587
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2571
2588
|
if (uule) p2params.set("uule", uule);
|
|
2572
2589
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2578,9 +2595,9 @@ var PAAExtractor = class {
|
|
|
2578
2595
|
}
|
|
2579
2596
|
}
|
|
2580
2597
|
const allVideos = [...videos, ...shortVideos];
|
|
2581
|
-
const tree = this.buildTree(flat,
|
|
2598
|
+
const tree = this.buildTree(flat, executionOptions.query);
|
|
2582
2599
|
const stats = {
|
|
2583
|
-
seed:
|
|
2600
|
+
seed: executionOptions.query,
|
|
2584
2601
|
totalQuestions: flat.length,
|
|
2585
2602
|
maxDepthReached: flat.reduce((m, r) => Math.max(m, r.depth), 0),
|
|
2586
2603
|
durationMs: Date.now() - startMs,
|
|
@@ -2588,7 +2605,7 @@ var PAAExtractor = class {
|
|
|
2588
2605
|
};
|
|
2589
2606
|
this.reporter.onComplete(stats);
|
|
2590
2607
|
return {
|
|
2591
|
-
seed:
|
|
2608
|
+
seed: executionOptions.query,
|
|
2592
2609
|
location: options.location ?? null,
|
|
2593
2610
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2594
2611
|
diagnostics: {
|
|
@@ -2596,7 +2613,7 @@ var PAAExtractor = class {
|
|
|
2596
2613
|
problem: null,
|
|
2597
2614
|
completeness: { ...this.completeness },
|
|
2598
2615
|
...diagnosticWarnings.length > 0 ? { warnings: diagnosticWarnings } : {},
|
|
2599
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2616
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}
|
|
2600
2617
|
},
|
|
2601
2618
|
totalQuestions: flat.length,
|
|
2602
2619
|
surface: aiSurfaces.surface,
|
|
@@ -3323,8 +3340,33 @@ function classifyAttemptError(err) {
|
|
|
3323
3340
|
return /timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message) ? "timeout" : "error";
|
|
3324
3341
|
}
|
|
3325
3342
|
function classifyAttemptResult(result) {
|
|
3343
|
+
if (result.diagnostics.degradedResult) return "degraded_result";
|
|
3326
3344
|
return result.diagnostics?.completionStatus ?? (result.totalQuestions > 0 ? "paa_found" : "no_paa");
|
|
3327
3345
|
}
|
|
3346
|
+
function resultHasUsableContent(result) {
|
|
3347
|
+
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;
|
|
3348
|
+
}
|
|
3349
|
+
function withResultQuality(result) {
|
|
3350
|
+
const hasUsableContent = resultHasUsableContent(result);
|
|
3351
|
+
const hasWarnings = (result.diagnostics.warnings?.length ?? 0) > 0;
|
|
3352
|
+
const resultQuality = hasUsableContent ? hasWarnings ? "partial" : "complete" : "degraded";
|
|
3353
|
+
const degradedResult = resultQuality === "degraded";
|
|
3354
|
+
result.diagnostics = {
|
|
3355
|
+
...result.diagnostics,
|
|
3356
|
+
resultQuality,
|
|
3357
|
+
degradedResult,
|
|
3358
|
+
degradationReasons: degradedResult ? ["empty_primary_serp"] : [],
|
|
3359
|
+
retryRecommended: degradedResult
|
|
3360
|
+
};
|
|
3361
|
+
return result;
|
|
3362
|
+
}
|
|
3363
|
+
function degradedResultMessage(result) {
|
|
3364
|
+
const reasons = result.diagnostics.degradationReasons ?? [];
|
|
3365
|
+
if (reasons.includes("empty_primary_serp")) {
|
|
3366
|
+
return "No usable primary SERP surface was captured; the result may be incomplete.";
|
|
3367
|
+
}
|
|
3368
|
+
return "The harvest result was marked degraded.";
|
|
3369
|
+
}
|
|
3328
3370
|
function errorMessage(err) {
|
|
3329
3371
|
return err instanceof Error ? err.message : String(err);
|
|
3330
3372
|
}
|
|
@@ -3494,60 +3536,60 @@ async function harvest(rawOptions) {
|
|
|
3494
3536
|
const attempt = baseCtx ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal)) : await extractOnce(attemptOptions, signal);
|
|
3495
3537
|
if (attempt.error) {
|
|
3496
3538
|
const err = attempt.error;
|
|
3497
|
-
const
|
|
3498
|
-
if ((
|
|
3539
|
+
const outcome2 = classifyAttemptError(err);
|
|
3540
|
+
if ((outcome2 === "captcha" || outcome2 === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
3499
3541
|
proxyFailures++;
|
|
3500
3542
|
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
3501
3543
|
}
|
|
3502
|
-
const
|
|
3503
|
-
if (
|
|
3544
|
+
const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome2));
|
|
3545
|
+
if (outcome2 === "captcha") {
|
|
3504
3546
|
console.warn(JSON.stringify({
|
|
3505
3547
|
event: "harvest_attempt_captcha",
|
|
3506
3548
|
attempt_number: attemptNumber,
|
|
3507
3549
|
max_attempts: maxAttempts,
|
|
3508
3550
|
message: errorMessage(err),
|
|
3509
|
-
will_retry:
|
|
3551
|
+
will_retry: willRetry2
|
|
3510
3552
|
}));
|
|
3511
|
-
} else if (
|
|
3553
|
+
} else if (willRetry2) {
|
|
3512
3554
|
console.warn(JSON.stringify({
|
|
3513
3555
|
event: "harvest_attempt_proxy_retry",
|
|
3514
3556
|
attempt_number: attemptNumber,
|
|
3515
3557
|
max_attempts: maxAttempts,
|
|
3516
|
-
outcome,
|
|
3558
|
+
outcome: outcome2,
|
|
3517
3559
|
message: errorMessage(err),
|
|
3518
|
-
will_retry:
|
|
3560
|
+
will_retry: willRetry2
|
|
3519
3561
|
}));
|
|
3520
3562
|
}
|
|
3521
3563
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3522
3564
|
type: "finished",
|
|
3523
3565
|
attemptNumber,
|
|
3524
3566
|
maxAttempts,
|
|
3525
|
-
outcome,
|
|
3567
|
+
outcome: outcome2,
|
|
3526
3568
|
kernelSessionId: attempt.cleanup.kernelSessionId,
|
|
3527
3569
|
questionCount: 0,
|
|
3528
3570
|
durationMs: Date.now() - startedAtMs,
|
|
3529
3571
|
error: errorMessage(err),
|
|
3530
|
-
willRetry,
|
|
3572
|
+
willRetry: willRetry2,
|
|
3531
3573
|
cleanup: attempt.cleanup,
|
|
3532
3574
|
debug: attempt.debug,
|
|
3533
3575
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3534
3576
|
});
|
|
3535
3577
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3536
3578
|
lastError = err;
|
|
3537
|
-
if (
|
|
3579
|
+
if (willRetry2) continue;
|
|
3538
3580
|
break;
|
|
3539
3581
|
}
|
|
3540
3582
|
const result = attempt.result;
|
|
3541
3583
|
if (!result) throw new Error("Harvest attempt completed without a result");
|
|
3542
3584
|
if (shouldRetryLocationMismatch(result, proxyMode)) {
|
|
3543
3585
|
const err = new LocationMismatchError(locationMismatchMessage(result));
|
|
3544
|
-
const
|
|
3586
|
+
const willRetry2 = i < maxAttempts - 1;
|
|
3545
3587
|
console.warn(JSON.stringify({
|
|
3546
3588
|
event: "harvest_attempt_location_mismatch",
|
|
3547
3589
|
attempt_number: attemptNumber,
|
|
3548
3590
|
max_attempts: maxAttempts,
|
|
3549
3591
|
message: err.message,
|
|
3550
|
-
will_retry:
|
|
3592
|
+
will_retry: willRetry2
|
|
3551
3593
|
}));
|
|
3552
3594
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3553
3595
|
type: "finished",
|
|
@@ -3558,32 +3600,45 @@ async function harvest(rawOptions) {
|
|
|
3558
3600
|
questionCount: result.totalQuestions,
|
|
3559
3601
|
durationMs: Date.now() - startedAtMs,
|
|
3560
3602
|
error: err.message,
|
|
3561
|
-
willRetry,
|
|
3603
|
+
willRetry: willRetry2,
|
|
3562
3604
|
cleanup: attempt.cleanup,
|
|
3563
3605
|
debug: attempt.debug,
|
|
3564
3606
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3565
3607
|
});
|
|
3566
3608
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3567
3609
|
lastError = err;
|
|
3568
|
-
if (
|
|
3610
|
+
if (willRetry2) continue;
|
|
3569
3611
|
break;
|
|
3570
3612
|
}
|
|
3571
|
-
const finalResult = stripInternalDebug(result, requestedDebug);
|
|
3613
|
+
const finalResult = withResultQuality(stripInternalDebug(result, requestedDebug));
|
|
3614
|
+
const outcome = classifyAttemptResult(finalResult);
|
|
3615
|
+
const willRetry = outcome === "degraded_result" && i < maxAttempts - 1;
|
|
3616
|
+
const resultError = outcome === "degraded_result" ? degradedResultMessage(finalResult) : null;
|
|
3617
|
+
if (willRetry) {
|
|
3618
|
+
console.warn(JSON.stringify({
|
|
3619
|
+
event: "harvest_attempt_degraded_result",
|
|
3620
|
+
attempt_number: attemptNumber,
|
|
3621
|
+
max_attempts: maxAttempts,
|
|
3622
|
+
degradation_reasons: finalResult.diagnostics.degradationReasons,
|
|
3623
|
+
will_retry: true
|
|
3624
|
+
}));
|
|
3625
|
+
}
|
|
3572
3626
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3573
3627
|
type: "finished",
|
|
3574
3628
|
attemptNumber,
|
|
3575
3629
|
maxAttempts,
|
|
3576
|
-
outcome
|
|
3630
|
+
outcome,
|
|
3577
3631
|
kernelSessionId: attempt.cleanup.kernelSessionId,
|
|
3578
3632
|
questionCount: finalResult.totalQuestions,
|
|
3579
3633
|
durationMs: Date.now() - startedAtMs,
|
|
3580
|
-
error:
|
|
3581
|
-
willRetry
|
|
3634
|
+
error: resultError,
|
|
3635
|
+
willRetry,
|
|
3582
3636
|
cleanup: attempt.cleanup,
|
|
3583
3637
|
debug: attempt.debug,
|
|
3584
3638
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3585
3639
|
});
|
|
3586
3640
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3641
|
+
if (willRetry) continue;
|
|
3587
3642
|
if (attemptOptions.format === "json" || attemptOptions.format === "both") {
|
|
3588
3643
|
await serializer.writeJSON(finalResult, attemptOptions.outputDir);
|
|
3589
3644
|
}
|