Fix: cross-page duplicate billing. Cloud QA of 0.2.1 (run
t3x9JJz5IcHvTbkLC) surfaced a second shape of the same bug: 271 dataset
rows but only 211 unique gigIds. All 40 repeats were the SAME sponsored
gig re-injected by Fiverr on multiple pages of one keyword (e.g. gigId
430037097 on pages 1, 2, and 3 of "logo design") — 0.2.1's dedupe only
looked within a single page's raw response, so it missed this entirely.
parser.dedupe_gigs() now takes an optional seen: set[int] that
scraper._scrape_keyword() threads across that keyword's whole page loop
(reset per keyword — the same gig matching two different keywords is
legitimate distinct information, not a duplicate). 2 more tests
(test_parser.py seen-set cases, test_scraper.py cross-page case);
test_two_data_pages_yield_two_pages_of_rows renamed/split into a
distinct-gigs-across-pages case and a same-gig-across-pages case.
0.2.1 — 2026-09-12
Fix: PPE billing gap. The Actor published 2026-09-12 with only the
flat actor-start ($0.20) event — a customer scraping 10,000 gigs paid
$0.20 total, the same as a customer who got 0 rows. Added a per-row
gig-result event ($0.003/gig, .actor/pay_per_event.json) and charge it
in src/main.py's _finish() for exactly the row count _push_rows()
reports as actually pushed — never for rows merely fetched, and never for
rows a stalled push_data() never delivered.
Fix: duplicate-gig double-count. Live recon (2026-09-12,
query="logo design" page 1) showed Fiverr returning the same gigId
twice on one page — once flagged type: "promoted_gigs", once with no
type at all (48 raw entries, 39 unique). Added parser.dedupe_gigs()
(collapses by gigId, keeps first-seen order, folds the sponsored marker
onto the survivor) and wired it into scraper._append_rows() ahead of
gig_to_row(), so the dataset — and the gig-result charge — reflect
unique gigs delivered, not raw duplicate-inflated search-response entries.
Corrected .actor/actor.jsonversion (was stuck at the scaffold
default "0.0", never bumped past 0.1.0/0.2.0) to "0.2".
README pricing section rewritten to state the live per-1,000 price
instead of pre-launch placeholder copy.
9 new tests (test_parser.py::TestDedupeGigs, test_scraper.py
duplicate-page case, test_main.py_finish/gig-result charge cases).
0.2.0 — 2026-09-10
Real scraping logic (T02-T08, T10): src/parser.py (perseus-initial-props
extraction + gig-to-row mapping, REQ-2/REQ-10), src/classifier.py
(3-way data/empty/blocked response classification, REQ-2/3/4/5),
src/client.py (curl-cffi fetch with exponential backoff on
408/429/503/504 and bounded Apify-Proxy session rotation on 403,
REQ-7/8/9), src/scraper.py (keyword x page pagination loop, wall-clock
deadline governor, per-keyword fault isolation, REQ-1/5/6), src/main.py
(Actor SDK wiring, verified-RESIDENTIAL proxy guard, PPE actor-start
charge, REQ-5/REQ-6 fail-loud-vs-deadline interaction).
77 fixture-backed unit tests across test_models.py, test_parser.py,
test_classifier.py, test_client.py, test_scraper.py, test_main.py —
no live-network calls (network tests would live behind the smoke marker;
none needed for this local-green build).
promo.json (GTM manifest) and finalized README.md (brand-voice,
pre-launch pricing wording).
Local-green only — no apify push, no cloud QA, no PPE result-row
pricing. See docs/specs/fiverr-gig-listings-scraper/spec.md "Out of
Scope" and design.md "Scope note".
0.1.0 — 2026-09-10
Scaffolded (T01/T09): .actor/ config (actor.json, input_schema.json,
output_schema.json, dataset_schema.json, pay_per_event.json,
Dockerfile), pyproject.toml, src/models.py (ActorInput +
ResultRow per spec.md), src/main.py (boot-only stub that pushes
one placeholder record). No scraping logic yet — see
docs/specs/fiverr-gig-listings-scraper/tasks.md T02-T08.