mcp-scraper 0.69.0 → 0.72.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/CHANGELOG.md +60 -2
- package/README.md +6 -2
- package/dist/{analytics-repository-62CAZAIP.js → analytics-repository-6YLRSWYO.js} +3 -2
- package/dist/bin/api-server.cjs +18592 -12680
- package/dist/bin/api-server.js +4 -4
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +3 -3
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +10492 -9129
- package/dist/bin/mcp-stdio-server.js +9 -7
- package/dist/bin/paa-harvest.cjs +308 -41
- package/dist/bin/paa-harvest.js +4 -3
- package/dist/{chunk-M7D7WO75.js → chunk-2N4V4LVU.js} +3178 -2019
- package/dist/chunk-7RQULQF3.js +263 -0
- package/dist/{chunk-UL4ZKAWZ.js → chunk-A66DGFOU.js} +1 -1
- package/dist/{chunk-ES25GP6C.js → chunk-CXY5WV45.js} +24 -34
- package/dist/{chunk-Y46YNQMM.js → chunk-F5TK4KMT.js} +1 -1
- package/dist/{chunk-62LEXS5O.js → chunk-F6MUGMRN.js} +306 -44
- package/dist/{chunk-5RFQGIYC.js → chunk-FVL4GUTP.js} +2 -2
- package/dist/{chunk-OZIUFN6B.js → chunk-I45JV4EU.js} +2 -2
- package/dist/{chunk-2LXTOAKS.js → chunk-KIPIBPUB.js} +9 -1
- package/dist/chunk-MB72PA6S.js +2003 -0
- package/dist/chunk-OM7HVEJ3.js +26 -0
- package/dist/chunk-PJEEKOUM.js +404 -0
- package/dist/{chunk-RX2QAHML.js → chunk-RAFQEPJ4.js} +2 -2
- package/dist/{chunk-KO5CK5ZT.js → chunk-SR7GSLEA.js} +202 -441
- package/dist/chunk-T3MZISOF.js +240 -0
- package/dist/{chunk-ULYOCJUJ.js → chunk-TUTQHP4O.js} +1 -1
- package/dist/{chunk-65FTMB7G.js → chunk-WEFPBAAG.js} +180 -9
- package/dist/{chunk-LN6N3YLK.js → chunk-WFQ2E4WE.js} +1 -1
- package/dist/{db-ZGZAKYBW.js → db-566MOHHD.js} +7 -1
- package/dist/{extract-bundle-SXSJCPQI.js → extract-bundle-337ZDIIQ.js} +4 -4
- package/dist/gmail-service-E276SBJU.js +25 -0
- package/dist/index.cjs +322 -49
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +4 -3
- package/dist/{lead-list-enrichment-repository-CXLVIV3R.js → lead-list-enrichment-repository-WWEMJ7EN.js} +2 -2
- package/dist/{location-data-repository-G72M7DTN.js → location-data-repository-H4B2BWGY.js} +2 -2
- package/dist/{server-WCNYXILA.js → server-UGWC5YMD.js} +8376 -6616
- package/dist/{site-extract-repository-RLB3TFJ3.js → site-extract-repository-ACNAVNDN.js} +3 -3
- package/dist/{worker-KHSJG365.js → worker-JQORPCFQ.js} +9 -7
- package/package.json +1 -1
- package/dist/chunk-2L4C4DAZ.js +0 -103
package/dist/bin/paa-harvest.cjs
CHANGED
|
@@ -430,6 +430,7 @@ var NANGO_USD_PER_CONNECTION_MONTH = envRate("NANGO_USD_PER_CONNECTION_MONTH", 1
|
|
|
430
430
|
var NANGO_USD_PER_FUNCTION_RUN = envRate("NANGO_USD_PER_FUNCTION_RUN", 1e-4);
|
|
431
431
|
var NANGO_USD_PER_PROXY_REQUEST = envRate("NANGO_USD_PER_PROXY_REQUEST", 1e-4);
|
|
432
432
|
var NANGO_USD_PER_COMPUTE_SEC = envRate("NANGO_USD_PER_COMPUTE_SEC", 2e-4);
|
|
433
|
+
var BRIGHTDATA_BROWSER_USD_PER_GB = envRate("BRIGHTDATA_BROWSER_USD_PER_GB", 0);
|
|
433
434
|
function kernelCostUsd(ms, headful) {
|
|
434
435
|
const sec = Math.max(0, ms) / 1e3;
|
|
435
436
|
return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC);
|
|
@@ -452,7 +453,8 @@ async function runCostTelemetryMigration() {
|
|
|
452
453
|
SELECT
|
|
453
454
|
(SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN ('kernel_session_log', 'vendor_usage_log', 'cost_probe_runs')) = 3
|
|
454
455
|
AND (SELECT COUNT(*) FROM pragma_table_info('kernel_session_log') WHERE name IN ('proxy_source', 'proxy_type', 'method')) = 3
|
|
455
|
-
AND (SELECT COUNT(*) FROM pragma_table_info('vendor_usage_log') WHERE name
|
|
456
|
+
AND (SELECT COUNT(*) FROM pragma_table_info('vendor_usage_log') WHERE name IN ('method', 'source_key')) = 2
|
|
457
|
+
AND (SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name = 'vendor_usage_log_vendor_source_key') = 1
|
|
456
458
|
AND (SELECT COUNT(*) FROM pragma_table_info('cost_probe_runs') WHERE name IN ('units', 'unit_type', 'mode')) = 3
|
|
457
459
|
AS ready
|
|
458
460
|
`);
|
|
@@ -508,6 +510,7 @@ async function runCostTelemetryMigration() {
|
|
|
508
510
|
unit_type TEXT,
|
|
509
511
|
est_cost_usd REAL NOT NULL DEFAULT 0,
|
|
510
512
|
error TEXT,
|
|
513
|
+
source_key TEXT,
|
|
511
514
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
512
515
|
)
|
|
513
516
|
`);
|
|
@@ -518,6 +521,11 @@ async function runCostTelemetryMigration() {
|
|
|
518
521
|
await db.execute(`ALTER TABLE vendor_usage_log ADD COLUMN method TEXT`);
|
|
519
522
|
} catch {
|
|
520
523
|
}
|
|
524
|
+
try {
|
|
525
|
+
await db.execute(`ALTER TABLE vendor_usage_log ADD COLUMN source_key TEXT`);
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS vendor_usage_log_vendor_source_key ON vendor_usage_log(vendor, source_key) WHERE source_key IS NOT NULL`);
|
|
521
529
|
await db.execute(`
|
|
522
530
|
CREATE TABLE IF NOT EXISTS cost_probe_runs (
|
|
523
531
|
id TEXT PRIMARY KEY,
|
|
@@ -1122,6 +1130,13 @@ var BrowserDriver = class {
|
|
|
1122
1130
|
getKernelSessionId() {
|
|
1123
1131
|
return this.kernelSessionId;
|
|
1124
1132
|
}
|
|
1133
|
+
getProviderSessionMetadata() {
|
|
1134
|
+
return {
|
|
1135
|
+
provider: this.kernelSessionId ? "kernel" : "local",
|
|
1136
|
+
providerSessionId: this.kernelSessionId,
|
|
1137
|
+
disconnectObservation: null
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1125
1140
|
getDebugSnapshot() {
|
|
1126
1141
|
return this.debugSnapshot;
|
|
1127
1142
|
}
|
|
@@ -1136,6 +1151,7 @@ var BrowserDriver = class {
|
|
|
1136
1151
|
const proxySource = this.kernelProxySource;
|
|
1137
1152
|
const proxyType = this.kernelProxyType;
|
|
1138
1153
|
const headlessSent = this.kernelHeadlessSent;
|
|
1154
|
+
const provider = this.getProviderSessionMetadata();
|
|
1139
1155
|
this.browser = null;
|
|
1140
1156
|
this.context = null;
|
|
1141
1157
|
this.page = null;
|
|
@@ -1170,7 +1186,11 @@ var BrowserDriver = class {
|
|
|
1170
1186
|
kernelDeleteSucceeded: deleteResult.status === "fulfilled",
|
|
1171
1187
|
kernelDeleteError: deleteResult.status === "rejected" ? deleteResult.reason instanceof Error ? deleteResult.reason.message : String(deleteResult.reason) : null,
|
|
1172
1188
|
browserCloseSucceeded: closeResult.status === "fulfilled",
|
|
1173
|
-
browserCloseError: closeResult.status === "rejected" ? closeResult.reason instanceof Error ? closeResult.reason.message : String(closeResult.reason) : null
|
|
1189
|
+
browserCloseError: closeResult.status === "rejected" ? closeResult.reason instanceof Error ? closeResult.reason.message : String(closeResult.reason) : null,
|
|
1190
|
+
provider: provider.provider,
|
|
1191
|
+
providerSessionId: provider.providerSessionId,
|
|
1192
|
+
providerDisconnectObserved: false,
|
|
1193
|
+
providerDisconnectMessage: null
|
|
1174
1194
|
};
|
|
1175
1195
|
if (deleteResult.status === "rejected") {
|
|
1176
1196
|
console.warn(JSON.stringify({
|
|
@@ -1202,7 +1222,11 @@ var BrowserDriver = class {
|
|
|
1202
1222
|
kernelDeleteSucceeded: null,
|
|
1203
1223
|
kernelDeleteError: null,
|
|
1204
1224
|
browserCloseSucceeded: true,
|
|
1205
|
-
browserCloseError: null
|
|
1225
|
+
browserCloseError: null,
|
|
1226
|
+
provider: provider.provider,
|
|
1227
|
+
providerSessionId: provider.providerSessionId,
|
|
1228
|
+
providerDisconnectObserved: false,
|
|
1229
|
+
providerDisconnectMessage: null
|
|
1206
1230
|
};
|
|
1207
1231
|
} else if (this.context) {
|
|
1208
1232
|
const ctx = this.context;
|
|
@@ -1215,7 +1239,11 @@ var BrowserDriver = class {
|
|
|
1215
1239
|
kernelDeleteSucceeded: null,
|
|
1216
1240
|
kernelDeleteError: null,
|
|
1217
1241
|
browserCloseSucceeded: true,
|
|
1218
|
-
browserCloseError: null
|
|
1242
|
+
browserCloseError: null,
|
|
1243
|
+
provider: "local",
|
|
1244
|
+
providerSessionId: null,
|
|
1245
|
+
providerDisconnectObserved: false,
|
|
1246
|
+
providerDisconnectMessage: null
|
|
1219
1247
|
};
|
|
1220
1248
|
}
|
|
1221
1249
|
return {
|
|
@@ -1224,13 +1252,64 @@ var BrowserDriver = class {
|
|
|
1224
1252
|
kernelDeleteSucceeded: null,
|
|
1225
1253
|
kernelDeleteError: null,
|
|
1226
1254
|
browserCloseSucceeded: null,
|
|
1227
|
-
browserCloseError: null
|
|
1255
|
+
browserCloseError: null,
|
|
1256
|
+
provider: "local",
|
|
1257
|
+
providerSessionId: null,
|
|
1258
|
+
providerDisconnectObserved: false,
|
|
1259
|
+
providerDisconnectMessage: null
|
|
1228
1260
|
};
|
|
1229
1261
|
}
|
|
1230
1262
|
};
|
|
1231
1263
|
|
|
1232
1264
|
// src/driver/BrightDataSerpDriver.ts
|
|
1233
1265
|
var import_playwright2 = require("playwright");
|
|
1266
|
+
|
|
1267
|
+
// src/driver/browser-provider-telemetry.ts
|
|
1268
|
+
var MAX_PROVIDER_SESSION_ID_LENGTH = 512;
|
|
1269
|
+
var MAX_PROVIDER_MESSAGE_LENGTH = 320;
|
|
1270
|
+
function boundedText(value, maxLength) {
|
|
1271
|
+
if (typeof value !== "string") return null;
|
|
1272
|
+
const normalized = value.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1273
|
+
if (!normalized) return null;
|
|
1274
|
+
return normalized.slice(0, maxLength);
|
|
1275
|
+
}
|
|
1276
|
+
function boundedProviderMessage(value) {
|
|
1277
|
+
const message = boundedText(value, MAX_PROVIDER_MESSAGE_LENGTH);
|
|
1278
|
+
if (!message) return null;
|
|
1279
|
+
return message.replace(/\b(?:wss?|https?):\/\/\S+/gi, "[redacted endpoint]").replace(/\b(?:authorization|proxy-authorization):\s*\S+/gi, "$1: [redacted]").replace(/\bBearer\s+\S+/gi, "Bearer [redacted]").slice(0, MAX_PROVIDER_MESSAGE_LENGTH);
|
|
1280
|
+
}
|
|
1281
|
+
function sanitizeProviderLocalMessage(value) {
|
|
1282
|
+
return boundedProviderMessage(value);
|
|
1283
|
+
}
|
|
1284
|
+
function validateProviderSessionId(value) {
|
|
1285
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_PROVIDER_SESSION_ID_LENGTH || value.trim() !== value || /[\s\u0000-\u001f\u007f]/.test(value) || /:\/\//.test(value)) {
|
|
1286
|
+
throw new Error("Bright Data did not return a valid browser session ID");
|
|
1287
|
+
}
|
|
1288
|
+
return value;
|
|
1289
|
+
}
|
|
1290
|
+
function makeProviderInterruptionObservation(source, message, observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1291
|
+
const safeMessage = boundedProviderMessage(message);
|
|
1292
|
+
return {
|
|
1293
|
+
source,
|
|
1294
|
+
message: safeMessage ?? source.replaceAll("_", " "),
|
|
1295
|
+
observedAt
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
function projectProviderSessionPublic(metadata) {
|
|
1299
|
+
return {
|
|
1300
|
+
provider: metadata.provider,
|
|
1301
|
+
providerDisconnectObserved: metadata.disconnectObservation !== null,
|
|
1302
|
+
providerDisconnectMessage: metadata.disconnectObservation?.message ?? null
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function projectProviderSessionPrivate(metadata) {
|
|
1306
|
+
return {
|
|
1307
|
+
...projectProviderSessionPublic(metadata),
|
|
1308
|
+
providerSessionId: metadata.providerSessionId
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// src/driver/BrightDataSerpDriver.ts
|
|
1234
1313
|
var BLOCKED_TYPES = /* @__PURE__ */ new Set(["image", "media", "font", "stylesheet", "ping"]);
|
|
1235
1314
|
var BLOCKED_HOST_PARTS = [
|
|
1236
1315
|
"doubleclick.net",
|
|
@@ -1280,8 +1359,19 @@ var BrightDataSerpDriver = class {
|
|
|
1280
1359
|
browser = null;
|
|
1281
1360
|
context = null;
|
|
1282
1361
|
page = null;
|
|
1362
|
+
cdpSession = null;
|
|
1363
|
+
providerSessionId = null;
|
|
1364
|
+
disconnectObservation = null;
|
|
1365
|
+
closing = false;
|
|
1366
|
+
observeDisconnect(source, message) {
|
|
1367
|
+
if (this.closing || this.disconnectObservation) return;
|
|
1368
|
+
this.disconnectObservation = makeProviderInterruptionObservation(source, message);
|
|
1369
|
+
}
|
|
1283
1370
|
async launch(_config) {
|
|
1284
1371
|
if (this.page) return;
|
|
1372
|
+
this.closing = false;
|
|
1373
|
+
this.providerSessionId = null;
|
|
1374
|
+
this.disconnectObservation = null;
|
|
1285
1375
|
const endpoint = brightDataBrowserEndpoint();
|
|
1286
1376
|
if (!endpoint) throw new Error("SERP browser service is not configured");
|
|
1287
1377
|
this.browser = await import_playwright2.chromium.connectOverCDP(endpoint);
|
|
@@ -1297,6 +1387,14 @@ var BrightDataSerpDriver = class {
|
|
|
1297
1387
|
}
|
|
1298
1388
|
});
|
|
1299
1389
|
this.page = this.context.pages()[0] ?? await this.context.newPage();
|
|
1390
|
+
this.browser.on("disconnected", () => this.observeDisconnect("browser_disconnected"));
|
|
1391
|
+
this.context.on("close", () => this.observeDisconnect("context_closed"));
|
|
1392
|
+
this.page.on("close", () => this.observeDisconnect("page_closed"));
|
|
1393
|
+
this.page.on("crash", () => this.observeDisconnect("page_crashed"));
|
|
1394
|
+
this.cdpSession = await this.context.newCDPSession(this.page);
|
|
1395
|
+
const customCdp = this.cdpSession;
|
|
1396
|
+
const sessionResponse = await customCdp.send("Browser.getSessionId");
|
|
1397
|
+
this.providerSessionId = validateProviderSessionId(sessionResponse?.sessionId);
|
|
1300
1398
|
await this.page.route("**/*", async (route) => {
|
|
1301
1399
|
const request = route.request();
|
|
1302
1400
|
const resourceType = request.resourceType();
|
|
@@ -1352,23 +1450,38 @@ var BrightDataSerpDriver = class {
|
|
|
1352
1450
|
getKernelSessionId() {
|
|
1353
1451
|
return null;
|
|
1354
1452
|
}
|
|
1453
|
+
getProviderSessionMetadata() {
|
|
1454
|
+
return {
|
|
1455
|
+
provider: "bright_data_browser_api",
|
|
1456
|
+
providerSessionId: this.providerSessionId,
|
|
1457
|
+
disconnectObservation: this.disconnectObservation
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1355
1460
|
getDebugSnapshot() {
|
|
1356
1461
|
return { kernel: null, context: null, networkLocation: null, serpNavigation: null };
|
|
1357
1462
|
}
|
|
1358
1463
|
async close() {
|
|
1464
|
+
this.closing = true;
|
|
1359
1465
|
let error = null;
|
|
1360
1466
|
try {
|
|
1361
1467
|
await this.browser?.close();
|
|
1362
1468
|
} catch (caught) {
|
|
1363
|
-
error = caught instanceof Error ? caught.message : String(caught);
|
|
1469
|
+
error = sanitizeProviderLocalMessage(caught instanceof Error ? caught.message : String(caught)) ?? "Bright Data browser close failed";
|
|
1470
|
+
this.disconnectObservation ??= makeProviderInterruptionObservation("browser_close_error", error);
|
|
1364
1471
|
}
|
|
1472
|
+
const provider = projectProviderSessionPrivate(this.getProviderSessionMetadata());
|
|
1473
|
+
this.browser = null;
|
|
1474
|
+
this.context = null;
|
|
1475
|
+
this.page = null;
|
|
1476
|
+
this.cdpSession = null;
|
|
1365
1477
|
return {
|
|
1366
1478
|
kernelSessionId: null,
|
|
1367
1479
|
kernelDeleteStarted: false,
|
|
1368
1480
|
kernelDeleteSucceeded: null,
|
|
1369
1481
|
kernelDeleteError: null,
|
|
1370
1482
|
browserCloseSucceeded: error === null,
|
|
1371
|
-
browserCloseError: error
|
|
1483
|
+
browserCloseError: error,
|
|
1484
|
+
...provider
|
|
1372
1485
|
};
|
|
1373
1486
|
}
|
|
1374
1487
|
};
|
|
@@ -2264,12 +2377,14 @@ async function extractAISurfacesFromDocument(config) {
|
|
|
2264
2377
|
|
|
2265
2378
|
// src/extractor/PAAExtractor.ts
|
|
2266
2379
|
var PAAExtractor = class {
|
|
2267
|
-
constructor(driver, reporter) {
|
|
2380
|
+
constructor(driver, reporter, onPaaProgress) {
|
|
2268
2381
|
this.driver = driver;
|
|
2269
2382
|
this.reporter = reporter;
|
|
2383
|
+
this.onPaaProgress = onPaaProgress;
|
|
2270
2384
|
}
|
|
2271
2385
|
driver;
|
|
2272
2386
|
reporter;
|
|
2387
|
+
onPaaProgress;
|
|
2273
2388
|
completeness = {
|
|
2274
2389
|
paaWithoutAnswer: 0,
|
|
2275
2390
|
paaWithoutSource: 0,
|
|
@@ -2279,6 +2394,8 @@ var PAAExtractor = class {
|
|
|
2279
2394
|
aioShareUrlEarly = null;
|
|
2280
2395
|
collectedRows = [];
|
|
2281
2396
|
partialContext = null;
|
|
2397
|
+
progressSequence = 0;
|
|
2398
|
+
clickedQuestions = /* @__PURE__ */ new Set();
|
|
2282
2399
|
normalizeQuestion(q) {
|
|
2283
2400
|
return q.toLowerCase().replace(/[^\w\s]/g, "").replace(/\s+/g, " ").trim();
|
|
2284
2401
|
}
|
|
@@ -2315,6 +2432,34 @@ var PAAExtractor = class {
|
|
|
2315
2432
|
function cleanSourceLabel(value) {
|
|
2316
2433
|
return (value ?? "").replace(/\s*\(\+\d+\)\s*-\s*View related links.*$/i, "").replace(/\s*-\s*Opens in new tab.*$/i, "").trim();
|
|
2317
2434
|
}
|
|
2435
|
+
function isGoogleHost(host) {
|
|
2436
|
+
const normalized = host.toLowerCase().replace(/\.$/, "");
|
|
2437
|
+
return normalized === "google.com" || normalized.endsWith(".google.com") || /^google\.[a-z.]+$/.test(normalized) || /\.google\.[a-z.]+$/.test(normalized) || normalized === "googleusercontent.com" || normalized.endsWith(".googleusercontent.com");
|
|
2438
|
+
}
|
|
2439
|
+
function safeCitationTarget(rawHref) {
|
|
2440
|
+
let parsed;
|
|
2441
|
+
try {
|
|
2442
|
+
parsed = new URL(rawHref);
|
|
2443
|
+
} catch {
|
|
2444
|
+
return null;
|
|
2445
|
+
}
|
|
2446
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
2447
|
+
if (parsed.username || parsed.password) return null;
|
|
2448
|
+
if (isGoogleHost(parsed.hostname)) {
|
|
2449
|
+
if (parsed.pathname !== "/url") return null;
|
|
2450
|
+
const redirected = parsed.searchParams.get("q") ?? parsed.searchParams.get("url");
|
|
2451
|
+
if (!redirected) return null;
|
|
2452
|
+
try {
|
|
2453
|
+
parsed = new URL(redirected);
|
|
2454
|
+
} catch {
|
|
2455
|
+
return null;
|
|
2456
|
+
}
|
|
2457
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
2458
|
+
if (parsed.username || parsed.password) return null;
|
|
2459
|
+
}
|
|
2460
|
+
if (isGoogleHost(parsed.hostname)) return null;
|
|
2461
|
+
return { href: parsed.href, host: parsed.hostname };
|
|
2462
|
+
}
|
|
2318
2463
|
return Array.from(document.querySelectorAll(selectors.item)).filter((pair) => !onlyQuestion2 || (pair.getAttribute(selectors.itemDataQ) || pair.getAttribute(selectors.itemDataInitQ) || pair.querySelector(selectors.itemQuestionEl)?.innerText?.trim() || "") === onlyQuestion2).map((pair) => {
|
|
2319
2464
|
const clickTarget = pair.querySelector(selectors.clickTarget);
|
|
2320
2465
|
const expanded = pair.classList.contains(selectors.expandedClass) || clickTarget?.getAttribute("aria-expanded") === "true";
|
|
@@ -2336,21 +2481,14 @@ var PAAExtractor = class {
|
|
|
2336
2481
|
...Array.from(anchorRoot.querySelectorAll("a[href]"))
|
|
2337
2482
|
];
|
|
2338
2483
|
for (const a of prioritized) {
|
|
2339
|
-
const
|
|
2340
|
-
if (
|
|
2341
|
-
|
|
2342
|
-
try {
|
|
2343
|
-
host = new URL(href).hostname;
|
|
2344
|
-
} catch {
|
|
2345
|
-
continue;
|
|
2346
|
-
}
|
|
2347
|
-
if (/(^|\.)google(usercontent)?\.[a-z.]+$/.test(host)) continue;
|
|
2348
|
-
seenHrefs.add(href);
|
|
2484
|
+
const target = safeCitationTarget(a.href);
|
|
2485
|
+
if (!target || seenHrefs.has(target.href)) continue;
|
|
2486
|
+
seenHrefs.add(target.href);
|
|
2349
2487
|
anchors.push({
|
|
2350
2488
|
text: (a.textContent || "").trim(),
|
|
2351
2489
|
label: cleanSourceLabel(a.getAttribute("aria-label")),
|
|
2352
|
-
href,
|
|
2353
|
-
host
|
|
2490
|
+
href: target.href,
|
|
2491
|
+
host: target.host
|
|
2354
2492
|
});
|
|
2355
2493
|
}
|
|
2356
2494
|
const primary = anchors.find((x) => x.label.length > 2 || x.text.length > 2) ?? anchors[0];
|
|
@@ -2407,10 +2545,53 @@ var PAAExtractor = class {
|
|
|
2407
2545
|
extracted_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2408
2546
|
};
|
|
2409
2547
|
}
|
|
2548
|
+
upsertCollectedRow(row) {
|
|
2549
|
+
const key = this.normalizeQuestion(row.question);
|
|
2550
|
+
const index = this.collectedRows.findIndex((existing2) => this.normalizeQuestion(existing2.question) === key);
|
|
2551
|
+
if (index < 0) {
|
|
2552
|
+
this.collectedRows.push({ ...row });
|
|
2553
|
+
return true;
|
|
2554
|
+
}
|
|
2555
|
+
const existing = this.collectedRows[index];
|
|
2556
|
+
const merged = {
|
|
2557
|
+
...existing,
|
|
2558
|
+
...row,
|
|
2559
|
+
answer: row.answer || existing.answer,
|
|
2560
|
+
source_title: row.source_title || existing.source_title,
|
|
2561
|
+
source_site: row.source_site || existing.source_site,
|
|
2562
|
+
source_cite: row.source_cite || existing.source_cite,
|
|
2563
|
+
extracted_at: row.extracted_at || existing.extracted_at
|
|
2564
|
+
};
|
|
2565
|
+
const changed = merged.answer !== existing.answer || merged.source_title !== existing.source_title || merged.source_site !== existing.source_site || merged.source_cite !== existing.source_cite;
|
|
2566
|
+
if (changed) this.collectedRows[index] = merged;
|
|
2567
|
+
return changed;
|
|
2568
|
+
}
|
|
2569
|
+
async emitProgress(phase) {
|
|
2570
|
+
if (!this.onPaaProgress || this.collectedRows.length === 0) return;
|
|
2571
|
+
const snapshot = {
|
|
2572
|
+
sequence: ++this.progressSequence,
|
|
2573
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2574
|
+
clickedQuestions: [...this.clickedQuestions],
|
|
2575
|
+
phase,
|
|
2576
|
+
records: this.collectedRows.map((row) => ({ ...row }))
|
|
2577
|
+
};
|
|
2578
|
+
try {
|
|
2579
|
+
await this.onPaaProgress(snapshot);
|
|
2580
|
+
} catch (err) {
|
|
2581
|
+
console.warn(JSON.stringify({
|
|
2582
|
+
event: "paa_progress_sink_failed",
|
|
2583
|
+
sequence: snapshot.sequence,
|
|
2584
|
+
phase: snapshot.phase,
|
|
2585
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2586
|
+
}));
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2410
2589
|
getPartialResult() {
|
|
2411
2590
|
const ctx = this.partialContext;
|
|
2412
2591
|
if (!ctx || this.collectedRows.length === 0) return null;
|
|
2413
|
-
const flat =
|
|
2592
|
+
const flat = this.collectedRows.map((row) => ({ ...row }));
|
|
2593
|
+
const paaWithoutAnswer = ctx.questionsOnly ? 0 : flat.filter((row) => !row.answer?.trim()).length;
|
|
2594
|
+
const paaWithoutSource = ctx.questionsOnly ? 0 : flat.filter((row) => !row.source_cite?.trim()).length;
|
|
2414
2595
|
const stats = {
|
|
2415
2596
|
seed: ctx.seed,
|
|
2416
2597
|
totalQuestions: flat.length,
|
|
@@ -2425,7 +2606,17 @@ var PAAExtractor = class {
|
|
|
2425
2606
|
diagnostics: {
|
|
2426
2607
|
completionStatus: "paa_found",
|
|
2427
2608
|
problem: null,
|
|
2428
|
-
|
|
2609
|
+
resultQuality: "partial",
|
|
2610
|
+
degradedResult: false,
|
|
2611
|
+
retryRecommended: true,
|
|
2612
|
+
completeness: {
|
|
2613
|
+
...this.completeness,
|
|
2614
|
+
paaWithoutAnswer,
|
|
2615
|
+
paaWithoutSource,
|
|
2616
|
+
paaRequested: ctx.maxQuestions,
|
|
2617
|
+
paaReturned: flat.length,
|
|
2618
|
+
paaUnique: flat.length
|
|
2619
|
+
},
|
|
2429
2620
|
warnings: [{
|
|
2430
2621
|
code: "paa_partial",
|
|
2431
2622
|
surface: "paa",
|
|
@@ -2434,6 +2625,9 @@ var PAAExtractor = class {
|
|
|
2434
2625
|
}]
|
|
2435
2626
|
},
|
|
2436
2627
|
totalQuestions: flat.length,
|
|
2628
|
+
surface: "web",
|
|
2629
|
+
aiOverview: { detected: false, text: null, citations: [] },
|
|
2630
|
+
aiMode: { detected: false, text: null, citations: [] },
|
|
2437
2631
|
whatPeopleSaying: [],
|
|
2438
2632
|
tree: this.buildTree(flat, ctx.seed),
|
|
2439
2633
|
flat,
|
|
@@ -2441,7 +2635,7 @@ var PAAExtractor = class {
|
|
|
2441
2635
|
forums: [],
|
|
2442
2636
|
organicResults: [],
|
|
2443
2637
|
localPack: [],
|
|
2444
|
-
entityIds: {},
|
|
2638
|
+
entityIds: { entities: [], kgIds: [], cids: [], gcids: [] },
|
|
2445
2639
|
stats
|
|
2446
2640
|
};
|
|
2447
2641
|
}
|
|
@@ -2451,7 +2645,8 @@ var PAAExtractor = class {
|
|
|
2451
2645
|
const seenQs = /* @__PURE__ */ new Set();
|
|
2452
2646
|
const orderedQs = [];
|
|
2453
2647
|
this.collectedRows = [];
|
|
2454
|
-
|
|
2648
|
+
this.progressSequence = 0;
|
|
2649
|
+
this.clickedQuestions = /* @__PURE__ */ new Set();
|
|
2455
2650
|
const abandonedNeverReclick = /* @__PURE__ */ new Set();
|
|
2456
2651
|
const clickedOnceEver = /* @__PURE__ */ new Set();
|
|
2457
2652
|
const capturedItems = /* @__PURE__ */ new Map();
|
|
@@ -2499,12 +2694,17 @@ var PAAExtractor = class {
|
|
|
2499
2694
|
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
2500
2695
|
this.throwIfAborted(signal);
|
|
2501
2696
|
const states = await readItemStates();
|
|
2697
|
+
let discovered = false;
|
|
2502
2698
|
for (const s of states) {
|
|
2503
2699
|
if (!seenQs.has(s.q)) {
|
|
2504
2700
|
seenQs.add(s.q);
|
|
2505
2701
|
orderedQs.push(s.q);
|
|
2702
|
+
if (orderedQs.length <= options.maxQuestions) {
|
|
2703
|
+
discovered = this.upsertCollectedRow(this.toFlatRow({ question: s.q }, 1, null, options.query)) || discovered;
|
|
2704
|
+
}
|
|
2506
2705
|
}
|
|
2507
2706
|
}
|
|
2707
|
+
if (discovered) await this.emitProgress("discovered");
|
|
2508
2708
|
if (options.questionsOnly && seenQs.size >= options.maxQuestions) break;
|
|
2509
2709
|
const kept = new Set(orderedQs.slice(0, options.maxQuestions));
|
|
2510
2710
|
const clickable = states.filter((s) => kept.has(s.q) && !clickedOnceEver.has(s.q) && !abandonedNeverReclick.has(s.q));
|
|
@@ -2524,6 +2724,7 @@ var PAAExtractor = class {
|
|
|
2524
2724
|
this.reporter.onDepth(++round);
|
|
2525
2725
|
await this.throwIfCaptcha(page, "Google PAA expansion");
|
|
2526
2726
|
clickedOnceEver.add(target.q);
|
|
2727
|
+
this.clickedQuestions.add(target.q);
|
|
2527
2728
|
const expansionStatus = await expandOneItemSerially(target.q);
|
|
2528
2729
|
if (!options.questionsOnly) {
|
|
2529
2730
|
let clickedItem = (await this.extractVisibleItems(page, target.q))[0];
|
|
@@ -2531,7 +2732,12 @@ var PAAExtractor = class {
|
|
|
2531
2732
|
await page.waitForTimeout(1300);
|
|
2532
2733
|
clickedItem = (await this.extractVisibleItems(page, target.q))[0];
|
|
2533
2734
|
}
|
|
2534
|
-
if (clickedItem
|
|
2735
|
+
if (clickedItem) {
|
|
2736
|
+
capturedItems.set(target.q, clickedItem);
|
|
2737
|
+
if (this.upsertCollectedRow(this.toFlatRow(clickedItem, 1, null, options.query))) {
|
|
2738
|
+
await this.emitProgress("answer_captured");
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2535
2741
|
}
|
|
2536
2742
|
if (expansionStatus === "failed" && !capturedItems.has(target.q)) {
|
|
2537
2743
|
abandonedNeverReclick.add(target.q);
|
|
@@ -2547,14 +2753,19 @@ var PAAExtractor = class {
|
|
|
2547
2753
|
if (!seenQs.has(state.q)) {
|
|
2548
2754
|
seenQs.add(state.q);
|
|
2549
2755
|
orderedQs.push(state.q);
|
|
2756
|
+
if (orderedQs.length <= options.maxQuestions) {
|
|
2757
|
+
this.upsertCollectedRow(this.toFlatRow({ question: state.q }, 1, null, options.query));
|
|
2758
|
+
}
|
|
2550
2759
|
}
|
|
2551
2760
|
}
|
|
2761
|
+
await this.emitProgress("discovered");
|
|
2552
2762
|
}
|
|
2553
2763
|
}
|
|
2554
2764
|
const itemMap = options.questionsOnly ? /* @__PURE__ */ new Map() : new Map(capturedItems);
|
|
2555
2765
|
if (!options.questionsOnly) {
|
|
2556
2766
|
await this.fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick);
|
|
2557
2767
|
}
|
|
2768
|
+
const results = [];
|
|
2558
2769
|
for (const q of orderedQs) {
|
|
2559
2770
|
if (results.length >= options.maxQuestions) break;
|
|
2560
2771
|
const key = this.normalizeQuestion(q);
|
|
@@ -2562,14 +2773,22 @@ var PAAExtractor = class {
|
|
|
2562
2773
|
seenKeys.add(key);
|
|
2563
2774
|
const item = itemMap.get(q);
|
|
2564
2775
|
if (item) {
|
|
2565
|
-
|
|
2776
|
+
const row = this.toFlatRow(item, 1, null, options.query);
|
|
2777
|
+
this.upsertCollectedRow(row);
|
|
2778
|
+
results.push(row);
|
|
2566
2779
|
this.reporter.onQuestion({ question: item.question, answer: item.answer ?? null, sourceTitle: item.sourceTitle ?? null, sourceSite: item.sourceSite ?? null, sourceCite: item.sourceCite ?? null, depth: 1, parentQuestion: null, children: [] });
|
|
2567
2780
|
} else {
|
|
2568
|
-
|
|
2781
|
+
const row = this.toFlatRow({ question: q, answer: void 0, sourceTitle: void 0, sourceSite: void 0, sourceCite: void 0 }, 1, null, options.query);
|
|
2782
|
+
this.upsertCollectedRow(row);
|
|
2783
|
+
results.push(row);
|
|
2569
2784
|
}
|
|
2570
2785
|
}
|
|
2571
2786
|
this.completeness.paaWithoutAnswer = options.questionsOnly ? 0 : results.filter((r) => !r.answer).length;
|
|
2572
|
-
this.completeness.paaWithoutSource = options.questionsOnly ? 0 : results.filter((r) => !r.
|
|
2787
|
+
this.completeness.paaWithoutSource = options.questionsOnly ? 0 : results.filter((r) => !r.source_cite).length;
|
|
2788
|
+
this.completeness.paaRequested = options.maxQuestions;
|
|
2789
|
+
this.completeness.paaReturned = results.length;
|
|
2790
|
+
this.completeness.paaUnique = seenKeys.size;
|
|
2791
|
+
await this.emitProgress("fill_incomplete");
|
|
2573
2792
|
return results;
|
|
2574
2793
|
}
|
|
2575
2794
|
async fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick) {
|
|
@@ -2594,6 +2813,10 @@ var PAAExtractor = class {
|
|
|
2594
2813
|
sourceSite: item.sourceSite ?? existing?.sourceSite,
|
|
2595
2814
|
sourceCite: item.sourceCite ?? existing?.sourceCite
|
|
2596
2815
|
});
|
|
2816
|
+
const upgraded = itemMap.get(q);
|
|
2817
|
+
if (upgraded && this.upsertCollectedRow(this.toFlatRow(upgraded, 1, null, options.query))) {
|
|
2818
|
+
await this.emitProgress("fill_incomplete");
|
|
2819
|
+
}
|
|
2597
2820
|
}
|
|
2598
2821
|
this.completeness.paaAnswersRecovered = [...missingAnswersBefore].filter((q) => itemMap.get(q)?.answer).length;
|
|
2599
2822
|
}
|
|
@@ -3058,7 +3281,15 @@ var PAAExtractor = class {
|
|
|
3058
3281
|
this.completeness = { paaWithoutAnswer: 0, paaWithoutSource: 0, paaAnswersRecovered: 0, aioShareCaptured: null };
|
|
3059
3282
|
this.aioShareUrlEarly = null;
|
|
3060
3283
|
this.collectedRows = [];
|
|
3061
|
-
this.
|
|
3284
|
+
this.progressSequence = 0;
|
|
3285
|
+
this.clickedQuestions = /* @__PURE__ */ new Set();
|
|
3286
|
+
this.partialContext = {
|
|
3287
|
+
seed: options.query,
|
|
3288
|
+
location: options.location ?? null,
|
|
3289
|
+
startMs,
|
|
3290
|
+
maxQuestions: options.maxQuestions,
|
|
3291
|
+
questionsOnly: options.questionsOnly
|
|
3292
|
+
};
|
|
3062
3293
|
const isMobile = options.device === "mobile";
|
|
3063
3294
|
const config = {
|
|
3064
3295
|
headless: options.headless,
|
|
@@ -3130,6 +3361,7 @@ var PAAExtractor = class {
|
|
|
3130
3361
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3131
3362
|
diagnostics: {
|
|
3132
3363
|
completionStatus: hasPaa ? "paa_found" : "no_paa",
|
|
3364
|
+
...!hasPaa ? { noPaaObserved: true } : {},
|
|
3133
3365
|
problem: null,
|
|
3134
3366
|
completeness: { ...this.completeness },
|
|
3135
3367
|
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, void 0, locationResolution) } : {}
|
|
@@ -3249,6 +3481,7 @@ var PAAExtractor = class {
|
|
|
3249
3481
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3250
3482
|
diagnostics: {
|
|
3251
3483
|
completionStatus: "no_paa",
|
|
3484
|
+
noPaaObserved: true,
|
|
3252
3485
|
problem: null,
|
|
3253
3486
|
completeness: { ...this.completeness },
|
|
3254
3487
|
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence2, locationResolution) } : {}
|
|
@@ -3340,6 +3573,8 @@ var PAAExtractor = class {
|
|
|
3340
3573
|
};
|
|
3341
3574
|
} catch (err) {
|
|
3342
3575
|
errorCount++;
|
|
3576
|
+
await this.emitProgress("attempt_ending").catch(() => {
|
|
3577
|
+
});
|
|
3343
3578
|
this.reporter.onError(err instanceof Error ? err : new Error(String(err)));
|
|
3344
3579
|
throw err;
|
|
3345
3580
|
}
|
|
@@ -4019,6 +4254,11 @@ function getAttemptLogSink(rawOptions) {
|
|
|
4019
4254
|
const sink = rawOptions.onAttemptEvent;
|
|
4020
4255
|
return typeof sink === "function" ? sink : void 0;
|
|
4021
4256
|
}
|
|
4257
|
+
function getPaaProgressSink(rawOptions) {
|
|
4258
|
+
if (!rawOptions || typeof rawOptions !== "object") return void 0;
|
|
4259
|
+
const sink = rawOptions.onPaaProgress;
|
|
4260
|
+
return typeof sink === "function" ? sink : void 0;
|
|
4261
|
+
}
|
|
4022
4262
|
async function emitAttemptEvent(sink, event) {
|
|
4023
4263
|
if (!sink) return;
|
|
4024
4264
|
try {
|
|
@@ -4051,17 +4291,32 @@ function classifyAttemptResult(result) {
|
|
|
4051
4291
|
function resultHasUsableContent(result) {
|
|
4052
4292
|
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;
|
|
4053
4293
|
}
|
|
4054
|
-
function withResultQuality(result) {
|
|
4294
|
+
function withResultQuality(result, requestedPaaCount, questionsOnly) {
|
|
4055
4295
|
const hasUsableContent = resultHasUsableContent(result);
|
|
4056
4296
|
const hasWarnings = (result.diagnostics.warnings?.length ?? 0) > 0;
|
|
4057
|
-
const
|
|
4297
|
+
const uniquePaaCount = new Set(result.flat.map((row) => row.question.toLowerCase().replace(/[^\w\s]/g, "").replace(/\s+/g, " ").trim())).size;
|
|
4298
|
+
const paaWithoutAnswer = questionsOnly ? 0 : result.flat.filter((row) => !row.answer?.trim()).length;
|
|
4299
|
+
const paaWithoutSource = questionsOnly ? 0 : result.flat.filter((row) => !row.source_cite?.trim()).length;
|
|
4300
|
+
const noPaa = result.diagnostics.completionStatus === "no_paa" && (result.diagnostics.noPaaObserved === true || hasUsableContent);
|
|
4301
|
+
const hasPaa = result.diagnostics.completionStatus === "paa_found";
|
|
4302
|
+
const paaIncomplete = hasPaa && (uniquePaaCount < requestedPaaCount || paaWithoutAnswer > 0 || paaWithoutSource > 0 || Boolean(result.diagnostics.interruption));
|
|
4303
|
+
const resultQuality = noPaa ? "complete" : hasPaa && uniquePaaCount > 0 ? paaIncomplete || hasWarnings ? "partial" : "complete" : hasUsableContent ? hasWarnings ? "partial" : "complete" : "degraded";
|
|
4058
4304
|
const degradedResult = resultQuality === "degraded";
|
|
4059
4305
|
result.diagnostics = {
|
|
4060
4306
|
...result.diagnostics,
|
|
4061
4307
|
resultQuality,
|
|
4062
4308
|
degradedResult,
|
|
4063
4309
|
degradationReasons: degradedResult ? ["empty_primary_serp"] : [],
|
|
4064
|
-
retryRecommended: degradedResult
|
|
4310
|
+
retryRecommended: degradedResult || resultQuality === "partial" && (paaIncomplete || result.diagnostics.warnings?.some((warning) => warning.retryable) === true),
|
|
4311
|
+
completeness: {
|
|
4312
|
+
paaWithoutAnswer,
|
|
4313
|
+
paaWithoutSource,
|
|
4314
|
+
paaAnswersRecovered: result.diagnostics.completeness?.paaAnswersRecovered ?? 0,
|
|
4315
|
+
aioShareCaptured: result.diagnostics.completeness?.aioShareCaptured ?? null,
|
|
4316
|
+
paaRequested: requestedPaaCount,
|
|
4317
|
+
paaReturned: result.flat.length,
|
|
4318
|
+
paaUnique: uniquePaaCount
|
|
4319
|
+
}
|
|
4065
4320
|
};
|
|
4066
4321
|
return result;
|
|
4067
4322
|
}
|
|
@@ -4121,10 +4376,10 @@ async function cleanupDisposableProxy(kernelApiKey, proxyId) {
|
|
|
4121
4376
|
}));
|
|
4122
4377
|
}
|
|
4123
4378
|
}
|
|
4124
|
-
async function extractOnce(options, signal, forceManagedSerp = false) {
|
|
4379
|
+
async function extractOnce(options, signal, forceManagedSerp = false, onPaaProgress) {
|
|
4125
4380
|
const driver = createSerpDriver(options, { forceManaged: forceManagedSerp });
|
|
4126
4381
|
const reporter = new ProgressReporter();
|
|
4127
|
-
const extractor = new PAAExtractor(driver, reporter);
|
|
4382
|
+
const extractor = new PAAExtractor(driver, reporter, onPaaProgress);
|
|
4128
4383
|
if (signal?.aborted) {
|
|
4129
4384
|
return {
|
|
4130
4385
|
result: null,
|
|
@@ -4176,17 +4431,25 @@ async function extractOnce(options, signal, forceManagedSerp = false) {
|
|
|
4176
4431
|
}
|
|
4177
4432
|
if (error) {
|
|
4178
4433
|
const outcome = classifyAttemptError(error);
|
|
4179
|
-
const
|
|
4434
|
+
const interruptionCode = outcome === "timeout" || outcome === "request_aborted" || outcome === "browser_session_interrupted" ? outcome : null;
|
|
4180
4435
|
let salvaged = null;
|
|
4181
|
-
if (
|
|
4436
|
+
if (interruptionCode) {
|
|
4182
4437
|
try {
|
|
4183
4438
|
salvaged = typeof extractor.getPartialResult === "function" ? extractor.getPartialResult() : null;
|
|
4184
4439
|
} catch {
|
|
4185
4440
|
salvaged = null;
|
|
4186
4441
|
}
|
|
4187
4442
|
}
|
|
4188
|
-
if (salvaged) {
|
|
4189
|
-
|
|
4443
|
+
if (salvaged && interruptionCode) {
|
|
4444
|
+
salvaged.diagnostics = {
|
|
4445
|
+
...salvaged.diagnostics,
|
|
4446
|
+
interruption: {
|
|
4447
|
+
code: interruptionCode,
|
|
4448
|
+
reasonSource: "client_runtime",
|
|
4449
|
+
message: errorMessage(error)
|
|
4450
|
+
}
|
|
4451
|
+
};
|
|
4452
|
+
return { result: salvaged, error, cleanup, debug, salvagedFrom: error };
|
|
4190
4453
|
}
|
|
4191
4454
|
return { result: null, error, cleanup, debug };
|
|
4192
4455
|
}
|
|
@@ -4196,6 +4459,7 @@ async function harvest(rawOptions) {
|
|
|
4196
4459
|
const raw = typeof rawOptions === "object" && rawOptions !== null ? rawOptions : {};
|
|
4197
4460
|
const signal = getAbortSignal(rawOptions);
|
|
4198
4461
|
const onAttemptEvent = getAttemptLogSink(rawOptions);
|
|
4462
|
+
const onPaaProgress = getPaaProgressSink(rawOptions);
|
|
4199
4463
|
const forceManagedSerp = raw.forceManagedSerp === true;
|
|
4200
4464
|
const requestedProxyMode = raw.proxyMode;
|
|
4201
4465
|
const proxyMode = requestedProxyMode === "location" || requestedProxyMode === "none" || requestedProxyMode === "configured" ? requestedProxyMode : DEFAULT_PROXY_MODE;
|
|
@@ -4265,7 +4529,7 @@ async function harvest(rawOptions) {
|
|
|
4265
4529
|
...baseCtx,
|
|
4266
4530
|
forceHeadless: allowHeadlessTest,
|
|
4267
4531
|
forceHeadful: !allowHeadlessTest
|
|
4268
|
-
}, () => extractOnce(attemptOptions, signal, forceManagedSerp)) : await extractOnce(attemptOptions, signal, forceManagedSerp);
|
|
4532
|
+
}, () => extractOnce(attemptOptions, signal, forceManagedSerp, onPaaProgress)) : await extractOnce(attemptOptions, signal, forceManagedSerp, onPaaProgress);
|
|
4269
4533
|
if (attempt.error) {
|
|
4270
4534
|
const err = attempt.error;
|
|
4271
4535
|
const outcome2 = classifyAttemptError(err);
|
|
@@ -4298,7 +4562,7 @@ async function harvest(rawOptions) {
|
|
|
4298
4562
|
maxAttempts,
|
|
4299
4563
|
outcome: outcome2,
|
|
4300
4564
|
kernelSessionId: attempt.cleanup.kernelSessionId,
|
|
4301
|
-
questionCount: 0,
|
|
4565
|
+
questionCount: attempt.result?.totalQuestions ?? 0,
|
|
4302
4566
|
durationMs: Date.now() - startedAtMs,
|
|
4303
4567
|
error: errorMessage(err),
|
|
4304
4568
|
willRetry: willRetry2,
|
|
@@ -4309,6 +4573,9 @@ async function harvest(rawOptions) {
|
|
|
4309
4573
|
await cleanupDisposableProxy(kernelApiKey, resolution2.disposableProxyId);
|
|
4310
4574
|
lastError = err;
|
|
4311
4575
|
if (willRetry2) continue;
|
|
4576
|
+
if (attempt.result) {
|
|
4577
|
+
return withResultQuality(stripInternalDebug(attempt.result, requestedDebug), attemptOptions.maxQuestions, attemptOptions.questionsOnly);
|
|
4578
|
+
}
|
|
4312
4579
|
break;
|
|
4313
4580
|
}
|
|
4314
4581
|
const result = attempt.result;
|
|
@@ -4342,7 +4609,7 @@ async function harvest(rawOptions) {
|
|
|
4342
4609
|
if (willRetry2) continue;
|
|
4343
4610
|
break;
|
|
4344
4611
|
}
|
|
4345
|
-
const finalResult = withResultQuality(stripInternalDebug(result, requestedDebug));
|
|
4612
|
+
const finalResult = withResultQuality(stripInternalDebug(result, requestedDebug), attemptOptions.maxQuestions, attemptOptions.questionsOnly);
|
|
4346
4613
|
const outcome = classifyAttemptResult(finalResult);
|
|
4347
4614
|
const willRetry = outcome === "degraded_result" && i < maxAttempts - 1;
|
|
4348
4615
|
const resultError = outcome === "degraded_result" ? degradedResultMessage(finalResult) : null;
|