# Changelog of MachineryTrader Equipment Scraper (`devilscrapes/machinerytrader-equipment-scraper`) Actor

- **URL**: https://apify.com/devilscrapes/machinerytrader-equipment-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/devilscrapes/machinerytrader-equipment-scraper.md

## MachineryTrader Equipment Scraper — Changelog

### 0.2 — 2026-09-19

- Fix: shelved for reliability on 0.1.1 — 2 SUCCEEDED / 2 FAILED noted in the
  shelf entry, and a fuller count of all 9 runs on that build found 5/9
  SUCCEEDED (56%), all four FAILED runs Cloudflare 403s on `page 1`.
  Root cause: the default `proxyConfiguration` was a bare `{"useApifyProxy":
  true}`, which resolves to Apify's shared ~27-IP datacenter pool, not
  residential — confirmed from billing (`PROXY_RESIDENTIAL_TRANSFER_GBYTES == 0` on all 8 runs on build 0.1.1). Measured a same-build/same-query/
  same-hour matrix: bare `useApifyProxy` (the historical 8 runs) = 4/8
  (50%) Cloudflare clearance; `RESIDENTIAL` + `apifyProxyCountry: "US"` =
  8/8 (100%), every run delivering 5/5 real, US-located rows with billing
  confirming genuine residential transfer on each. The curl-cffi
  impersonation-profile rotation (`chrome131`/`chrome124`/`firefox147`/
  `safari180`, unchanged across both cells) is not implicated — the same
  pool cleared 100% once the proxy tier changed, so this was a tier defect,
  not a fingerprint defect. `_default_proxy_configuration()` in
  `src/models.py` now defaults to `{"useApifyProxy": true,
  "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US"}`; country
  is pinned alongside the tier because a geo-random residential exit would
  return plausible non-US data with a 200 OK on this US-only marketplace,
  not a visible error. `src/client.py` also now logs a bounded prefix of
  any non-200 response body on retry, so the next block investigation reads
  the actual challenge instead of just a status code.

### 0.1 — 2026-09-18

- Fix: `year` filled on 0/150 rows of the newest cloud run
  (`CrpPb89eubCgmoTPF`, build 0.0.x) despite being sold in five places
  (actor.json description/seoDescription, input\_schema.json, README
  prose + field table + example row). Root cause: `card_year()` only
  read a `.spec-label`/`.spec-value` pair whose label text is "Year" —
  a fresh residential-proxy capture of two live search pages (45 cards,
  2026-09-18) shows zero cards ever carry that label; only `Hours` does
  (which is why it fills 92-100%). The model year is instead the
  leading 4-digit token of the title (`"2015 CAT 320EL"`). `card_year()`
  now falls back to a title-token parse when the spec-label is absent,
  bounded to `[1900, this_year+2]` so a stray 4-digit model number can
  never land in `year` — returns `None` rather than guessing. Spec-label
  lookup stays first so a future site change that reintroduces the
  label wins with no code change. Verified against a real, unmodified
  HTML capture (`tests/fixtures/search_page_real_capture.html`), not a
  hand-authored fixture — see `src/parser.py` module docstring.

### 0.1.0 — 2026-09-18

- Initial scaffolded release. `src/main.py` is a placeholder that reads
  input, logs it, and pushes one `{"placeholder": true}` dataset row —
  no real scraping yet. See
  `docs/specs/machinerytrader-equipment-scraper/tasks.md` (T02-T08) for
  the implementation plan.
