# Changelog of Poshmark Sold Comps & Resale Price Scraper (`kulqiz/poshmark-sold-comps`) Actor

- **URL**: https://apify.com/kulqiz/poshmark-sold-comps/changelog.md
- **Full Actor documentation**: https://apify.com/kulqiz/poshmark-sold-comps.md

## Changelog

All notable changes to this Actor are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/); versions follow semver.

### \[0.5.5] - 2026-09-14

#### Changed

- README "Why this one" section rewritten. It no longer claims most Store Poshmark scrapers
  are free hobby projects (several are paid now); it lists what this Actor does on top of a
  per-listing export, including poshmark.ca support and outlier-trimmed stats.
- Removed the "$0.20 / $0.21 per 1,000 results" figures. Those were platform compute costs
  measured before paid pricing; what you pay is the per-result price on the Pricing tab, with
  platform usage included.
- Proxy descriptions no longer call datacenter "cheapest" or residential "higher cost", for
  the same reason. The no-proxy log warning now matches the README.
- API example uses the real Actor ID (`kulqiz~poshmark-sold-comps`) instead of a placeholder.

### \[0.5.4] - 2026-09-14

#### Changed

- Changelog versions from 0.5 on match the Apify build number that shipped each change
  (Canada support shipped in build 0.5.1, the condition fix in 0.5.2, the description fixes
  in 0.5.3).
- `actor.json` categories set to E-commerce only, matching the Store. Apify now assigns Store
  categories automatically.

### \[0.5.3] - 2026-09-14

#### Changed

- Descriptions now match what the Actor actually does:
  - `includeSellerStats` is marked as not available yet. Since the HTTP-first rewrite in 0.3 it
    makes no closet requests, and seller rating, sales and followers are always `null`. The
    old text said it fetched the closet page.
  - `brand` is described as a filter applied after scraping. It was never sent to Poshmark as a
    search facet, which the old text claimed.
  - `maxConcurrency` means parallel requests, not browser pages (there is no browser since 0.3).
  - README lists the condition labels and notes that some older sales have no condition code.
  - Store description mentions US and Canada.

### \[0.5.2] - 2026-09-14

#### Fixed

- Condition codes `ug` and `uf` now map to "Good" and "Fair". Before, they came back as the
  raw code, which hit about 20-30% of comps in the coach tabby and lululemon samples and split
  `priceByCondition` into unreadable buckets. Both labels, and the existing `uln` = "Like New",
  were read off live listing pages. Applies to poshmark.com and poshmark.ca.

### \[0.5.1] - 2026-09-14

#### Added

- **Poshmark Canada.** New `country` input: `US` (default) searches poshmark.com, `CA` searches
  poshmark.ca. poshmark.ca serves the same search page, `availability=sold_out` facet and
  embedded grid, so the parser is unchanged. Listing URLs use the chosen site, and the
  currency fallback follows it (CAD for Canada). Asked for in a Store issue.
- Aggregate records now include `currency`, so the price stats are labeled once two
  marketplaces exist.

### \[0.4.0] — 2026-06-17

#### Changed

- **Search-only extraction.** Poshmark's search page embeds a grid (`$_search.gridData.data`) with
  full per-listing data (price, `inventory.status` + `status_changed_at`, `first_published_at`,
  condition, size, brand), so the Actor now builds every comp from one search fetch and no longer
  requests per-listing detail pages. Measured on the platform: **$0.25 per 1,000 results** (down
  from $1.00 in v0.3 and $35.72 on the old browser), 48 comps from a single request, 0 blocks,
  \~97% core completeness.
- `parse_detail` is retained and refactored to share one record assembler (`_assemble_record`)
  with the new search path.

#### Added

- **Search pagination.** Follows `more.next_max_id` to page through results, so one query returns
  up to `maxItems` comps (tested to 150 across 4 requests at $0.21/1,000, 0 blocks). Cross-page
  itemId de-dup; page count is bounded by `maxItems`. Bigger samples lift `compConfidence` (150
  comps reaches "high").

### \[0.3.0] — 2026-06-17

#### Changed

- **HTTP-first rewrite.** Replaced the Playwright browser crawler with Crawlee's
  BeautifulSoupCrawler (HTTP plus the impit browser-impersonation client). Every field is in
  Poshmark's server-rendered HTML (`window.__INITIAL_STATE__`), so the browser was pure cost.
  Measured on the platform: cost per 1,000 results fell from **$35.72 to $1.00-$1.85**
  (datacenter / residential), run time for 20 items from ~391s to ~84s, still 0 blocks and
  100% core completeness.
- Default proxy is now Apify datacenter (cheapest, works at modest volume). Residential is the
  fallback for heavy or blocked runs.
- HTTP-mode block detection: HTTP 403/429/503 plus challenge-page markers, keyed off the
  presence of `__INITIAL_STATE__` so the word "captcha" in normal script bundles is not a false
  positive.

#### Note

- `includeSellerStats` is not fetched in the HTTP path (it was experimental and null anyway).

### \[0.2.0] — 2026-06-17

#### Added

- **Per-run `DATA_QUALITY` record + log line** (`src/quality.py`): block rate, items returned vs
  requested, fill rate, and per-field core completeness. Reliability becomes measurable per run.
- **Comps intelligence layer** (`src/aggregate.py`): recency window + `pctSoldWithin30/60/90Days`,
  IQR **outlier trimming** (`outliersRemoved`), a transparent `compConfidence` score, `priceByBrand`,
  `medianDaysToSell`, and a `pricingRecommendation` (`listAt` / `fastSaleListAt` / `topDollarListAt`
  / `expectedDaysToSell` / `confidence`).
- **Reproducible `/benchmarks`**: `fetch_live.py` (re-pull live HTML) + `measure_live.py` (parse and
  score real data). Phase 0 teardown documented in `benchmarks/README.md`.

#### Changed

- **Honest `sellThroughRate`**: now `null` (with a note) in the default sold-only mode instead of a
  misleading `1.0`. Real rate requires `soldOnly: false` (sold + active sample).
- **Hardened block detection**: added PerimeterX / DataDome / Imperva challenge-page markers and
  HTTP `403/429/503` awareness on navigations (`src/config.py`, `src/poshmark.py`, `src/scraper.py`).
- Store listing rewritten with measured, reproducible benchmark numbers and a 403 FAQ; Store name,
  title, description, and categories tuned for search. Version bumped to 0.2.

#### Fixed (surfaced by the first live platform run)

- `ConcurrencySettings`: Crawlee 1.7.2 rejects `desired_concurrency` (default 10) > `max_concurrency`.
  Now clamped to `min(DESIRED_CONCURRENCY, max_concurrency)`.
- Detail/search navigations hung on the `load` event (~57 s timeout) on Poshmark. Now navigate with
  `wait_until="domcontentloaded"` (all data is in the SSR HTML) and `block_requests()` to drop
  images/css/fonts (faster + much less residential proxy bandwidth).

#### Verified

- `pytest` 27 passed; parser scores 100% core completeness on 17 live sold listings across 3
  queries; 0 blocks in a 24-request paced datacenter-IP baseline. See `/benchmarks`.
- **Deployed and ran live on Apify (RESIDENTIAL proxy): run SUCCEEDED, 0 blocks, 100% core
  completeness, `DATA_QUALITY` + `AGGREGATE` records written as designed.**

### \[0.1.0] — 2026-06-17

#### Added

- Initial release: Poshmark **SOLD** listing scraper producing resale price comps.
- Input schema: `searchQueries`, `brand`, `department`, `category`, `sizes`, `conditions`,
  `soldOnly`, `maxItems`, `includeSellerStats`, `computeAggregates`, `maxConcurrency`,
  `proxyConfiguration` (RESIDENTIAL/US default).
- Fixed, validated dataset record (Pydantic) — one object per listing with `soldPrice`,
  `daysToSell`, `originalRetailPrice`, condition/size/color, likes/comments, seller fields,
  and image URLs.
- Per‑query aggregate written to the Key‑Value Store (`AGGREGATE-<slug>`): count, soldCount,
  median/mean/p25/p75 sold price, avg days‑to‑sell, sell‑through rate, price by condition/size.
- Layered extraction: embedded `__INITIAL_STATE__` → JSON‑LD `Product` → `<meta>` → DOM.
- Anti‑bot: Apify residential proxy, Crawlee session pool with block‑status retirement,
  captcha/block detection with jittered exponential backoff and session rotation, randomized
  human‑like delays, low concurrency.
- Thin `Marketplace` interface so future eBay/Mercari/Depop/Grailed/StockX adapters slot in
  without rewriting the crawl loop.
- `--validate` offline smoke test and `pytest` suite against saved HTML fixtures.

#### Known limitations

- `soldPrice` mirrors `listingPrice` for sold items (Poshmark exposes no separate final amount).
- Seller rating / sales / followers are best‑effort and may be `null` (closet page not yet
  fully verified).
- Condition‑code → label map is partially verified; unknown codes degrade to New/Used or raw.
