feat: add cookie support to kijiji scraper
Add optional cookie parameter to bypass bot detection (403 errors). Cookies can be provided via parameter, KIJIJI_COOKIE env var, or cookies/kijiji.json file. Supports both JSON array and string formats. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ export async function kijijiRoute(req: Request): Promise<Response> {
|
|||||||
maxPages,
|
maxPages,
|
||||||
priceMin,
|
priceMin,
|
||||||
priceMax,
|
priceMax,
|
||||||
|
cookies: reqUrl.searchParams.get("cookies") || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import cliProgress from "cli-progress";
|
|||||||
import { parseHTML } from "linkedom";
|
import { parseHTML } from "linkedom";
|
||||||
import unidecode from "unidecode";
|
import unidecode from "unidecode";
|
||||||
import type { HTMLString } from "../types/common";
|
import type { HTMLString } from "../types/common";
|
||||||
|
import {
|
||||||
|
type CookieConfig,
|
||||||
|
formatCookiesForHeader,
|
||||||
|
loadCookiesOptional,
|
||||||
|
} from "../utils/cookies";
|
||||||
import { formatCentsToCurrency } from "../utils/format";
|
import { formatCentsToCurrency } from "../utils/format";
|
||||||
import {
|
import {
|
||||||
fetchHtml,
|
fetchHtml,
|
||||||
@@ -13,6 +18,14 @@ import {
|
|||||||
ValidationError,
|
ValidationError,
|
||||||
} from "../utils/http";
|
} from "../utils/http";
|
||||||
|
|
||||||
|
// Kijiji cookie configuration
|
||||||
|
const KIJIJI_COOKIE_CONFIG: CookieConfig = {
|
||||||
|
name: "Kijiji",
|
||||||
|
domain: ".kijiji.ca",
|
||||||
|
envVar: "KIJIJI_COOKIE",
|
||||||
|
filePath: "./cookies/kijiji.json",
|
||||||
|
};
|
||||||
|
|
||||||
// ----------------------------- Types -----------------------------
|
// ----------------------------- Types -----------------------------
|
||||||
|
|
||||||
type SearchListing = {
|
type SearchListing = {
|
||||||
@@ -110,6 +123,7 @@ export interface SearchOptions {
|
|||||||
maxPages?: number; // Default: 5
|
maxPages?: number; // Default: 5
|
||||||
priceMin?: number;
|
priceMin?: number;
|
||||||
priceMax?: number;
|
priceMax?: number;
|
||||||
|
cookies?: string; // Optional: Cookie string or JSON (helps bypass bot detection)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListingFetchOptions {
|
export interface ListingFetchOptions {
|
||||||
@@ -691,6 +705,16 @@ export default async function fetchKijijiItems(
|
|||||||
) {
|
) {
|
||||||
const DELAY_MS = Math.max(1, Math.floor(1000 / REQUESTS_PER_SECOND));
|
const DELAY_MS = Math.max(1, Math.floor(1000 / REQUESTS_PER_SECOND));
|
||||||
|
|
||||||
|
// Load Kijiji cookies (optional - helps bypass bot detection)
|
||||||
|
const cookies = await loadCookiesOptional(
|
||||||
|
KIJIJI_COOKIE_CONFIG,
|
||||||
|
searchOptions.cookies,
|
||||||
|
);
|
||||||
|
const cookieHeader =
|
||||||
|
cookies.length > 0
|
||||||
|
? formatCookiesForHeader(cookies, "www.kijiji.ca")
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// Set defaults for configuration
|
// Set defaults for configuration
|
||||||
const finalSearchOptions: Required<SearchOptions> = {
|
const finalSearchOptions: Required<SearchOptions> = {
|
||||||
location: searchOptions.location ?? 1700272, // Default to GTA
|
location: searchOptions.location ?? 1700272, // Default to GTA
|
||||||
@@ -701,6 +725,7 @@ export default async function fetchKijijiItems(
|
|||||||
maxPages: searchOptions.maxPages ?? 5, // Default to 5 pages
|
maxPages: searchOptions.maxPages ?? 5, // Default to 5 pages
|
||||||
priceMin: searchOptions.priceMin as number,
|
priceMin: searchOptions.priceMin as number,
|
||||||
priceMax: searchOptions.priceMax as number,
|
priceMax: searchOptions.priceMax as number,
|
||||||
|
cookies: searchOptions.cookies ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const finalListingOptions: Required<ListingFetchOptions> = {
|
const finalListingOptions: Required<ListingFetchOptions> = {
|
||||||
@@ -733,6 +758,7 @@ export default async function fetchKijijiItems(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
headers: cookieHeader ? { cookie: cookieHeader } : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchResults = parseSearch(searchHtml, BASE_URL);
|
const searchResults = parseSearch(searchHtml, BASE_URL);
|
||||||
@@ -782,6 +808,7 @@ export default async function fetchKijijiItems(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
headers: cookieHeader ? { cookie: cookieHeader } : undefined,
|
||||||
});
|
});
|
||||||
const parsed = await parseDetailedListing(
|
const parsed = await parseDetailedListing(
|
||||||
html,
|
html,
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export async function handleMcpRequest(req: Request): Promise<Response> {
|
|||||||
params.append("priceMin", args.priceMin.toString());
|
params.append("priceMin", args.priceMin.toString());
|
||||||
if (args.priceMax)
|
if (args.priceMax)
|
||||||
params.append("priceMax", args.priceMax.toString());
|
params.append("priceMax", args.priceMax.toString());
|
||||||
|
if (args.cookies) params.append("cookies", args.cookies);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[MCP] Calling Kijiji API: ${API_BASE_URL}/kijiji?${params.toString()}`,
|
`[MCP] Calling Kijiji API: ${API_BASE_URL}/kijiji?${params.toString()}`,
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ export const tools = [
|
|||||||
type: "number",
|
type: "number",
|
||||||
description: "Maximum price in cents",
|
description: "Maximum price in cents",
|
||||||
},
|
},
|
||||||
|
cookies: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"Optional: Kijiji session cookies to bypass bot detection (JSON array or 'name1=value1; name2=value2')",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: ["query"],
|
required: ["query"],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user