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
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
currentCostContext,
|
|
6
6
|
recordKernelSession,
|
|
7
7
|
runWithCostContext
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MA5JBAUZ.js";
|
|
9
9
|
import {
|
|
10
10
|
postToMemoryLibrary
|
|
11
11
|
} from "./chunk-K443GQY5.js";
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
RECAPTCHA_INSTRUCTIONS,
|
|
23
23
|
RequestAbortedError,
|
|
24
24
|
sanitizeVendorName
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-44HZLHDV.js";
|
|
26
26
|
|
|
27
27
|
// src/driver/BrowserDriver.ts
|
|
28
28
|
import { chromium } from "playwright-extra";
|
|
@@ -136,8 +136,6 @@ var MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)
|
|
|
136
136
|
var DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS = 180;
|
|
137
137
|
var KERNEL_BROWSER_CLOSE_TIMEOUT_MS = 3e3;
|
|
138
138
|
var KERNEL_SESSION_DELETE_TIMEOUT_MS = 5e3;
|
|
139
|
-
var DEFAULT_SERP_CHALLENGE_SOLVE_WAIT_MS = 2500;
|
|
140
|
-
var SERP_CHALLENGE_SOLVE_POLL_MS = 500;
|
|
141
139
|
function positiveIntFromEnv(name, fallback) {
|
|
142
140
|
const raw = process.env[name];
|
|
143
141
|
if (!raw) return fallback;
|
|
@@ -509,8 +507,7 @@ var BrowserDriver = class {
|
|
|
509
507
|
}
|
|
510
508
|
if (await this.isSerpChallengeVisible()) {
|
|
511
509
|
await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true });
|
|
512
|
-
|
|
513
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
510
|
+
throw new CaptchaError(this.captchaMessage());
|
|
514
511
|
}
|
|
515
512
|
const fastFound = await this.page.waitForSelector(PAASelectors.item, { timeout: 4e3 }).catch(() => null);
|
|
516
513
|
if (fastFound) {
|
|
@@ -519,8 +516,7 @@ var BrowserDriver = class {
|
|
|
519
516
|
}
|
|
520
517
|
if (await this.isSerpChallengeVisible()) {
|
|
521
518
|
await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true });
|
|
522
|
-
|
|
523
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
519
|
+
throw new CaptchaError(this.captchaMessage());
|
|
524
520
|
}
|
|
525
521
|
for (let i = 1; i <= 6; i++) {
|
|
526
522
|
await this.page.evaluate((f) => {
|
|
@@ -548,23 +544,6 @@ var BrowserDriver = class {
|
|
|
548
544
|
}).catch(() => "");
|
|
549
545
|
return /recaptcha|unusual traffic|are you a robot|about this page|detected unusual traffic/i.test(bodySnippet);
|
|
550
546
|
}
|
|
551
|
-
async waitForSerpChallengeToClear(navDebug, requestedUrl) {
|
|
552
|
-
if (!this.kernelClient || !this.page) return false;
|
|
553
|
-
const waitMs = positiveIntFromEnv("SERP_CHALLENGE_SOLVE_WAIT_MS", DEFAULT_SERP_CHALLENGE_SOLVE_WAIT_MS);
|
|
554
|
-
const deadline = Date.now() + waitMs;
|
|
555
|
-
while (Date.now() < deadline) {
|
|
556
|
-
await this.page.waitForTimeout(Math.min(SERP_CHALLENGE_SOLVE_POLL_MS, Math.max(250, deadline - Date.now())));
|
|
557
|
-
if (!await this.isSerpChallengeVisible()) {
|
|
558
|
-
await this.page.waitForTimeout(1e3);
|
|
559
|
-
if (!await this.isSerpChallengeVisible()) {
|
|
560
|
-
await this.updateSerpNavigationDebug(navDebug, requestedUrl, { hasPaa: null, captchaDetected: false });
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
await this.updateSerpNavigationDebug(navDebug, requestedUrl, { hasPaa: false, captchaDetected: true });
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
568
547
|
async updateSerpNavigationDebug(navDebug, requestedUrl, state) {
|
|
569
548
|
if (!navDebug || !this.page) return;
|
|
570
549
|
try {
|
|
@@ -613,8 +592,7 @@ var BrowserDriver = class {
|
|
|
613
592
|
try {
|
|
614
593
|
await this.page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
615
594
|
if (/google\.[^/]+\/search\?/i.test(url) && await this.isSerpChallengeVisible()) {
|
|
616
|
-
|
|
617
|
-
if (!solved) throw new CaptchaError(this.captchaMessage());
|
|
595
|
+
throw new CaptchaError(this.captchaMessage());
|
|
618
596
|
}
|
|
619
597
|
} catch (err) {
|
|
620
598
|
if (err instanceof CaptchaError) throw err;
|
|
@@ -844,6 +822,27 @@ function normalizeLocation(input) {
|
|
|
844
822
|
return input;
|
|
845
823
|
}
|
|
846
824
|
|
|
825
|
+
// src/serp-location-query.ts
|
|
826
|
+
function normalizedTerms(value) {
|
|
827
|
+
return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
828
|
+
}
|
|
829
|
+
function includesTermSequence(query, phrase) {
|
|
830
|
+
const queryTerms = normalizedTerms(query);
|
|
831
|
+
const phraseTerms = normalizedTerms(phrase);
|
|
832
|
+
if (phraseTerms.length === 0 || phraseTerms.length > queryTerms.length) return false;
|
|
833
|
+
return queryTerms.some((_, start) => phraseTerms.every((term, index) => queryTerms[start + index] === term));
|
|
834
|
+
}
|
|
835
|
+
function cityFromCanonicalLocation(location, fallback) {
|
|
836
|
+
return location.split(",", 1)[0]?.trim() || fallback.split(",", 1)[0]?.trim() || fallback.trim();
|
|
837
|
+
}
|
|
838
|
+
function buildLocalizedSerpQuery(query, location, canonicalLocation) {
|
|
839
|
+
const normalizedQuery = query.trim();
|
|
840
|
+
const requestedLocation = location?.trim();
|
|
841
|
+
if (!requestedLocation || !canonicalLocation) return normalizedQuery;
|
|
842
|
+
const city = cityFromCanonicalLocation(canonicalLocation, requestedLocation);
|
|
843
|
+
return includesTermSequence(normalizedQuery, city) ? normalizedQuery : `${normalizedQuery} ${requestedLocation}`;
|
|
844
|
+
}
|
|
845
|
+
|
|
847
846
|
// src/serp-location-debug.ts
|
|
848
847
|
var STATE_TO_CODE = {
|
|
849
848
|
alabama: "AL",
|
|
@@ -1097,6 +1096,11 @@ async function extractAISurfacesFromDocument(config) {
|
|
|
1097
1096
|
if (!el) return "";
|
|
1098
1097
|
return (el.innerText ?? el.textContent ?? "").trim();
|
|
1099
1098
|
}
|
|
1099
|
+
function hasSubstantiveContent(text) {
|
|
1100
|
+
if (!text) return false;
|
|
1101
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1102
|
+
return normalized.length >= 16 && normalized.split(" ").filter(Boolean).length >= 2;
|
|
1103
|
+
}
|
|
1100
1104
|
function hasAIOverviewLabel(el) {
|
|
1101
1105
|
const heading = el.querySelector(selectors.aio.heading);
|
|
1102
1106
|
if (textOf(heading) === "AI Overview") return true;
|
|
@@ -1259,19 +1263,22 @@ async function extractAISurfacesFromDocument(config) {
|
|
|
1259
1263
|
aioCitations = extractCitations(aioRoot);
|
|
1260
1264
|
}
|
|
1261
1265
|
const aimRoot = document.querySelector(selectors.aim.root);
|
|
1262
|
-
const
|
|
1266
|
+
const aimRootDetected = surface === "aim" && !!aimRoot;
|
|
1263
1267
|
const aimContainer = aimRoot?.closest(selectors.aim.wrapper) ?? aimRoot;
|
|
1264
|
-
const
|
|
1268
|
+
const rawAimText = aimRootDetected ? cleanText(aimContainer) : null;
|
|
1269
|
+
const aimDetected = aimRootDetected && hasSubstantiveContent(rawAimText);
|
|
1270
|
+
const aimText = aimDetected ? rawAimText : null;
|
|
1265
1271
|
const aimCitations = aimDetected ? extractCitations(aimContainer) : [];
|
|
1272
|
+
const aiOverviewDetected = !!aioRoot && hasSubstantiveContent(aioText);
|
|
1266
1273
|
return {
|
|
1267
1274
|
surface,
|
|
1268
1275
|
aiOverview: {
|
|
1269
|
-
detected:
|
|
1270
|
-
text: aioText,
|
|
1271
|
-
citations: aioCitations,
|
|
1272
|
-
expanded: aioExpanded,
|
|
1273
|
-
fullyExpanded: aioFullyExpanded,
|
|
1274
|
-
sections: aioSections
|
|
1276
|
+
detected: aiOverviewDetected,
|
|
1277
|
+
text: aiOverviewDetected ? aioText : null,
|
|
1278
|
+
citations: aiOverviewDetected ? aioCitations : [],
|
|
1279
|
+
expanded: aiOverviewDetected ? aioExpanded : false,
|
|
1280
|
+
fullyExpanded: aiOverviewDetected ? aioFullyExpanded : false,
|
|
1281
|
+
sections: aiOverviewDetected ? aioSections : []
|
|
1275
1282
|
},
|
|
1276
1283
|
aiMode: {
|
|
1277
1284
|
detected: aimDetected && aimText !== null,
|
|
@@ -2045,8 +2052,10 @@ var PAAExtractor = class {
|
|
|
2045
2052
|
this.throwIfAborted(signal);
|
|
2046
2053
|
const canonicalLocation = options.location ? normalizeLocation(options.location) : null;
|
|
2047
2054
|
const uule = canonicalLocation ? encodeUule(canonicalLocation) : null;
|
|
2055
|
+
const executedQuery = buildLocalizedSerpQuery(options.query, options.location, canonicalLocation);
|
|
2056
|
+
const executionOptions = executedQuery === options.query ? options : { ...options, query: executedQuery };
|
|
2048
2057
|
const { hasPaa } = await this.driver.navigateToSERP(
|
|
2049
|
-
|
|
2058
|
+
executionOptions.query,
|
|
2050
2059
|
uule,
|
|
2051
2060
|
options.gl,
|
|
2052
2061
|
options.hl,
|
|
@@ -2071,8 +2080,7 @@ var PAAExtractor = class {
|
|
|
2071
2080
|
let locationEvidence2 = options.debug ? inferSerpLocationEvidence(canonicalLocation, organicResults2, localPack2) : void 0;
|
|
2072
2081
|
let allOrganic2 = organicResults2;
|
|
2073
2082
|
if ((options.pages ?? 1) >= 2) {
|
|
2074
|
-
const p2params = new URLSearchParams({ q:
|
|
2075
|
-
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2083
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2076
2084
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2077
2085
|
if (uule) p2params.set("uule", uule);
|
|
2078
2086
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2084,7 +2092,7 @@ var PAAExtractor = class {
|
|
|
2084
2092
|
}
|
|
2085
2093
|
}
|
|
2086
2094
|
const stats2 = {
|
|
2087
|
-
seed:
|
|
2095
|
+
seed: executionOptions.query,
|
|
2088
2096
|
totalQuestions: 0,
|
|
2089
2097
|
maxDepthReached: 0,
|
|
2090
2098
|
durationMs: Date.now() - startMs,
|
|
@@ -2092,14 +2100,14 @@ var PAAExtractor = class {
|
|
|
2092
2100
|
};
|
|
2093
2101
|
this.reporter.onComplete(stats2);
|
|
2094
2102
|
return {
|
|
2095
|
-
seed:
|
|
2103
|
+
seed: executionOptions.query,
|
|
2096
2104
|
location: options.location ?? null,
|
|
2097
2105
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2098
2106
|
diagnostics: {
|
|
2099
2107
|
completionStatus: "serp_only",
|
|
2100
2108
|
problem: null,
|
|
2101
2109
|
completeness: { ...this.completeness },
|
|
2102
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2110
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence2) } : {}
|
|
2103
2111
|
},
|
|
2104
2112
|
totalQuestions: 0,
|
|
2105
2113
|
surface: aiSurfaces2.surface,
|
|
@@ -2132,8 +2140,7 @@ var PAAExtractor = class {
|
|
|
2132
2140
|
let noPaaOrganic = organicResults;
|
|
2133
2141
|
let locationEvidence2 = initialLocationEvidence;
|
|
2134
2142
|
if ((options.pages ?? 1) >= 2) {
|
|
2135
|
-
const p2params = new URLSearchParams({ q:
|
|
2136
|
-
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2143
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2137
2144
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2138
2145
|
if (uule) p2params.set("uule", uule);
|
|
2139
2146
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2146,7 +2153,7 @@ var PAAExtractor = class {
|
|
|
2146
2153
|
}
|
|
2147
2154
|
const aiSurfaces2 = await this.extractAISurfaces(page, options);
|
|
2148
2155
|
const stats2 = {
|
|
2149
|
-
seed:
|
|
2156
|
+
seed: executionOptions.query,
|
|
2150
2157
|
totalQuestions: 0,
|
|
2151
2158
|
maxDepthReached: 0,
|
|
2152
2159
|
durationMs: Date.now() - startMs,
|
|
@@ -2154,14 +2161,14 @@ var PAAExtractor = class {
|
|
|
2154
2161
|
};
|
|
2155
2162
|
this.reporter.onComplete(stats2);
|
|
2156
2163
|
return {
|
|
2157
|
-
seed:
|
|
2164
|
+
seed: executionOptions.query,
|
|
2158
2165
|
location: options.location ?? null,
|
|
2159
2166
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2160
2167
|
diagnostics: {
|
|
2161
2168
|
completionStatus: "no_paa",
|
|
2162
2169
|
problem: null,
|
|
2163
2170
|
completeness: { ...this.completeness },
|
|
2164
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2171
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence2) } : {}
|
|
2165
2172
|
},
|
|
2166
2173
|
totalQuestions: 0,
|
|
2167
2174
|
surface: aiSurfaces2.surface,
|
|
@@ -2178,10 +2185,10 @@ var PAAExtractor = class {
|
|
|
2178
2185
|
stats: stats2
|
|
2179
2186
|
};
|
|
2180
2187
|
}
|
|
2181
|
-
const flat = await this.runBFS(page,
|
|
2188
|
+
const flat = await this.runBFS(page, executionOptions, signal);
|
|
2182
2189
|
this.throwIfAborted(signal);
|
|
2183
2190
|
const aiSurfaces = await this.extractAISurfaces(page, options);
|
|
2184
|
-
const shortVidsParams = new URLSearchParams({ q:
|
|
2191
|
+
const shortVidsParams = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", udm: ShortVideoSelectors.udm });
|
|
2185
2192
|
if (uule) shortVidsParams.set("uule", uule);
|
|
2186
2193
|
let shortVideos = [];
|
|
2187
2194
|
try {
|
|
@@ -2200,7 +2207,7 @@ var PAAExtractor = class {
|
|
|
2200
2207
|
let allOrganic = organicResults;
|
|
2201
2208
|
let locationEvidence = initialLocationEvidence;
|
|
2202
2209
|
if ((options.pages ?? 1) >= 2) {
|
|
2203
|
-
const p2params = new URLSearchParams({ q:
|
|
2210
|
+
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
2204
2211
|
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
2205
2212
|
if (uule) p2params.set("uule", uule);
|
|
2206
2213
|
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
@@ -2212,9 +2219,9 @@ var PAAExtractor = class {
|
|
|
2212
2219
|
}
|
|
2213
2220
|
}
|
|
2214
2221
|
const allVideos = [...videos, ...shortVideos];
|
|
2215
|
-
const tree = this.buildTree(flat,
|
|
2222
|
+
const tree = this.buildTree(flat, executionOptions.query);
|
|
2216
2223
|
const stats = {
|
|
2217
|
-
seed:
|
|
2224
|
+
seed: executionOptions.query,
|
|
2218
2225
|
totalQuestions: flat.length,
|
|
2219
2226
|
maxDepthReached: flat.reduce((m, r) => Math.max(m, r.depth), 0),
|
|
2220
2227
|
durationMs: Date.now() - startMs,
|
|
@@ -2222,7 +2229,7 @@ var PAAExtractor = class {
|
|
|
2222
2229
|
};
|
|
2223
2230
|
this.reporter.onComplete(stats);
|
|
2224
2231
|
return {
|
|
2225
|
-
seed:
|
|
2232
|
+
seed: executionOptions.query,
|
|
2226
2233
|
location: options.location ?? null,
|
|
2227
2234
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2228
2235
|
diagnostics: {
|
|
@@ -2230,7 +2237,7 @@ var PAAExtractor = class {
|
|
|
2230
2237
|
problem: null,
|
|
2231
2238
|
completeness: { ...this.completeness },
|
|
2232
2239
|
...diagnosticWarnings.length > 0 ? { warnings: diagnosticWarnings } : {},
|
|
2233
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(
|
|
2240
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}
|
|
2234
2241
|
},
|
|
2235
2242
|
totalQuestions: flat.length,
|
|
2236
2243
|
surface: aiSurfaces.surface,
|
|
@@ -2951,8 +2958,33 @@ function classifyAttemptError(err) {
|
|
|
2951
2958
|
return /timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message) ? "timeout" : "error";
|
|
2952
2959
|
}
|
|
2953
2960
|
function classifyAttemptResult(result) {
|
|
2961
|
+
if (result.diagnostics.degradedResult) return "degraded_result";
|
|
2954
2962
|
return result.diagnostics?.completionStatus ?? (result.totalQuestions > 0 ? "paa_found" : "no_paa");
|
|
2955
2963
|
}
|
|
2964
|
+
function resultHasUsableContent(result) {
|
|
2965
|
+
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;
|
|
2966
|
+
}
|
|
2967
|
+
function withResultQuality(result) {
|
|
2968
|
+
const hasUsableContent = resultHasUsableContent(result);
|
|
2969
|
+
const hasWarnings = (result.diagnostics.warnings?.length ?? 0) > 0;
|
|
2970
|
+
const resultQuality = hasUsableContent ? hasWarnings ? "partial" : "complete" : "degraded";
|
|
2971
|
+
const degradedResult = resultQuality === "degraded";
|
|
2972
|
+
result.diagnostics = {
|
|
2973
|
+
...result.diagnostics,
|
|
2974
|
+
resultQuality,
|
|
2975
|
+
degradedResult,
|
|
2976
|
+
degradationReasons: degradedResult ? ["empty_primary_serp"] : [],
|
|
2977
|
+
retryRecommended: degradedResult
|
|
2978
|
+
};
|
|
2979
|
+
return result;
|
|
2980
|
+
}
|
|
2981
|
+
function degradedResultMessage(result) {
|
|
2982
|
+
const reasons = result.diagnostics.degradationReasons ?? [];
|
|
2983
|
+
if (reasons.includes("empty_primary_serp")) {
|
|
2984
|
+
return "No usable primary SERP surface was captured; the result may be incomplete.";
|
|
2985
|
+
}
|
|
2986
|
+
return "The harvest result was marked degraded.";
|
|
2987
|
+
}
|
|
2956
2988
|
function errorMessage(err) {
|
|
2957
2989
|
return err instanceof Error ? err.message : String(err);
|
|
2958
2990
|
}
|
|
@@ -3122,60 +3154,60 @@ async function harvest(rawOptions) {
|
|
|
3122
3154
|
const attempt = baseCtx ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal)) : await extractOnce(attemptOptions, signal);
|
|
3123
3155
|
if (attempt.error) {
|
|
3124
3156
|
const err = attempt.error;
|
|
3125
|
-
const
|
|
3126
|
-
if ((
|
|
3157
|
+
const outcome2 = classifyAttemptError(err);
|
|
3158
|
+
if ((outcome2 === "captcha" || outcome2 === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
3127
3159
|
proxyFailures++;
|
|
3128
3160
|
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
3129
3161
|
}
|
|
3130
|
-
const
|
|
3131
|
-
if (
|
|
3162
|
+
const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome2));
|
|
3163
|
+
if (outcome2 === "captcha") {
|
|
3132
3164
|
console.warn(JSON.stringify({
|
|
3133
3165
|
event: "harvest_attempt_captcha",
|
|
3134
3166
|
attempt_number: attemptNumber,
|
|
3135
3167
|
max_attempts: maxAttempts,
|
|
3136
3168
|
message: errorMessage(err),
|
|
3137
|
-
will_retry:
|
|
3169
|
+
will_retry: willRetry2
|
|
3138
3170
|
}));
|
|
3139
|
-
} else if (
|
|
3171
|
+
} else if (willRetry2) {
|
|
3140
3172
|
console.warn(JSON.stringify({
|
|
3141
3173
|
event: "harvest_attempt_proxy_retry",
|
|
3142
3174
|
attempt_number: attemptNumber,
|
|
3143
3175
|
max_attempts: maxAttempts,
|
|
3144
|
-
outcome,
|
|
3176
|
+
outcome: outcome2,
|
|
3145
3177
|
message: errorMessage(err),
|
|
3146
|
-
will_retry:
|
|
3178
|
+
will_retry: willRetry2
|
|
3147
3179
|
}));
|
|
3148
3180
|
}
|
|
3149
3181
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3150
3182
|
type: "finished",
|
|
3151
3183
|
attemptNumber,
|
|
3152
3184
|
maxAttempts,
|
|
3153
|
-
outcome,
|
|
3185
|
+
outcome: outcome2,
|
|
3154
3186
|
kernelSessionId: attempt.cleanup.kernelSessionId,
|
|
3155
3187
|
questionCount: 0,
|
|
3156
3188
|
durationMs: Date.now() - startedAtMs,
|
|
3157
3189
|
error: errorMessage(err),
|
|
3158
|
-
willRetry,
|
|
3190
|
+
willRetry: willRetry2,
|
|
3159
3191
|
cleanup: attempt.cleanup,
|
|
3160
3192
|
debug: attempt.debug,
|
|
3161
3193
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3162
3194
|
});
|
|
3163
3195
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3164
3196
|
lastError = err;
|
|
3165
|
-
if (
|
|
3197
|
+
if (willRetry2) continue;
|
|
3166
3198
|
break;
|
|
3167
3199
|
}
|
|
3168
3200
|
const result = attempt.result;
|
|
3169
3201
|
if (!result) throw new Error("Harvest attempt completed without a result");
|
|
3170
3202
|
if (shouldRetryLocationMismatch(result, proxyMode)) {
|
|
3171
3203
|
const err = new LocationMismatchError(locationMismatchMessage(result));
|
|
3172
|
-
const
|
|
3204
|
+
const willRetry2 = i < maxAttempts - 1;
|
|
3173
3205
|
console.warn(JSON.stringify({
|
|
3174
3206
|
event: "harvest_attempt_location_mismatch",
|
|
3175
3207
|
attempt_number: attemptNumber,
|
|
3176
3208
|
max_attempts: maxAttempts,
|
|
3177
3209
|
message: err.message,
|
|
3178
|
-
will_retry:
|
|
3210
|
+
will_retry: willRetry2
|
|
3179
3211
|
}));
|
|
3180
3212
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3181
3213
|
type: "finished",
|
|
@@ -3186,32 +3218,45 @@ async function harvest(rawOptions) {
|
|
|
3186
3218
|
questionCount: result.totalQuestions,
|
|
3187
3219
|
durationMs: Date.now() - startedAtMs,
|
|
3188
3220
|
error: err.message,
|
|
3189
|
-
willRetry,
|
|
3221
|
+
willRetry: willRetry2,
|
|
3190
3222
|
cleanup: attempt.cleanup,
|
|
3191
3223
|
debug: attempt.debug,
|
|
3192
3224
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3193
3225
|
});
|
|
3194
3226
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3195
3227
|
lastError = err;
|
|
3196
|
-
if (
|
|
3228
|
+
if (willRetry2) continue;
|
|
3197
3229
|
break;
|
|
3198
3230
|
}
|
|
3199
|
-
const finalResult = stripInternalDebug(result, requestedDebug);
|
|
3231
|
+
const finalResult = withResultQuality(stripInternalDebug(result, requestedDebug));
|
|
3232
|
+
const outcome = classifyAttemptResult(finalResult);
|
|
3233
|
+
const willRetry = outcome === "degraded_result" && i < maxAttempts - 1;
|
|
3234
|
+
const resultError = outcome === "degraded_result" ? degradedResultMessage(finalResult) : null;
|
|
3235
|
+
if (willRetry) {
|
|
3236
|
+
console.warn(JSON.stringify({
|
|
3237
|
+
event: "harvest_attempt_degraded_result",
|
|
3238
|
+
attempt_number: attemptNumber,
|
|
3239
|
+
max_attempts: maxAttempts,
|
|
3240
|
+
degradation_reasons: finalResult.diagnostics.degradationReasons,
|
|
3241
|
+
will_retry: true
|
|
3242
|
+
}));
|
|
3243
|
+
}
|
|
3200
3244
|
await emitAttemptEvent(onAttemptEvent, {
|
|
3201
3245
|
type: "finished",
|
|
3202
3246
|
attemptNumber,
|
|
3203
3247
|
maxAttempts,
|
|
3204
|
-
outcome
|
|
3248
|
+
outcome,
|
|
3205
3249
|
kernelSessionId: attempt.cleanup.kernelSessionId,
|
|
3206
3250
|
questionCount: finalResult.totalQuestions,
|
|
3207
3251
|
durationMs: Date.now() - startedAtMs,
|
|
3208
|
-
error:
|
|
3209
|
-
willRetry
|
|
3252
|
+
error: resultError,
|
|
3253
|
+
willRetry,
|
|
3210
3254
|
cleanup: attempt.cleanup,
|
|
3211
3255
|
debug: attempt.debug,
|
|
3212
3256
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3213
3257
|
});
|
|
3214
3258
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
3259
|
+
if (willRetry) continue;
|
|
3215
3260
|
if (attemptOptions.format === "json" || attemptOptions.format === "both") {
|
|
3216
3261
|
await serializer.writeJSON(finalResult, attemptOptions.outputDir);
|
|
3217
3262
|
}
|
|
@@ -3301,4 +3346,4 @@ export {
|
|
|
3301
3346
|
resolveKernelProxyId,
|
|
3302
3347
|
harvest
|
|
3303
3348
|
};
|
|
3304
|
-
//# sourceMappingURL=chunk-
|
|
3349
|
+
//# sourceMappingURL=chunk-LOPKN3YL.js.map
|