mcp-scraper 0.86.5 → 0.88.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 +22 -1
- package/README.md +17 -4
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.js +3 -3
- package/dist/bin/mcp-scraper-core.js +5 -5
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.js +5 -5
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-QJJQ4OAZ.js → chunk-2JVZQGSN.js} +50 -8
- package/dist/{chunk-NKRO2SUO.js → chunk-4QMUF6XM.js} +2 -2
- package/dist/{chunk-3JRZZVWL.js → chunk-ABF2CGOZ.js} +2 -2
- package/dist/{chunk-OWF2JJKN.js → chunk-GXBZXWXB.js} +8 -8
- package/dist/{chunk-KQWVJOVR.js → chunk-IHXAXYIS.js} +1 -1
- package/dist/{chunk-WFGAR4BZ.js → chunk-MZDNZQWT.js} +1 -1
- package/dist/{chunk-TV32LC76.js → chunk-P5FPC54B.js} +3 -3
- package/dist/{chunk-NH4QAH3X.js → chunk-S24LFPL7.js} +178 -53
- package/dist/{chunk-6IOGTFNS.js → chunk-UZPTGUDV.js} +1 -1
- package/dist/{chunk-BA6BPDSP.js → chunk-XIJI34RM.js} +1 -1
- package/dist/{extract-bundle-FALNLKYY.js → extract-bundle-565SBZCR.js} +2 -2
- package/dist/{gmail-service-BUSU4BVX.js → gmail-service-E6ALS7JG.js} +2 -2
- package/dist/index.cjs +185 -60
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +2 -2
- package/dist/{server-ARMF2IRI.js → server-PD3TDL6Q.js} +526 -428
- package/dist/{site-extract-repository-S5FYNFDY.js → site-extract-repository-VYFZASPU.js} +2 -2
- package/dist/{worker-OQSFSLNG.js → worker-LDCAULWL.js} +3 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -395,6 +395,58 @@ var LocationMismatchError = class extends Error {
|
|
|
395
395
|
}
|
|
396
396
|
};
|
|
397
397
|
|
|
398
|
+
// src/driver/IBrowserDriver.ts
|
|
399
|
+
async function withManagedTemporaryPage(context, setup, operation, signal) {
|
|
400
|
+
let page;
|
|
401
|
+
let ended = false;
|
|
402
|
+
let timer;
|
|
403
|
+
let rejectStop = () => {
|
|
404
|
+
};
|
|
405
|
+
const aborted = () => signal?.reason instanceof DOMException && signal.reason.name === "TimeoutError" ? signal.reason : new RequestAbortedError();
|
|
406
|
+
const onAbort = () => rejectStop(aborted());
|
|
407
|
+
const stop = new Promise((_, reject) => {
|
|
408
|
+
rejectStop = reject;
|
|
409
|
+
});
|
|
410
|
+
const close = async (target) => {
|
|
411
|
+
let closeTimer;
|
|
412
|
+
try {
|
|
413
|
+
await Promise.race([
|
|
414
|
+
target.close().catch(() => {
|
|
415
|
+
}),
|
|
416
|
+
new Promise((resolve) => {
|
|
417
|
+
closeTimer = setTimeout(resolve, 2e3);
|
|
418
|
+
})
|
|
419
|
+
]);
|
|
420
|
+
} finally {
|
|
421
|
+
if (closeTimer) clearTimeout(closeTimer);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
if (signal?.aborted) throw aborted();
|
|
425
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
426
|
+
timer = setTimeout(() => rejectStop(new DOMException("Optional pagination timed out", "TimeoutError")), 3e4);
|
|
427
|
+
try {
|
|
428
|
+
return await Promise.race([
|
|
429
|
+
(async () => {
|
|
430
|
+
const created = await context.newPage();
|
|
431
|
+
if (ended) {
|
|
432
|
+
await close(created);
|
|
433
|
+
throw new RequestAbortedError();
|
|
434
|
+
}
|
|
435
|
+
page = created;
|
|
436
|
+
await setup(created);
|
|
437
|
+
if (ended) throw new RequestAbortedError();
|
|
438
|
+
return operation(created);
|
|
439
|
+
})(),
|
|
440
|
+
stop
|
|
441
|
+
]);
|
|
442
|
+
} finally {
|
|
443
|
+
ended = true;
|
|
444
|
+
if (timer) clearTimeout(timer);
|
|
445
|
+
signal?.removeEventListener("abort", onAbort);
|
|
446
|
+
if (page) await close(page);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
398
450
|
// src/api/cost-telemetry.ts
|
|
399
451
|
var import_node_crypto2 = require("crypto");
|
|
400
452
|
|
|
@@ -1085,6 +1137,11 @@ var BrowserDriver = class {
|
|
|
1085
1137
|
};
|
|
1086
1138
|
});
|
|
1087
1139
|
}
|
|
1140
|
+
async withTemporaryPage(operation, signal) {
|
|
1141
|
+
if (!this.context) throw new Error("Browser context is not available");
|
|
1142
|
+
return withManagedTemporaryPage(this.context, async () => {
|
|
1143
|
+
}, operation, signal);
|
|
1144
|
+
}
|
|
1088
1145
|
async navigateToSERP(query, uule, gl, hl, options) {
|
|
1089
1146
|
const params = new URLSearchParams({ q: query, gl, hl, pws: "0" });
|
|
1090
1147
|
if (options?.tbs) params.set("tbs", options.tbs);
|
|
@@ -1489,7 +1546,10 @@ var BrightDataSerpDriver = class {
|
|
|
1489
1546
|
const customCdp = this.cdpSession;
|
|
1490
1547
|
const sessionResponse = await customCdp.send("Browser.getSessionId");
|
|
1491
1548
|
this.providerSessionId = validateProviderSessionId(sessionResponse?.sessionId);
|
|
1492
|
-
await this.page
|
|
1549
|
+
await this.setupPage(this.page);
|
|
1550
|
+
}
|
|
1551
|
+
async setupPage(page) {
|
|
1552
|
+
await page.route("**/*", async (route) => {
|
|
1493
1553
|
const request = route.request();
|
|
1494
1554
|
const resourceType = request.resourceType();
|
|
1495
1555
|
let requestUrl;
|
|
@@ -1514,6 +1574,10 @@ var BrightDataSerpDriver = class {
|
|
|
1514
1574
|
await route.continue();
|
|
1515
1575
|
});
|
|
1516
1576
|
}
|
|
1577
|
+
async withTemporaryPage(operation, signal) {
|
|
1578
|
+
if (!this.context || this.closing) throw new Error("Browser context is not available");
|
|
1579
|
+
return withManagedTemporaryPage(this.context, (page) => this.setupPage(page), operation, signal);
|
|
1580
|
+
}
|
|
1517
1581
|
async navigateToSERP(query, uule, gl, hl, options) {
|
|
1518
1582
|
if (!this.page) throw new Error("Browser page is not available");
|
|
1519
1583
|
const params = new URLSearchParams({ q: query, gl, hl, pws: "0" });
|
|
@@ -2489,6 +2553,11 @@ function parsedGoogleGoto(value) {
|
|
|
2489
2553
|
if (!url || url.protocol !== "https:" || !isGoogleHost(url.hostname) || url.pathname !== "/goto") return null;
|
|
2490
2554
|
return url.searchParams.get("url") ? url : null;
|
|
2491
2555
|
}
|
|
2556
|
+
function hasResolvedGotoDestination(value) {
|
|
2557
|
+
if (value.linkType !== "google_goto_redirect" || value.resolutionStatus !== "resolved" || !parsedGoogleGoto(value.rawUrl) || !value.resolvedUrl) return false;
|
|
2558
|
+
const destination = safeHttpUrl(value.resolvedUrl);
|
|
2559
|
+
return Boolean(destination && !isGoogleHost(destination.hostname) && destination.href === value.resolvedUrl && value.url === destination.href);
|
|
2560
|
+
}
|
|
2492
2561
|
function questionIdFor(question) {
|
|
2493
2562
|
const normalized = question.normalize("NFKC").toLocaleLowerCase("en-US").replace(/[^\p{L}\p{N}\s]/gu, "").replace(/\s+/g, " ").trim();
|
|
2494
2563
|
return `paa_${(0, import_node_crypto3.createHash)("sha256").update(normalized).digest("hex").slice(0, 16)}`;
|
|
@@ -2705,8 +2774,12 @@ async function resolveHarvestResultGoogleGotoLinks(result, options = {}) {
|
|
|
2705
2774
|
}
|
|
2706
2775
|
async function resolveGoogleOutboundLinks(page, values, options = {}) {
|
|
2707
2776
|
const byRawUrl = /* @__PURE__ */ new Map();
|
|
2708
|
-
for (const value of values)
|
|
2709
|
-
|
|
2777
|
+
for (const value of values) {
|
|
2778
|
+
const existing = byRawUrl.get(value.rawUrl);
|
|
2779
|
+
if (existing && hasResolvedGotoDestination(existing)) continue;
|
|
2780
|
+
byRawUrl.set(value.rawUrl, value.linkType === "google_goto_redirect" && !hasResolvedGotoDestination(value) ? { ...value, url: value.rawUrl, resolvedUrl: null, resolutionStatus: "unresolved" } : value);
|
|
2781
|
+
}
|
|
2782
|
+
const inputs = [...byRawUrl.values()].filter((value) => value.linkType === "google_goto_redirect" && !hasResolvedGotoDestination(value));
|
|
2710
2783
|
if (inputs.length === 0) return byRawUrl;
|
|
2711
2784
|
const deadlineMs = Date.now() + (options.totalBudgetMs ?? DEFAULT_TOTAL_BUDGET_MS);
|
|
2712
2785
|
const direct = await resolveGoogleGotoUrls(inputs.map((value) => value.rawUrl), options);
|
|
@@ -2922,7 +2995,7 @@ var PAAExtractor = class {
|
|
|
2922
2995
|
];
|
|
2923
2996
|
const unique = new Map(candidates.map((link) => [link.rawUrl, link]));
|
|
2924
2997
|
let resolverNonGoogleRequestsObserved = 0;
|
|
2925
|
-
const resolved = await resolveGoogleOutboundLinks(page,
|
|
2998
|
+
const resolved = await resolveGoogleOutboundLinks(page, candidates, {
|
|
2926
2999
|
onNetworkAudit: (audit) => {
|
|
2927
3000
|
resolverNonGoogleRequestsObserved = audit.nonGoogleRequestsObserved;
|
|
2928
3001
|
}
|
|
@@ -3173,6 +3246,7 @@ var PAAExtractor = class {
|
|
|
3173
3246
|
diagnostics: {
|
|
3174
3247
|
completionStatus: "paa_found",
|
|
3175
3248
|
problem: null,
|
|
3249
|
+
...material?.pagination ? { pagination: { ...material.pagination } } : {},
|
|
3176
3250
|
resultQuality: "partial",
|
|
3177
3251
|
degradedResult: false,
|
|
3178
3252
|
retryRecommended: true,
|
|
@@ -4137,6 +4211,55 @@ var PAAExtractor = class {
|
|
|
4137
4211
|
...locationEvidence ? { locationEvidence } : {}
|
|
4138
4212
|
};
|
|
4139
4213
|
}
|
|
4214
|
+
async captureSecondOrganicPage(page, options, signal) {
|
|
4215
|
+
this.throwIfAborted(signal);
|
|
4216
|
+
if (!this.driver.withTemporaryPage) return { organic: [], status: "failed", failureCode: "unsupported_driver" };
|
|
4217
|
+
let guardedFailure;
|
|
4218
|
+
try {
|
|
4219
|
+
const initial = new URL(page.url());
|
|
4220
|
+
const valid = (raw) => {
|
|
4221
|
+
try {
|
|
4222
|
+
const url = new URL(raw, initial);
|
|
4223
|
+
return initial.origin === "https://www.google.com" && url.origin === initial.origin && !url.username && !url.password && url.pathname === "/search" && url.searchParams.getAll("q").length === 1 && url.searchParams.get("q") === options.query && url.searchParams.getAll("start").length === 1 && url.searchParams.get("start") === "10" && (!url.searchParams.has("num") || url.searchParams.get("num") === "10") && ["gl", "hl"].every((key) => !url.searchParams.has(key) || url.searchParams.getAll(key).length === 1 && url.searchParams.get(key) === options[key]) && ["uule", "tbs", "udm", "tbm", "pws"].every((key) => url.searchParams.getAll(key).length <= 1 && url.searchParams.get(key) === initial.searchParams.get(key));
|
|
4224
|
+
} catch {
|
|
4225
|
+
return false;
|
|
4226
|
+
}
|
|
4227
|
+
};
|
|
4228
|
+
const href = await page.evaluate(() => {
|
|
4229
|
+
const next = document.querySelector('a#pnnext, a[rel="next"]');
|
|
4230
|
+
return next?.getAttribute("href") ?? null;
|
|
4231
|
+
});
|
|
4232
|
+
if (!href) return { organic: [], status: "unavailable", failureCode: "missing_next" };
|
|
4233
|
+
if (!valid(href)) return { organic: [], status: "unavailable", failureCode: "invalid_next" };
|
|
4234
|
+
const organic = await this.driver.withTemporaryPage(async (second) => {
|
|
4235
|
+
await second.route("**/*", async (route) => {
|
|
4236
|
+
const request = route.request();
|
|
4237
|
+
if (request.isNavigationRequest() && request.frame() === second.mainFrame() && !valid(request.url())) {
|
|
4238
|
+
guardedFailure = /^https:\/\/www\.google\.com\/sorry(?:\/|\?)/.test(request.url()) ? "captcha" : "invalid_next";
|
|
4239
|
+
await route.abort("blockedbyclient");
|
|
4240
|
+
} else await route.fallback();
|
|
4241
|
+
});
|
|
4242
|
+
await second.goto(new URL(href, initial).href, { waitUntil: "domcontentloaded", timeout: 2e4 });
|
|
4243
|
+
this.throwIfAborted(signal);
|
|
4244
|
+
await this.throwIfCaptcha(second, "Google SERP page 2");
|
|
4245
|
+
if (!valid(second.url())) {
|
|
4246
|
+
guardedFailure = "invalid_next";
|
|
4247
|
+
throw new Error("Invalid pagination landing");
|
|
4248
|
+
}
|
|
4249
|
+
return this.extractOrganicResults(second);
|
|
4250
|
+
}, signal);
|
|
4251
|
+
this.throwIfAborted(signal);
|
|
4252
|
+
return organic.length > 0 ? { organic: organic.map((row) => ({ ...row, position: row.position + 10 })), status: "captured" } : { organic: [], status: "unavailable", failureCode: "empty_page" };
|
|
4253
|
+
} catch (err) {
|
|
4254
|
+
this.throwIfAborted(signal);
|
|
4255
|
+
if (err instanceof RequestAbortedError) throw err;
|
|
4256
|
+
return {
|
|
4257
|
+
organic: [],
|
|
4258
|
+
status: "failed",
|
|
4259
|
+
failureCode: guardedFailure ?? (err instanceof CaptchaError ? "captcha" : err instanceof Error && (err.name === "TimeoutError" || /timed? ?out/i.test(err.message)) ? "timeout" : "navigation_error")
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4140
4263
|
async extract(options, signal) {
|
|
4141
4264
|
const startMs = Date.now();
|
|
4142
4265
|
this.completeness = { paaWithoutAnswer: 0, paaWithoutSource: 0, paaAnswersRecovered: 0, aioShareCaptured: null };
|
|
@@ -4340,23 +4463,49 @@ var PAAExtractor = class {
|
|
|
4340
4463
|
const initialLocationEvidence = options.debug ? inferSerpLocationEvidence(canonicalLocation, organicResults, localPack) : void 0;
|
|
4341
4464
|
this.reporter.onVideos(videos);
|
|
4342
4465
|
this.reporter.onForums(forums);
|
|
4466
|
+
const aiSurfaces = includeAiOverview ? await this.extractAISurfaces(page, options) : emptyAiSurfaces;
|
|
4467
|
+
let pagination = {
|
|
4468
|
+
requestedPages: (options.pages ?? 1) >= 2 ? 2 : 1,
|
|
4469
|
+
capturedPages: 1,
|
|
4470
|
+
page2Status: (options.pages ?? 1) >= 2 ? "not_attempted" : "not_requested",
|
|
4471
|
+
page1OrganicCount: organicResults.length,
|
|
4472
|
+
page2OrganicCount: 0
|
|
4473
|
+
};
|
|
4474
|
+
let allOrganic = organicResults;
|
|
4475
|
+
this.checkpointMaterial = {
|
|
4476
|
+
surface: aiSurfaces.surface,
|
|
4477
|
+
aiOverview: aiSurfaces.aiOverview,
|
|
4478
|
+
aiMode: aiSurfaces.aiMode,
|
|
4479
|
+
whatPeopleSaying,
|
|
4480
|
+
videos,
|
|
4481
|
+
forums,
|
|
4482
|
+
organicResults: allOrganic,
|
|
4483
|
+
localPack,
|
|
4484
|
+
entityIds,
|
|
4485
|
+
pagination
|
|
4486
|
+
};
|
|
4487
|
+
await this.emitProgress("serp_captured");
|
|
4488
|
+
if (pagination.requestedPages === 2) {
|
|
4489
|
+
const second = await this.captureSecondOrganicPage(page, executionOptions, signal);
|
|
4490
|
+
allOrganic = [...organicResults, ...second.organic];
|
|
4491
|
+
pagination = {
|
|
4492
|
+
...pagination,
|
|
4493
|
+
capturedPages: second.status === "captured" ? 2 : 1,
|
|
4494
|
+
page2Status: second.status,
|
|
4495
|
+
page2OrganicCount: second.organic.length,
|
|
4496
|
+
...second.failureCode ? { failureCode: second.failureCode } : {}
|
|
4497
|
+
};
|
|
4498
|
+
this.checkpointMaterial = { ...this.checkpointMaterial, organicResults: allOrganic, pagination };
|
|
4499
|
+
await this.emitProgress("serp_captured");
|
|
4500
|
+
await this.resolveMaterialLinks(page, [], allOrganic, aiSurfaces.aiOverview, aiSurfaces.aiMode);
|
|
4501
|
+
this.checkpointMaterial = { ...this.checkpointMaterial, organicResults: allOrganic };
|
|
4502
|
+
await this.emitProgress("serp_captured");
|
|
4503
|
+
}
|
|
4504
|
+
const locationEvidence = options.debug ? inferSerpLocationEvidence(canonicalLocation, allOrganic, localPack) : initialLocationEvidence;
|
|
4343
4505
|
if (!hasPaa) {
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
4348
|
-
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
4349
|
-
if (uule) p2params.set("uule", uule);
|
|
4350
|
-
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
4351
|
-
await this.throwIfCaptcha(page, "Google SERP page 2");
|
|
4352
|
-
const p2organic = await this.extractOrganicResults(page);
|
|
4353
|
-
noPaaOrganic = [...organicResults, ...p2organic.map((r) => ({ ...r, position: r.position + 10 }))];
|
|
4354
|
-
if (options.debug) {
|
|
4355
|
-
locationEvidence2 = inferSerpLocationEvidence(canonicalLocation, noPaaOrganic, localPack);
|
|
4356
|
-
}
|
|
4357
|
-
}
|
|
4358
|
-
const aiSurfaces2 = includeAiOverview ? await this.extractAISurfaces(page, options) : emptyAiSurfaces;
|
|
4359
|
-
await this.resolveMaterialLinks(page, [], noPaaOrganic, aiSurfaces2.aiOverview, aiSurfaces2.aiMode);
|
|
4506
|
+
await this.resolveMaterialLinks(page, [], allOrganic, aiSurfaces.aiOverview, aiSurfaces.aiMode);
|
|
4507
|
+
this.checkpointMaterial = { ...this.checkpointMaterial, organicResults: allOrganic };
|
|
4508
|
+
await this.emitProgress("serp_captured");
|
|
4360
4509
|
const stats2 = {
|
|
4361
4510
|
seed: executionOptions.query,
|
|
4362
4511
|
totalQuestions: 0,
|
|
@@ -4373,39 +4522,27 @@ var PAAExtractor = class {
|
|
|
4373
4522
|
completionStatus: "no_paa",
|
|
4374
4523
|
noPaaObserved: true,
|
|
4375
4524
|
problem: null,
|
|
4525
|
+
pagination,
|
|
4376
4526
|
paaLifecycle: { ...this.paaLifecycle },
|
|
4377
4527
|
completeness: { ...this.completeness },
|
|
4378
4528
|
links: { ...this.linkDiagnostics },
|
|
4379
|
-
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule,
|
|
4529
|
+
...options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence, locationResolution) } : {}
|
|
4380
4530
|
},
|
|
4381
4531
|
totalQuestions: 0,
|
|
4382
|
-
surface:
|
|
4383
|
-
aiOverview:
|
|
4384
|
-
aiMode:
|
|
4532
|
+
surface: aiSurfaces.surface,
|
|
4533
|
+
aiOverview: aiSurfaces.aiOverview,
|
|
4534
|
+
aiMode: aiSurfaces.aiMode,
|
|
4385
4535
|
whatPeopleSaying,
|
|
4386
4536
|
tree: [],
|
|
4387
4537
|
flat: [],
|
|
4388
4538
|
videos,
|
|
4389
4539
|
forums,
|
|
4390
|
-
organicResults:
|
|
4540
|
+
organicResults: allOrganic,
|
|
4391
4541
|
localPack,
|
|
4392
4542
|
entityIds,
|
|
4393
4543
|
stats: stats2
|
|
4394
4544
|
};
|
|
4395
4545
|
}
|
|
4396
|
-
const aiSurfaces = includeAiOverview ? await this.extractAISurfaces(page, options) : emptyAiSurfaces;
|
|
4397
|
-
this.checkpointMaterial = {
|
|
4398
|
-
surface: aiSurfaces.surface,
|
|
4399
|
-
aiOverview: aiSurfaces.aiOverview,
|
|
4400
|
-
aiMode: aiSurfaces.aiMode,
|
|
4401
|
-
whatPeopleSaying,
|
|
4402
|
-
videos,
|
|
4403
|
-
forums,
|
|
4404
|
-
organicResults,
|
|
4405
|
-
localPack,
|
|
4406
|
-
entityIds
|
|
4407
|
-
};
|
|
4408
|
-
await this.emitProgress("serp_captured");
|
|
4409
4546
|
const flat = await this.runBFS(page, executionOptions, signal);
|
|
4410
4547
|
this.throwIfAborted(signal);
|
|
4411
4548
|
const shortVidsParams = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", udm: ShortVideoSelectors.udm });
|
|
@@ -4426,20 +4563,6 @@ var PAAExtractor = class {
|
|
|
4426
4563
|
}
|
|
4427
4564
|
}
|
|
4428
4565
|
this.reporter.onVideos(shortVideos);
|
|
4429
|
-
let allOrganic = organicResults;
|
|
4430
|
-
let locationEvidence = initialLocationEvidence;
|
|
4431
|
-
if ((options.pages ?? 1) >= 2) {
|
|
4432
|
-
const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: "0", start: "10" });
|
|
4433
|
-
if (recencyToTbs(options.recency)) p2params.set("tbs", recencyToTbs(options.recency));
|
|
4434
|
-
if (uule) p2params.set("uule", uule);
|
|
4435
|
-
await this.driver.navigateTo("https://www.google.com/search?" + p2params.toString());
|
|
4436
|
-
await this.throwIfCaptcha(page, "Google SERP page 2");
|
|
4437
|
-
const p2organic = await this.extractOrganicResults(page);
|
|
4438
|
-
allOrganic = [...organicResults, ...p2organic.map((r) => ({ ...r, position: r.position + 10 }))];
|
|
4439
|
-
if (options.debug) {
|
|
4440
|
-
locationEvidence = inferSerpLocationEvidence(canonicalLocation, allOrganic, localPack);
|
|
4441
|
-
}
|
|
4442
|
-
}
|
|
4443
4566
|
await this.resolveMaterialLinks(page, flat, allOrganic, aiSurfaces.aiOverview, aiSurfaces.aiMode);
|
|
4444
4567
|
this.checkpointMaterial = {
|
|
4445
4568
|
...this.checkpointMaterial,
|
|
@@ -4449,6 +4572,7 @@ var PAAExtractor = class {
|
|
|
4449
4572
|
videos: [...videos, ...shortVideos],
|
|
4450
4573
|
organicResults: allOrganic
|
|
4451
4574
|
};
|
|
4575
|
+
await this.emitProgress("expansion_finished");
|
|
4452
4576
|
const allVideos = [...videos, ...shortVideos];
|
|
4453
4577
|
const tree = this.buildTree(flat, executionOptions.query);
|
|
4454
4578
|
const stats = {
|
|
@@ -4466,6 +4590,7 @@ var PAAExtractor = class {
|
|
|
4466
4590
|
diagnostics: {
|
|
4467
4591
|
completionStatus: "paa_found",
|
|
4468
4592
|
problem: null,
|
|
4593
|
+
pagination,
|
|
4469
4594
|
paaLifecycle: { ...this.paaLifecycle },
|
|
4470
4595
|
completeness: { ...this.completeness },
|
|
4471
4596
|
links: { ...this.linkDiagnostics },
|
|
@@ -4510,18 +4635,18 @@ var import_papaparse = __toESM(require("papaparse"), 1);
|
|
|
4510
4635
|
|
|
4511
4636
|
// src/output/memory-library-sink.ts
|
|
4512
4637
|
async function postToMemoryLibrary(args) {
|
|
4513
|
-
const
|
|
4514
|
-
const
|
|
4515
|
-
if (!
|
|
4638
|
+
const key = process.env.MCP_SCRAPER_API_KEY?.trim();
|
|
4639
|
+
const baseUrl = (process.env.MCP_SCRAPER_BASE_URL ?? "https://mcpscraper.dev").replace(/\/$/, "");
|
|
4640
|
+
if (!key) return;
|
|
4516
4641
|
try {
|
|
4517
|
-
const res = await fetch(
|
|
4642
|
+
const res = await fetch(`${baseUrl}/memory/mcp-call`, {
|
|
4518
4643
|
method: "POST",
|
|
4519
|
-
headers: { "content-type": "application/json" },
|
|
4520
|
-
body: JSON.stringify({
|
|
4644
|
+
headers: { "content-type": "application/json", "x-api-key": key },
|
|
4645
|
+
body: JSON.stringify({ toolName: "libraryIngestTool", args })
|
|
4521
4646
|
});
|
|
4522
4647
|
const body = await res.json().catch(() => null);
|
|
4523
|
-
if (!res.ok || body?.
|
|
4524
|
-
console.warn("[memory-library-sink] ingest not accepted:", res.status, body?.
|
|
4648
|
+
if (!res.ok || body?.ok === false) {
|
|
4649
|
+
console.warn("[memory-library-sink] ingest not accepted:", res.status, body?.error ?? "");
|
|
4525
4650
|
}
|
|
4526
4651
|
} catch (err) {
|
|
4527
4652
|
console.warn("[memory-library-sink] ingest failed:", err?.message);
|
package/dist/index.d.cts
CHANGED
|
@@ -177,9 +177,19 @@ interface PaaInteractionObservation {
|
|
|
177
177
|
errorCode: 'click_failed' | 'click_ack_timeout' | 'control_missing' | 'confirmation_timeout' | null;
|
|
178
178
|
}
|
|
179
179
|
type PaaProgressPhase = 'serp_captured' | 'discovered' | 'click_observed' | 'answer_captured' | 'fill_incomplete' | 'expansion_finished' | 'attempt_ending';
|
|
180
|
+
/** Observed organic pagination outcome; page two never expands a separate PAA graph. */
|
|
181
|
+
interface HarvestPaginationDiagnostics {
|
|
182
|
+
requestedPages: 1 | 2;
|
|
183
|
+
capturedPages: 1 | 2;
|
|
184
|
+
page2Status: 'not_requested' | 'not_attempted' | 'captured' | 'unavailable' | 'failed';
|
|
185
|
+
page1OrganicCount: number;
|
|
186
|
+
page2OrganicCount: number;
|
|
187
|
+
failureCode?: 'missing_next' | 'invalid_next' | 'empty_page' | 'captcha' | 'timeout' | 'navigation_error' | 'unsupported_driver';
|
|
188
|
+
}
|
|
180
189
|
/** Material already observed before PAA expansion completes. It is checkpointed
|
|
181
190
|
* so a worker interruption cannot discard the SERP that led to the PAA graph. */
|
|
182
191
|
interface PaaProgressMaterial {
|
|
192
|
+
pagination?: HarvestPaginationDiagnostics;
|
|
183
193
|
surface: GoogleSurface;
|
|
184
194
|
aiOverview: AIOverviewResult;
|
|
185
195
|
aiMode: AIModeResult;
|
|
@@ -201,6 +211,7 @@ interface PaaProgressSnapshot {
|
|
|
201
211
|
interactions?: PaaInteractionObservation[];
|
|
202
212
|
}
|
|
203
213
|
interface HarvestDiagnostics {
|
|
214
|
+
pagination?: HarvestPaginationDiagnostics;
|
|
204
215
|
completionStatus: HarvestCompletionStatus;
|
|
205
216
|
/** Set only when the extractor directly observed a readable SERP without a PAA block. */
|
|
206
217
|
noPaaObserved?: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -177,9 +177,19 @@ interface PaaInteractionObservation {
|
|
|
177
177
|
errorCode: 'click_failed' | 'click_ack_timeout' | 'control_missing' | 'confirmation_timeout' | null;
|
|
178
178
|
}
|
|
179
179
|
type PaaProgressPhase = 'serp_captured' | 'discovered' | 'click_observed' | 'answer_captured' | 'fill_incomplete' | 'expansion_finished' | 'attempt_ending';
|
|
180
|
+
/** Observed organic pagination outcome; page two never expands a separate PAA graph. */
|
|
181
|
+
interface HarvestPaginationDiagnostics {
|
|
182
|
+
requestedPages: 1 | 2;
|
|
183
|
+
capturedPages: 1 | 2;
|
|
184
|
+
page2Status: 'not_requested' | 'not_attempted' | 'captured' | 'unavailable' | 'failed';
|
|
185
|
+
page1OrganicCount: number;
|
|
186
|
+
page2OrganicCount: number;
|
|
187
|
+
failureCode?: 'missing_next' | 'invalid_next' | 'empty_page' | 'captcha' | 'timeout' | 'navigation_error' | 'unsupported_driver';
|
|
188
|
+
}
|
|
180
189
|
/** Material already observed before PAA expansion completes. It is checkpointed
|
|
181
190
|
* so a worker interruption cannot discard the SERP that led to the PAA graph. */
|
|
182
191
|
interface PaaProgressMaterial {
|
|
192
|
+
pagination?: HarvestPaginationDiagnostics;
|
|
183
193
|
surface: GoogleSurface;
|
|
184
194
|
aiOverview: AIOverviewResult;
|
|
185
195
|
aiMode: AIModeResult;
|
|
@@ -201,6 +211,7 @@ interface PaaProgressSnapshot {
|
|
|
201
211
|
interactions?: PaaInteractionObservation[];
|
|
202
212
|
}
|
|
203
213
|
interface HarvestDiagnostics {
|
|
214
|
+
pagination?: HarvestPaginationDiagnostics;
|
|
204
215
|
completionStatus: HarvestCompletionStatus;
|
|
205
216
|
/** Set only when the extractor directly observed a readable SERP without a PAA block. */
|
|
206
217
|
noPaaObserved?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
harvest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-S24LFPL7.js";
|
|
4
4
|
import "./chunk-OM7HVEJ3.js";
|
|
5
5
|
import {
|
|
6
6
|
recordVendorUsage
|
|
7
7
|
} from "./chunk-OPQIGAFB.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-GXBZXWXB.js";
|
|
9
9
|
import "./chunk-YXNDOQXN.js";
|
|
10
10
|
|
|
11
11
|
// src/video/VideoGenerator.ts
|