# Changelog of Zhaopin Scraper (`normdata/zhaopin-scraper`) Actor

- **URL**: https://apify.com/normdata/zhaopin-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/normdata/zhaopin-scraper.md

## Changelog

### 2026-09-19

- Initial build: multi-keyword, multi-city search reading Zhaopin's own real search-results JSON
  (`__INITIAL_STATE__` → `positionList`) directly. No API key, no login, no browser - confirmed live
  with zero anti-bot friction under sustained requests (15 rapid sequential requests, all HTTP 200,
  no slowdown).
- **Real discovery that reshaped the build:** Zhaopin's own search page already embeds a full job
  description, real GPS coordinates, and a recruiter response-rate badge (`staffCard.hrStateInfo`)
  per listing, at zero extra request cost - confirmed live on all 20 rows of a real page. Real-time
  recruiter presence (`staff.hrOnlineState`, e.g. "当前在线" = currently online) only comes back
  populated on the separate job detail page, so that alone stayed behind the opt-in `fetchDetails`
  flag instead of being bundled into a slower "fetch everything" mode.
- **Real bug found and fixed before shipping:** the city code used throughout early testing (`jl=489`)
  turned out not to exist in Zhaopin's own real city directory (`baseData.allCity`) - it silently fell
  back to mixed nationwide results (districts from Beijing, Guangzhou, Shanghai, etc. in one page)
  instead of erroring. The real Shanghai code is `538`, confirmed live by its own city districts
  ("奉贤"/"浦东"/"静安"/etc.) coming back exclusively. Fixed in the demo preset and default city.
- **Real bug found by a combined-filter test and fixed:** Zhaopin's own `cs` (company size) filter
  looked like an honest exact match when tested alone, but a real 100-row run combining it with
  education, company type, and salary filters still returned rows outside the requested size
  ("100-299人"/"300-499人" alongside the requested "1000-9999人"). Re-checked every row against its
  own real `company_size` label client-side, like the salary filter already was; re-verified clean.
- **Real, verified filter codes - one of them contradicts a real competitor's own published README:**
  `companyType=2` is genuinely "wholly foreign-owned" on Zhaopin's own site, not "private" as one
  competitor's README documents; the real private code is `5`. Found by testing every code value
  live rather than trusting either site's own docs or a competitor's claim.
- Company-size filter intentionally ships with only 3 of 6 real codes exposed (`500_999`,
  `1000_9999`, `over_10000`) - the other 3 were tested live and found to return a genuine mix of
  sizes, not an honest exact match, so they're left out rather than shipped broken.
- Fair keyword × city budgeting: each real combination gets its own share of `maxItems`, mirroring
  the same fix already proven necessary in a sibling actor for multi-keyword runs.
- Bottleneck-tested locally at 1,000 items (5 keywords × 4 cities): first pass ran in 62s at
  concurrency 3; raised combo concurrency to 10 (justified by the confirmed zero anti-bot friction)
  and re-measured at 25s, re-verified 0 duplicates / 0 missing core fields at the same 1,000-row scale.
- **Real finding from a 200-row null-rate audit, corrected in the README:** `financing_stage` and
  `welfare_labels` are not just "sometimes" null - confirmed live across several keywords/cities they
  come back empty essentially every time (both the top-level and the nested `jobDetailData` copy of
  welfare tags checked, same result). Softened the README/marketing language that implied these were
  reliably populated, rather than leaving an overselling claim standing.
- 15 unit tests, 157 `expect()` calls, against real saved search-page and detail-page fixtures.
- Full live field audit against `dataset_schema.json`.
