Files
ca-marketplace-scraper/packages/api-server/src/routes/kijiji.ts

100 lines
3.0 KiB
TypeScript

import { fetchKijijiItems } from "@marketplace-scrapers/core";
import { logger } from "../logger";
import {
emptySearchResponse,
getRequiredSearchQuery,
parseDollarPriceParam,
parseNonNegativeIntegerParam,
} from "./helpers";
/**
* GET /api/kijiji?q={query}
* Search Kijiji marketplace for listings
*/
export async function kijijiRoute(req: Request): Promise<Response> {
const reqUrl = new URL(req.url);
const SEARCH_QUERY = getRequiredSearchQuery(req);
if (SEARCH_QUERY instanceof Response) {
return SEARCH_QUERY;
}
const maxPages = parseNonNegativeIntegerParam(
reqUrl.searchParams,
"maxPages",
5,
);
if (maxPages instanceof Response) {
return maxPages;
}
const priceMin = parseDollarPriceParam(reqUrl.searchParams, "priceMin");
if (priceMin instanceof Response) {
return priceMin;
}
const priceMax = parseDollarPriceParam(reqUrl.searchParams, "priceMax");
if (priceMax instanceof Response) {
return priceMax;
}
const hideUnstableResults =
reqUrl.searchParams.get("unstableFilter") === "true";
const searchOptions = {
location: reqUrl.searchParams.get("location") || undefined,
category: reqUrl.searchParams.get("category") || undefined,
keywords: reqUrl.searchParams.get("keywords") || undefined,
sortBy:
(reqUrl.searchParams.get("sortBy") as
| "relevancy"
| "date"
| "price"
| "distance"
| undefined) || undefined,
sortOrder:
(reqUrl.searchParams.get("sortOrder") as "desc" | "asc" | undefined) ||
undefined,
maxPages,
priceMin,
priceMax,
};
try {
if (hideUnstableResults) {
const items = await fetchKijijiItems(
SEARCH_QUERY,
4, // 4 requests per second for faster scraping
"https://www.kijiji.ca",
searchOptions,
{},
{ hideUnstableResults: true },
);
if (items.results.length === 0 && items.unstableResults.length === 0) {
return emptySearchResponse(
`Kijiji matches ALL words in the query against listing titles. ` +
`Try a shorter or more common query (e.g. "macbook air m1" instead of "macbook air m1 apple silicon").`,
);
}
return Response.json(items, { status: 200 });
}
const items = await fetchKijijiItems(
SEARCH_QUERY,
4, // 4 requests per second for faster scraping
"https://www.kijiji.ca",
searchOptions,
{},
);
if (!items || items.length === 0) {
return emptySearchResponse(
`Kijiji matches ALL words in the query against listing titles. ` +
`Try a shorter or more common query (e.g. "macbook air m1" instead of "macbook air m1 apple silicon").`,
);
}
return Response.json(items, { status: 200 });
} catch (error) {
logger.error("Kijiji scraping error:", error);
const errorMessage =
error instanceof Error ? error.message : "Unknown error occurred";
return Response.json({ message: errorMessage }, { status: 400 });
}
}