mcp-scraper 0.2.7 → 0.2.8
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 +6 -4
- package/dist/bin/api-server.cjs +364 -162
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/browser-agent-stdio-server.cjs +1 -1
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +68 -13
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +2 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -1
- package/dist/bin/mcp-scraper-install.js.map +1 -1
- package/dist/bin/mcp-stdio-server.cjs +68 -13
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/bin/paa-harvest.cjs +42 -6
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/{chunk-3TF6UT2P.js → chunk-5HMOPP76.js} +2 -2
- package/dist/{chunk-7SI6XIR3.js → chunk-6NEXSNSA.js} +69 -14
- package/dist/chunk-6NEXSNSA.js.map +1 -0
- package/dist/{chunk-MY3S7EX7.js → chunk-CQTAKXBN.js} +43 -7
- package/dist/chunk-CQTAKXBN.js.map +1 -0
- package/dist/chunk-I26QN7WQ.js +7 -0
- package/dist/chunk-I26QN7WQ.js.map +1 -0
- package/dist/index.cjs +42 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-RIKRBDOI.js → server-BTTDFPSQ.js} +238 -133
- package/dist/server-BTTDFPSQ.js.map +1 -0
- package/dist/{worker-NAKGTIF5.js → worker-5O44YBF4.js} +2 -2
- package/docs/mcp-tool-quality-spec.md +3 -1
- package/package.json +1 -1
- package/dist/chunk-7SI6XIR3.js.map +0 -1
- package/dist/chunk-IZE5UW7Y.js +0 -7
- package/dist/chunk-IZE5UW7Y.js.map +0 -1
- package/dist/chunk-MY3S7EX7.js.map +0 -1
- package/dist/server-RIKRBDOI.js.map +0 -1
- /package/dist/{chunk-3TF6UT2P.js.map → chunk-5HMOPP76.js.map} +0 -0
- /package/dist/{worker-NAKGTIF5.js.map → worker-5O44YBF4.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
deleteKernelProxyId,
|
|
32
32
|
harvest,
|
|
33
33
|
resolveKernelProxyId
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-CQTAKXBN.js";
|
|
35
35
|
import {
|
|
36
36
|
CaptureSerpPageSnapshotsInputSchema,
|
|
37
37
|
CaptureSerpSnapshotInputSchema,
|
|
@@ -41,13 +41,13 @@ import {
|
|
|
41
41
|
harvestTimeoutBudget,
|
|
42
42
|
liveWebToolAnnotations,
|
|
43
43
|
outputBaseDir
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-6NEXSNSA.js";
|
|
45
45
|
import {
|
|
46
46
|
CaptchaError,
|
|
47
47
|
RECAPTCHA_INSTRUCTIONS,
|
|
48
48
|
sanitizeVendorName
|
|
49
49
|
} from "./chunk-M2S27J6Z.js";
|
|
50
|
-
import "./chunk-
|
|
50
|
+
import "./chunk-I26QN7WQ.js";
|
|
51
51
|
import {
|
|
52
52
|
SiteAuditJobRowSchema,
|
|
53
53
|
cancelJob,
|
|
@@ -9893,24 +9893,205 @@ var MapsSearchExtractor = class {
|
|
|
9893
9893
|
}
|
|
9894
9894
|
};
|
|
9895
9895
|
|
|
9896
|
-
// src/
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9896
|
+
// src/maps-search-rotation.ts
|
|
9897
|
+
var MAPS_SEARCH_MAX_ATTEMPTS = 3;
|
|
9898
|
+
var MAPS_SEARCH_LOCATION_PROXY_MAX_ATTEMPTS = 5;
|
|
9899
|
+
var MapsSearchRotationError = class extends Error {
|
|
9900
|
+
constructor(message, attempts, retryable, errorCode = "maps_search_failed") {
|
|
9901
|
+
super(message);
|
|
9902
|
+
this.attempts = attempts;
|
|
9903
|
+
this.retryable = retryable;
|
|
9904
|
+
this.errorCode = errorCode;
|
|
9905
|
+
}
|
|
9906
|
+
attempts;
|
|
9907
|
+
retryable;
|
|
9908
|
+
errorCode;
|
|
9909
|
+
name = "MapsSearchRotationError";
|
|
9910
|
+
};
|
|
9911
|
+
function mapsSearchMaxAttemptsForProxyMode(proxyMode) {
|
|
9912
|
+
return proxyMode === "location" ? MAPS_SEARCH_LOCATION_PROXY_MAX_ATTEMPTS : MAPS_SEARCH_MAX_ATTEMPTS;
|
|
9913
|
+
}
|
|
9914
|
+
function mapsSearchErrorMessage(err) {
|
|
9915
|
+
return sanitizeVendorName(err instanceof Error ? err.message : String(err));
|
|
9916
|
+
}
|
|
9917
|
+
function looksLikeProxyTunnelFailure(message) {
|
|
9918
|
+
return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message);
|
|
9919
|
+
}
|
|
9920
|
+
function looksLikeProxyUnavailable(message) {
|
|
9921
|
+
return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message);
|
|
9922
|
+
}
|
|
9923
|
+
function looksLikeBrowserClosed(message) {
|
|
9924
|
+
return /Target page, context or browser has been closed|browser has been closed|context has been closed|page has been closed|browser not found|CDP session closed|Connection closed|WebSocket.*closed|Protocol error.*closed|Browser closed|disconnected/i.test(message);
|
|
9925
|
+
}
|
|
9926
|
+
function mapsSearchFailureOutcome(err) {
|
|
9927
|
+
if (err instanceof CaptchaError) return "captcha";
|
|
9928
|
+
const message = mapsSearchErrorMessage(err);
|
|
9929
|
+
if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return "timeout";
|
|
9930
|
+
if (looksLikeProxyTunnelFailure(message)) return "proxy_tunnel_failed";
|
|
9931
|
+
if (looksLikeProxyUnavailable(message)) return "proxy_unavailable";
|
|
9932
|
+
if (looksLikeBrowserClosed(message)) return "browser_closed";
|
|
9933
|
+
return "unknown_error";
|
|
9904
9934
|
}
|
|
9905
|
-
|
|
9935
|
+
function retryableMapsSearchError(err, proxyMode) {
|
|
9936
|
+
if (err instanceof CaptchaError) return true;
|
|
9937
|
+
const message = mapsSearchErrorMessage(err);
|
|
9938
|
+
if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return true;
|
|
9939
|
+
if (looksLikeBrowserClosed(message)) return true;
|
|
9940
|
+
return proxyMode === "location" && (looksLikeProxyTunnelFailure(message) || looksLikeProxyUnavailable(message));
|
|
9941
|
+
}
|
|
9942
|
+
function suffix(value) {
|
|
9943
|
+
return value ? value.slice(-8) : null;
|
|
9944
|
+
}
|
|
9945
|
+
function attemptFromDebug(input) {
|
|
9946
|
+
const network = input.debug.networkLocation;
|
|
9947
|
+
const browserSessionId = input.debug.kernel?.sessionId ?? null;
|
|
9948
|
+
const target = input.resolution?.target ?? null;
|
|
9949
|
+
return {
|
|
9950
|
+
attemptNumber: input.attemptIndex + 1,
|
|
9951
|
+
maxAttempts: input.maxAttempts,
|
|
9952
|
+
status: input.status,
|
|
9953
|
+
outcome: input.outcome,
|
|
9954
|
+
willRetry: input.willRetry,
|
|
9955
|
+
durationMs: input.durationMs,
|
|
9956
|
+
resultCount: input.resultCount,
|
|
9957
|
+
error: input.error,
|
|
9958
|
+
proxyMode: input.proxyMode,
|
|
9959
|
+
proxyResolutionSource: input.resolution?.source ?? null,
|
|
9960
|
+
proxyIdSuffix: input.resolution?.proxyIdSuffix ?? null,
|
|
9961
|
+
proxyTargetLevel: target?.level ?? null,
|
|
9962
|
+
proxyTargetLocation: target?.canonicalLocation ?? null,
|
|
9963
|
+
proxyTargetZip: target?.zip ?? null,
|
|
9964
|
+
browserSessionIdSuffix: suffix(browserSessionId),
|
|
9965
|
+
observedIp: network?.ip ?? null,
|
|
9966
|
+
observedCity: network?.city ?? null,
|
|
9967
|
+
observedRegion: network?.region ?? null
|
|
9968
|
+
};
|
|
9969
|
+
}
|
|
9970
|
+
async function cleanupDisposableProxy(kernelApiKey, proxyId, eventName) {
|
|
9906
9971
|
if (!kernelApiKey || !proxyId) return;
|
|
9907
|
-
|
|
9972
|
+
try {
|
|
9973
|
+
await deleteKernelProxyId(kernelApiKey, proxyId);
|
|
9974
|
+
} catch (err) {
|
|
9908
9975
|
console.warn(JSON.stringify({
|
|
9909
|
-
event:
|
|
9910
|
-
proxy_id_suffix: proxyId
|
|
9911
|
-
message:
|
|
9976
|
+
event: eventName,
|
|
9977
|
+
proxy_id_suffix: suffix(proxyId),
|
|
9978
|
+
message: mapsSearchErrorMessage(err)
|
|
9912
9979
|
}));
|
|
9913
|
-
}
|
|
9980
|
+
}
|
|
9981
|
+
}
|
|
9982
|
+
async function runMapsSearchWithRotation(options) {
|
|
9983
|
+
const attempts = [];
|
|
9984
|
+
const maxAttempts = mapsSearchMaxAttemptsForProxyMode(options.proxyMode);
|
|
9985
|
+
const started = Date.now();
|
|
9986
|
+
let lastError = null;
|
|
9987
|
+
for (let attemptIndex = 0; attemptIndex < maxAttempts; attemptIndex += 1) {
|
|
9988
|
+
const attemptStarted = Date.now();
|
|
9989
|
+
const driver = new BrowserDriver();
|
|
9990
|
+
const extractor = new MapsSearchExtractor(driver);
|
|
9991
|
+
const proxyZip = options.proxyZipForAttempt?.(attemptIndex) ?? options.proxyZip;
|
|
9992
|
+
let resolution = null;
|
|
9993
|
+
let disposableProxyId;
|
|
9994
|
+
try {
|
|
9995
|
+
resolution = await resolveKernelProxyId({
|
|
9996
|
+
kernelApiKey: options.kernelApiKey,
|
|
9997
|
+
proxyMode: options.proxyMode,
|
|
9998
|
+
configuredKernelProxyId: options.kernelProxyId ?? options.configuredKernelProxyId,
|
|
9999
|
+
location: options.location,
|
|
10000
|
+
proxyZip,
|
|
10001
|
+
gl: options.gl,
|
|
10002
|
+
attemptIndex,
|
|
10003
|
+
fresh: options.proxyMode === "location"
|
|
10004
|
+
});
|
|
10005
|
+
disposableProxyId = resolution.disposableProxyId;
|
|
10006
|
+
const result = await extractor.extract({
|
|
10007
|
+
...options,
|
|
10008
|
+
proxyZip,
|
|
10009
|
+
kernelProxyId: options.proxyMode === "none" ? void 0 : resolution.kernelProxyId,
|
|
10010
|
+
kernelProxyResolution: resolution.resolution
|
|
10011
|
+
});
|
|
10012
|
+
const attempt = attemptFromDebug({
|
|
10013
|
+
attemptIndex,
|
|
10014
|
+
maxAttempts,
|
|
10015
|
+
status: "ok",
|
|
10016
|
+
outcome: "success",
|
|
10017
|
+
willRetry: false,
|
|
10018
|
+
durationMs: Date.now() - attemptStarted,
|
|
10019
|
+
resultCount: result.resultCount,
|
|
10020
|
+
error: null,
|
|
10021
|
+
proxyMode: options.proxyMode,
|
|
10022
|
+
resolution: resolution.resolution,
|
|
10023
|
+
debug: driver.getDebugSnapshot()
|
|
10024
|
+
});
|
|
10025
|
+
attempts.push(attempt);
|
|
10026
|
+
return {
|
|
10027
|
+
...result,
|
|
10028
|
+
attempts: attempts.slice(),
|
|
10029
|
+
durationMs: Date.now() - started
|
|
10030
|
+
};
|
|
10031
|
+
} catch (err) {
|
|
10032
|
+
lastError = err;
|
|
10033
|
+
const retryable = retryableMapsSearchError(err, options.proxyMode);
|
|
10034
|
+
const willRetry = attemptIndex < maxAttempts - 1 && retryable;
|
|
10035
|
+
const attempt = attemptFromDebug({
|
|
10036
|
+
attemptIndex,
|
|
10037
|
+
maxAttempts,
|
|
10038
|
+
status: "failed",
|
|
10039
|
+
outcome: mapsSearchFailureOutcome(err),
|
|
10040
|
+
willRetry,
|
|
10041
|
+
durationMs: Date.now() - attemptStarted,
|
|
10042
|
+
resultCount: 0,
|
|
10043
|
+
error: mapsSearchErrorMessage(err),
|
|
10044
|
+
proxyMode: options.proxyMode,
|
|
10045
|
+
resolution: resolution?.resolution ?? null,
|
|
10046
|
+
debug: driver.getDebugSnapshot()
|
|
10047
|
+
});
|
|
10048
|
+
attempts.push(attempt);
|
|
10049
|
+
console.warn(JSON.stringify({
|
|
10050
|
+
event: options.failureEventName ?? "maps_search_attempt_failed",
|
|
10051
|
+
...options.logContext,
|
|
10052
|
+
attempt_number: attempt.attemptNumber,
|
|
10053
|
+
max_attempts: attempt.maxAttempts,
|
|
10054
|
+
outcome: attempt.outcome,
|
|
10055
|
+
will_retry: attempt.willRetry,
|
|
10056
|
+
proxy_resolution_source: attempt.proxyResolutionSource,
|
|
10057
|
+
proxy_id_suffix: attempt.proxyIdSuffix,
|
|
10058
|
+
browser_session_id_suffix: attempt.browserSessionIdSuffix,
|
|
10059
|
+
observed_city: attempt.observedCity,
|
|
10060
|
+
observed_region: attempt.observedRegion,
|
|
10061
|
+
message: attempt.error
|
|
10062
|
+
}));
|
|
10063
|
+
if (!willRetry) {
|
|
10064
|
+
throw new MapsSearchRotationError(
|
|
10065
|
+
mapsSearchErrorMessage(err),
|
|
10066
|
+
attempts,
|
|
10067
|
+
retryable,
|
|
10068
|
+
err instanceof CaptchaError ? "captcha_or_blocked" : "maps_search_failed"
|
|
10069
|
+
);
|
|
10070
|
+
}
|
|
10071
|
+
} finally {
|
|
10072
|
+
await cleanupDisposableProxy(options.kernelApiKey, disposableProxyId, "maps_search_proxy_delete_failed");
|
|
10073
|
+
await driver.close();
|
|
10074
|
+
}
|
|
10075
|
+
}
|
|
10076
|
+
throw new MapsSearchRotationError(
|
|
10077
|
+
lastError ? mapsSearchErrorMessage(lastError) : "Maps search failed",
|
|
10078
|
+
attempts,
|
|
10079
|
+
true
|
|
10080
|
+
);
|
|
10081
|
+
}
|
|
10082
|
+
|
|
10083
|
+
// src/api/maps-routes.ts
|
|
10084
|
+
function mapsErrorResponse(c, err, errorCode) {
|
|
10085
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10086
|
+
const rotationError = err instanceof MapsSearchRotationError ? err : null;
|
|
10087
|
+
const blocked = msg.includes("CAPTCHA") || msg.includes("blocked");
|
|
10088
|
+
const retryable = rotationError?.retryable ?? blocked;
|
|
10089
|
+
return c.json({
|
|
10090
|
+
error: sanitizeVendorName(msg),
|
|
10091
|
+
error_code: rotationError?.errorCode ?? (blocked ? "captcha_or_blocked" : errorCode),
|
|
10092
|
+
retryable,
|
|
10093
|
+
attempts: rotationError?.attempts ?? void 0
|
|
10094
|
+
}, retryable ? 503 : 500);
|
|
9914
10095
|
}
|
|
9915
10096
|
var mapsApp = new Hono5();
|
|
9916
10097
|
mapsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
@@ -9930,24 +10111,10 @@ mapsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
|
9930
10111
|
[parsed.data.query, parsed.data.location].filter(Boolean).join(" ")
|
|
9931
10112
|
);
|
|
9932
10113
|
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.maps_search), 402);
|
|
9933
|
-
const driver = new BrowserDriver();
|
|
9934
|
-
const extractor = new MapsSearchExtractor(driver);
|
|
9935
|
-
let disposableProxyId;
|
|
9936
10114
|
try {
|
|
9937
|
-
const
|
|
9938
|
-
kernelApiKey: parsed.data.kernelApiKey,
|
|
9939
|
-
proxyMode: parsed.data.proxyMode,
|
|
9940
|
-
configuredKernelProxyId: browserServiceProxyId(),
|
|
9941
|
-
location: parsed.data.location,
|
|
9942
|
-
proxyZip: parsed.data.proxyZip,
|
|
9943
|
-
gl: parsed.data.gl,
|
|
9944
|
-
fresh: parsed.data.proxyMode === "location"
|
|
9945
|
-
});
|
|
9946
|
-
disposableProxyId = resolution.disposableProxyId;
|
|
9947
|
-
const result = await extractor.extract({
|
|
10115
|
+
const result = await runMapsSearchWithRotation({
|
|
9948
10116
|
...parsed.data,
|
|
9949
|
-
|
|
9950
|
-
kernelProxyResolution: resolution.resolution
|
|
10117
|
+
configuredKernelProxyId: browserServiceProxyId()
|
|
9951
10118
|
});
|
|
9952
10119
|
await logRequestEvent({
|
|
9953
10120
|
userId: user.id,
|
|
@@ -9968,12 +10135,10 @@ mapsApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
|
9968
10135
|
status: "failed",
|
|
9969
10136
|
query: [parsed.data.query, parsed.data.location].filter(Boolean).join(" "),
|
|
9970
10137
|
location: parsed.data.location,
|
|
9971
|
-
error: msg
|
|
10138
|
+
error: msg,
|
|
10139
|
+
result: err instanceof MapsSearchRotationError ? { attempts: err.attempts } : void 0
|
|
9972
10140
|
});
|
|
9973
|
-
return mapsErrorResponse(c,
|
|
9974
|
-
} finally {
|
|
9975
|
-
await cleanupDisposableProxy(parsed.data.kernelApiKey, disposableProxyId);
|
|
9976
|
-
await driver.close();
|
|
10141
|
+
return mapsErrorResponse(c, err, "maps_search_failed");
|
|
9977
10142
|
}
|
|
9978
10143
|
});
|
|
9979
10144
|
mapsApp.post("/place", createApiKeyAuth(), async (c) => {
|
|
@@ -10278,8 +10443,6 @@ async function resolveDirectoryMarkets(options) {
|
|
|
10278
10443
|
}
|
|
10279
10444
|
|
|
10280
10445
|
// src/directory/directory-workflow.ts
|
|
10281
|
-
var DIRECTORY_MAX_ATTEMPTS = 3;
|
|
10282
|
-
var DIRECTORY_LOCATION_PROXY_MAX_ATTEMPTS = 5;
|
|
10283
10446
|
var DirectoryWorkflowOptionsSchema = z15.object({
|
|
10284
10447
|
query: z15.string().min(1),
|
|
10285
10448
|
state: z15.string().min(2).default("TN"),
|
|
@@ -10299,36 +10462,9 @@ var DirectoryWorkflowOptionsSchema = z15.object({
|
|
|
10299
10462
|
headless: z15.boolean().default(true),
|
|
10300
10463
|
kernelApiKey: z15.string().optional()
|
|
10301
10464
|
});
|
|
10302
|
-
async function cleanupDisposableProxy2(kernelApiKey, proxyId) {
|
|
10303
|
-
if (!kernelApiKey || !proxyId) return;
|
|
10304
|
-
try {
|
|
10305
|
-
await deleteKernelProxyId(kernelApiKey, proxyId);
|
|
10306
|
-
} catch (err) {
|
|
10307
|
-
console.warn(JSON.stringify({
|
|
10308
|
-
event: "directory_workflow_proxy_delete_failed",
|
|
10309
|
-
proxy_id_suffix: proxyId.slice(-6),
|
|
10310
|
-
message: err instanceof Error ? err.message : String(err)
|
|
10311
|
-
}));
|
|
10312
|
-
}
|
|
10313
|
-
}
|
|
10314
|
-
function maxAttemptsForProxyMode(proxyMode) {
|
|
10315
|
-
return proxyMode === "location" ? DIRECTORY_LOCATION_PROXY_MAX_ATTEMPTS : DIRECTORY_MAX_ATTEMPTS;
|
|
10316
|
-
}
|
|
10317
10465
|
function errorMessage(err) {
|
|
10318
10466
|
return err instanceof Error ? err.message : String(err);
|
|
10319
10467
|
}
|
|
10320
|
-
function looksLikeProxyTunnelFailure(message) {
|
|
10321
|
-
return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message);
|
|
10322
|
-
}
|
|
10323
|
-
function looksLikeProxyUnavailable(message) {
|
|
10324
|
-
return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message);
|
|
10325
|
-
}
|
|
10326
|
-
function retryableCitySearchError(err, proxyMode) {
|
|
10327
|
-
if (err instanceof CaptchaError) return true;
|
|
10328
|
-
const message = errorMessage(err);
|
|
10329
|
-
if (/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message)) return true;
|
|
10330
|
-
return proxyMode === "location" && (looksLikeProxyTunnelFailure(message) || looksLikeProxyUnavailable(message));
|
|
10331
|
-
}
|
|
10332
10468
|
function proxyZipForAttempt(options, market, attemptIndex) {
|
|
10333
10469
|
if (options.proxyZip) return options.proxyZip;
|
|
10334
10470
|
if (!market.zips.length) return void 0;
|
|
@@ -10347,25 +10483,10 @@ async function mapLimit(items, limit, fn) {
|
|
|
10347
10483
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
10348
10484
|
return out;
|
|
10349
10485
|
}
|
|
10350
|
-
async function
|
|
10351
|
-
const
|
|
10352
|
-
const extractor = new MapsSearchExtractor(driver);
|
|
10353
|
-
const start = Date.now();
|
|
10354
|
-
let disposableProxyId;
|
|
10486
|
+
async function searchCity(options, market) {
|
|
10487
|
+
const started = Date.now();
|
|
10355
10488
|
try {
|
|
10356
|
-
const
|
|
10357
|
-
const resolution = await resolveKernelProxyId({
|
|
10358
|
-
kernelApiKey: options.kernelApiKey,
|
|
10359
|
-
proxyMode: options.proxyMode,
|
|
10360
|
-
configuredKernelProxyId: browserServiceProxyId(),
|
|
10361
|
-
location: market.location,
|
|
10362
|
-
proxyZip,
|
|
10363
|
-
gl: options.gl,
|
|
10364
|
-
attemptIndex,
|
|
10365
|
-
fresh: options.proxyMode === "location"
|
|
10366
|
-
});
|
|
10367
|
-
disposableProxyId = resolution.disposableProxyId;
|
|
10368
|
-
const result = await extractor.extract({
|
|
10489
|
+
const result = await runMapsSearchWithRotation({
|
|
10369
10490
|
query: options.query,
|
|
10370
10491
|
location: market.location,
|
|
10371
10492
|
gl: options.gl,
|
|
@@ -10373,11 +10494,16 @@ async function searchCityAttempt(options, market, attemptIndex) {
|
|
|
10373
10494
|
maxResults: options.maxResultsPerCity,
|
|
10374
10495
|
headless: options.headless,
|
|
10375
10496
|
kernelApiKey: options.kernelApiKey,
|
|
10376
|
-
kernelProxyId: options.proxyMode === "none" ? void 0 : resolution.kernelProxyId,
|
|
10377
|
-
kernelProxyResolution: resolution.resolution,
|
|
10378
10497
|
proxyMode: options.proxyMode,
|
|
10379
|
-
proxyZip,
|
|
10380
|
-
debug: options.debug
|
|
10498
|
+
proxyZip: options.proxyZip,
|
|
10499
|
+
debug: options.debug,
|
|
10500
|
+
configuredKernelProxyId: browserServiceProxyId(),
|
|
10501
|
+
proxyZipForAttempt: (attemptIndex) => proxyZipForAttempt(options, market, attemptIndex),
|
|
10502
|
+
failureEventName: "directory_workflow_city_attempt_failed",
|
|
10503
|
+
logContext: {
|
|
10504
|
+
city: market.city,
|
|
10505
|
+
state: market.state
|
|
10506
|
+
}
|
|
10381
10507
|
});
|
|
10382
10508
|
return {
|
|
10383
10509
|
city: market.city,
|
|
@@ -10393,50 +10519,29 @@ async function searchCityAttempt(options, market, attemptIndex) {
|
|
|
10393
10519
|
error: null,
|
|
10394
10520
|
resultCount: result.resultCount,
|
|
10395
10521
|
durationMs: result.durationMs,
|
|
10522
|
+
attempts: result.attempts ?? [],
|
|
10396
10523
|
results: result.results
|
|
10397
10524
|
};
|
|
10398
|
-
}
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
max_attempts: maxAttempts,
|
|
10418
|
-
will_retry: willRetry,
|
|
10419
|
-
message: errorMessage(err)
|
|
10420
|
-
}));
|
|
10421
|
-
if (!willRetry) break;
|
|
10422
|
-
}
|
|
10525
|
+
} catch (err) {
|
|
10526
|
+
const attempts = err instanceof MapsSearchRotationError ? err.attempts : [];
|
|
10527
|
+
return {
|
|
10528
|
+
city: market.city,
|
|
10529
|
+
state: market.state,
|
|
10530
|
+
location: market.location,
|
|
10531
|
+
cityKey: market.cityKey,
|
|
10532
|
+
censusName: market.censusName,
|
|
10533
|
+
population: market.population,
|
|
10534
|
+
populationYear: market.populationYear,
|
|
10535
|
+
zips: market.zips,
|
|
10536
|
+
counties: market.counties,
|
|
10537
|
+
status: "failed",
|
|
10538
|
+
error: errorMessage(err),
|
|
10539
|
+
resultCount: 0,
|
|
10540
|
+
durationMs: Date.now() - started,
|
|
10541
|
+
attempts,
|
|
10542
|
+
results: []
|
|
10543
|
+
};
|
|
10423
10544
|
}
|
|
10424
|
-
return {
|
|
10425
|
-
city: market.city,
|
|
10426
|
-
state: market.state,
|
|
10427
|
-
location: market.location,
|
|
10428
|
-
cityKey: market.cityKey,
|
|
10429
|
-
censusName: market.censusName,
|
|
10430
|
-
population: market.population,
|
|
10431
|
-
populationYear: market.populationYear,
|
|
10432
|
-
zips: market.zips,
|
|
10433
|
-
counties: market.counties,
|
|
10434
|
-
status: "failed",
|
|
10435
|
-
error: lastError ? errorMessage(lastError) : "City Maps search failed",
|
|
10436
|
-
resultCount: 0,
|
|
10437
|
-
durationMs: Date.now() - started,
|
|
10438
|
-
results: []
|
|
10439
|
-
};
|
|
10440
10545
|
}
|
|
10441
10546
|
function csvRowsFor(result) {
|
|
10442
10547
|
const rows = [];
|
|
@@ -11552,7 +11657,7 @@ var mcpApp = new Hono8();
|
|
|
11552
11657
|
function registerSerpIntelligenceCaptureTools(server, executor) {
|
|
11553
11658
|
server.registerTool("capture_serp_snapshot", {
|
|
11554
11659
|
title: "SERP Intelligence Snapshot",
|
|
11555
|
-
description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location, infer gl/hl, use proxyMode location for localized US residential evidence; location mode creates fresh proxy IDs across retries and
|
|
11660
|
+
description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location, infer gl/hl, use proxyMode location for localized US residential evidence; location mode creates fresh proxy IDs across retries, briefly waits for browser-service automatic CAPTCHA/challenge solving when Google blocks a page, and rotates to a new proxy/session if the challenge does not clear or location evidence is wrong. Use configured only for the static residential proxy, and none only for direct-network debugging. Set debug true when investigating location evidence, proxy behavior, CAPTCHA, or capture reliability.",
|
|
11556
11661
|
inputSchema: CaptureSerpSnapshotInputSchema,
|
|
11557
11662
|
annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
|
|
11558
11663
|
}, async (input) => executor.captureSerpSnapshot(input));
|
|
@@ -13594,7 +13699,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
13594
13699
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
13595
13700
|
return c.json({ error: "Unauthorized" }, 401);
|
|
13596
13701
|
}
|
|
13597
|
-
const { drainQueue } = await import("./worker-
|
|
13702
|
+
const { drainQueue } = await import("./worker-5O44YBF4.js");
|
|
13598
13703
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
13599
13704
|
const [results, sweepResult] = await Promise.all([
|
|
13600
13705
|
drainQueue(budget),
|
|
@@ -13720,4 +13825,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
13720
13825
|
export {
|
|
13721
13826
|
app
|
|
13722
13827
|
};
|
|
13723
|
-
//# sourceMappingURL=server-
|
|
13828
|
+
//# sourceMappingURL=server-BTTDFPSQ.js.map
|