Scrape live Fashionphile.com luxury handbag inventory by brand or keyword search — price, condition, SKU, images, and retail savings per row, sourced directly from Fashionphile's own Algolia search backend. No per-item HTTP calls, no HTML parsing, fully-resolved records in one pass.
Real implementation lands: src/parser.py (hit_to_row, tolerant
.get() chains, REQ-6 required-field guard), src/scraper.py
(rewritten — per-term pagination + fault isolation, REQ-5/7/10),
src/main.py (rewritten — validate-before-charge, BATCH_SIZE=25,
REQ-11 _finish), promo.json. Replaces the T01 scaffold stub that
yielded a single {"placeholder": true, ...} row.
Fixed a real PPE-charging bug in the old stub main.py: the trailing
partial-batch flush charged the undeclared event name "result"
instead of "result-row" — Apify silently ignores a charge for an
event absent from pay_per_event.json, so any run under the batch
threshold earned $0 on its rows. main.py now uses one
EVENT_RESULT_ROW constant for every flush, full or partial, and a
regression test (test_trailing_partial_batch_charges_result_row_not_result)
pins it.
Real fixtures (tests/fixtures/algolia_hits_page0.json,
algolia_hits_page1.json, algolia_zero_hits.json) captured from a
live probe of nsjaz0qg7k-dsn.algolia.net/1/indexes/shopify_products/query
on 2026-08-24 — confirms spec.md's wire format (envelope keys,
market_pricing_usd.price whole-USD, meta.custom.retail_savings,
compare_at_price), including a real discounted hit for the
non-null retail_savings_percent path.
79 tests passing (uv run pytest -v), ruff check and pyright
clean, verify_charge_order.py / verify_input_prefill.py /
verify_schema_refs.py / verify_actor_source_tracked.py all OK.
0.0.1 — 2026-08-24
Scaffolded: .actor/ (actor.json with categories: ["ECOMMERCE"],
version: "0.0"; input_schema.json / dataset_schema.json /
output_schema.json matching design.md field-for-field;
pay_per_event.json with the spec's actor-start ($0.05) and
result-row ($0.003) events; Dockerfile), throwaway placeholder
src/models.py (bare ActorInput/ResultRow generated from
scaffold.json) and src/scraper.py (yields one
{"placeholder": true, ...} row instead of raising, so the bare
skeleton boots and writes a dataset record), placeholder src/main.py
(boots, validates input via ActorInput.model_validate, charges
actor-start, pushes the placeholder batch, charges result-row,
exits cleanly — no real Algolia client yet), test scaffold
(tests/test_input.py, tests/test_main.py), the QA fixture
(tests/fixtures/input.qa.json, spec's own live-verified
searchTerms=["chanel", "hermes birkin"]), README skeleton,
scaffold.json, no promo.json yet (ships at T09).
.actor/icon.svg intentionally NOT created here — deferred to a
separate serial dispatch of actor-icon-designer (Fashionphile
brand mark inside the canonical Devil Scrapes container). House rule:
.actor/icon.svg is authored only by that agent.
Real implementation (src/client.py AlgoliaClient, src/parser.py
hit_to_row, the real src/models.py / src/scraper.py / src/main.py
with per-term fault isolation) is not implemented yet — see
docs/specs/fashionphile-handbag-listings-scraper/tasks.md (T02-T09).