# Changelog of 1688 Scraper — Product & Supplier Search, MOQ & Landed Cost (`crawleast/1688-product-scraper`) Actor

- **URL**: https://apify.com/crawleast/1688-product-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/crawleast/1688-product-scraper.md

## Changelog

Full release history lives in git commit messages (`git log --oneline`); this file records release-visible changes from 0.1.17 onward.

### 0.1.20 (2026-09-05)

- Fix (P1-4 fallout, found by the R2M-7b-equivalent run): the dataset schema rejected card-level rows whose detail response carries no weight module — `weightKg: null` failed `"type": "number"` (forensics run `RPahJyL8ctKR7pvul`: 2 of 3 delivered rows 400-rejected, run FAILED with 0 items; keyword card-only never tripped it only because the search-card parser omits the key entirely, while the offerIds card-only path always materialises `weightKg: null`). A full all-null schema audit surfaced three more fields with the same code-contract-vs-schema mismatch and they are fixed together: `price.min` (extractPriceFromContext ships `{min:null,…}` when no price module; `price.max` already allowed null — the original author covered max but missed min), `salesTotal` (parseTransactionCount returns null for listings without a transaction count) and `quantityPrices[].minQuantity` (tiers whose `beginAmount` is null are kept). All four now accept null; wrong types (strings) are still rejected. Pinned by `tests/task273-schema-null-tolerance.test.js` (N1–N8) using the real ajv validator.
- Ops: `data-safety.js` flush failures now log the platform's `invalidItems` payload (bounded JSON dump) so future schema rejections are diagnosable from the run log without a forensics build.

### 0.1.18 (2026-09-05)

- Fix (P1-4, 方案 A): `offerIds` + `includeDetails=false` no longer silently delivers 0 products (run `ui0FeqEeUkejFXArA`). The combination now means CARD-LEVEL delivery — every offerId goes through the mtop-primary transport (`fetchDetailsParallel`) and is delivered at card level only (no detail enrichment, no description images), billed `search-item` × N + run-base-fee exactly like the keyword card-only path (5 IDs ≈ $0.035). Offers the transport cannot resolve are honestly absent and never billed; `SUMMARY.detailStatus` reports `card_only`.
- Fix (P1-1): card-level paths with no search phase (Price Monitoring snapshots and the new offerIds card-only path) now seed the cached minter jar into a live warm page (`Crawler1688.seedWarmCardPage`) BEFORE the batch fetch — previously a settled cache-hit token stayed page-less, so `fetchDetailsParallel` skipped tier-1 (warm-page probe) and tier-1.5 (retained reuse) and fell straight to tier-2 fresh `mtopdet*` sessions (R2M-1: 3/3 RGV587, Cliff breaker TRIPPED, while the SAME token seeded by the keyword search tier delivered 20/20 — R2M-6b). A teardown leak guard (`releaseSearchFallbackBrowser`) closes any unconsumed seeded page on both paths.

### 0.1.17 (2026-09-04)

- Docs (P1-2 semantic clarification): the README input table now documents the `offerIds` direct-fetch path and states explicitly that `includeDetails=true` (details enrichment) requires `maxItems ≥ 20` — smaller batches fall to the `DETAILS_MIN_20` guard (honest refusal, $0 charged); use `includeDetails=false` for small card-only lookups — and that Price Monitoring (`watchlistOfferIds`) is a separate mode not subject to that guard (any watchlist size 1–200). The wall itself is unchanged: it is the 0.1.7 economic red line (detail enrichment breaks even only at batch scale).
