Extract stock status from JSON-LD availability field

Now extracts availability/stock status from JSON-LD structured data
(e.g., "https://schema.org/OutOfStock"). This fixes stock detection
for sites like Ubiquiti Store that provide availability in JSON-LD.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
clucraft 2026-01-21 21:28:39 -05:00
parent f188ad4ff1
commit a8a2562cee

View file

@ -729,12 +729,15 @@ export async function scrapeProduct(url: string): Promise<ScrapedProduct> {
} }
// Try JSON-LD structured data // Try JSON-LD structured data
if (!result.price || !result.name) { if (!result.price || !result.name || result.stockStatus === 'unknown') {
const jsonLdData = extractJsonLd($); const jsonLdData = extractJsonLd($);
if (jsonLdData) { if (jsonLdData) {
if (!result.name && jsonLdData.name) result.name = jsonLdData.name; if (!result.name && jsonLdData.name) result.name = jsonLdData.name;
if (!result.price && jsonLdData.price) result.price = jsonLdData.price; if (!result.price && jsonLdData.price) result.price = jsonLdData.price;
if (!result.imageUrl && jsonLdData.image) result.imageUrl = jsonLdData.image; if (!result.imageUrl && jsonLdData.image) result.imageUrl = jsonLdData.image;
if (result.stockStatus === 'unknown' && jsonLdData.stockStatus) {
result.stockStatus = jsonLdData.stockStatus;
}
} }
} }
@ -789,11 +792,12 @@ interface JsonLdOffer {
priceCurrency?: string; priceCurrency?: string;
lowPrice?: string | number; lowPrice?: string | number;
priceSpecification?: JsonLdPriceSpecification; priceSpecification?: JsonLdPriceSpecification;
availability?: string;
} }
function extractJsonLd( function extractJsonLd(
$: CheerioAPI $: CheerioAPI
): { name?: string; price?: ParsedPrice; image?: string } | null { ): { name?: string; price?: ParsedPrice; image?: string; stockStatus?: StockStatus } | null {
try { try {
const scripts = $('script[type="application/ld+json"]'); const scripts = $('script[type="application/ld+json"]');
for (let i = 0; i < scripts.length; i++) { for (let i = 0; i < scripts.length; i++) {
@ -804,7 +808,7 @@ function extractJsonLd(
const product = findProduct(data); const product = findProduct(data);
if (product) { if (product) {
const result: { name?: string; price?: ParsedPrice; image?: string } = {}; const result: { name?: string; price?: ParsedPrice; image?: string; stockStatus?: StockStatus } = {};
if (product.name) { if (product.name) {
result.name = product.name; result.name = product.name;
@ -828,6 +832,17 @@ function extractJsonLd(
currency, currency,
}; };
} }
// Extract stock status from availability
if (offer.availability) {
const avail = offer.availability.toLowerCase();
if (avail.includes('instock') || avail.includes('in_stock')) {
result.stockStatus = 'in_stock';
} else if (avail.includes('outofstock') || avail.includes('out_of_stock') ||
avail.includes('soldout') || avail.includes('sold_out')) {
result.stockStatus = 'out_of_stock';
}
}
} }
if (product.image) { if (product.image) {