feat(amazon-shopping): scrape and filter amazon product results

This commit is contained in:
2026-04-15 18:48:51 -05:00
parent ef326896f4
commit 1e0e265f1e
15 changed files with 844 additions and 6 deletions
+69
View File
@@ -0,0 +1,69 @@
import type { ProductFilters, ProductSearchResult, SearchProductsResponse } from "./types.js";
export interface ResponseInput {
query: string;
filters: ProductFilters;
limit: number;
maxSearchPages: number;
results: ProductSearchResult[];
filteredOutCount: number;
warnings: string[];
now?: () => Date;
}
export function createResponse(input: ResponseInput): SearchProductsResponse {
return {
query: input.query,
filters: input.filters,
limit: input.limit,
maxSearchPages: input.maxSearchPages,
results: input.results,
filteredOutCount: input.filteredOutCount,
warnings: input.warnings,
source: {
site: "amazon.com",
scrapedAt: (input.now ?? (() => new Date()))().toISOString(),
automation: "web-automation/CloakBrowser"
}
};
}
function formatFilters(filters: ProductFilters): string {
const parts = [
filters.minRating !== undefined ? `rating ${filters.ratingComparison ?? "gte"} ${filters.minRating}` : "",
filters.minReviews !== undefined ? `reviews ${filters.reviewCountComparison ?? "gte"} ${filters.minReviews}` : "",
filters.maxPrice !== undefined ? `price <= $${filters.maxPrice}` : "",
filters.maxUnitPrice !== undefined ? `unit price <= $${filters.maxUnitPrice}` : ""
].filter(Boolean);
return parts.length > 0 ? parts.join(", ") : "none";
}
function formatProduct(product: ProductSearchResult, index: number): string {
const specs = product.specs.slice(0, 3).map((spec) => `${spec.name}: ${spec.value}`).join("; ");
const lines = [
`${index}. ${product.title}`,
` Link: ${product.url}`,
` Price: ${product.price?.display ?? "unknown"}${product.unitPrice ? ` (${product.unitPrice.display})` : ""}`,
` Rating: ${product.rating ?? "unknown"} stars; reviews: ${product.reviewCount ?? "unknown"}`,
` Delivery: ${product.delivery?.display ?? "unknown"}`,
specs ? ` Specs: ${specs}` : "",
product.bullets[0] ? ` Notes: ${product.bullets.slice(0, 2).join(" ")}` : "",
product.missingFields.length > 0 ? ` Missing: ${product.missingFields.join(", ")}` : "",
product.isSponsored ? " Sponsored: yes" : ""
].filter(Boolean);
return lines.join("\n");
}
export function createMarkdownReport(response: SearchProductsResponse): string {
const lines = [
`# Amazon Shopping Results`,
"",
`Query: ${response.query}`,
`Filters: ${formatFilters(response.filters)}`,
`Results returned: ${response.results.length} (filtered out: ${response.filteredOutCount})`,
response.warnings.length > 0 ? `Warnings: ${response.warnings.join("; ")}` : "",
"",
...response.results.map((product, index) => formatProduct(product, index + 1))
].filter((line) => line !== "");
return `${lines.join("\n")}\n`;
}