Releases: 2scraper/dubizzle-scraper
Release list
v0.1.0 — rewritten as a member of the 2scraper family
If you used the previous version, every command changes. The old flags
(--categories,--max-pages,--output,--details) are gone; the new
ones are the 2scraper family's (--url,--pages,--out,--format, …)
and are identical in all three engines. The output has a different schema
and now comes with a<out>.meta.jsonsidecar per run.
The first release of this repository as a member of the
2scraper family. Nothing of the previous
four-file scraper survives.
The one thing to know
The exit address has to be in the UAE. Imperva fronts this site and
refuses everything else — headful and headless alike. And a refusal can be
HTTP 200: the "Pardon Our Interruption" page is served with one, so the
scraper decides "was this served at all" from the page being built out of
dubizzle's own asset hosts (126–2,571 references on every served page, 0 on
both refusals) rather than from the status code.
What it reads
Five verticals through one parser — motors, property for rent and for sale,
classified, jobs and community — in English and Arabic, 25 or 35 ads a page.
The primary source is the site's own SSR payload, not JSON-LD, and that
is measured: a JSON-LD ItemList is published on motors and property and on
none of classified, jobs or community, so a JSON-LD-primary parser would have
worked on cars and flats and silently returned nothing on half the site.
JSON-LD is read as an enrichment — the only source for brand, the
dealership's name and in_stock — joined on the ad's locale-stripped
path. Keyed on the full URL that join matched 25 of 25 ads in English and
0 of 25 in Arabic.
Live verification
Every run through a UAE residential exit:
| Run | Result |
|---|---|
| Playwright, 2 pages used cars | 52 rows, exit 0 |
| Playwright, 2 pages apartments for rent | 70 rows, exit 0 |
| Playwright, 3 pages televisions | 75 rows, exit 0 |
| Playwright, 1 page used cars in Arabic | 26 rows, brand 26/26, exit 0 |
| pyppeteer, 2 pages televisions | 50 rows, exit 0 |
273 rows; every sku unique, every page+position pair unique, every
priced row carrying AED and no unpriced row carrying one.
Selenium's content path was not live-verified and the README says so
rather than implying otherwise; its refusal path was exercised end to end.
The browserless scraper_api_client.py does not get into this site —
four requests, all refused, including two routed through a country-ae
Scraping Browser.
Traps this handles
- A page past the end of a listing answers HTTP 200 with
totalHits: 0and
still renders one ad — the promoted Car of the Week. Parsing it would
write a phantom row per page a run overshot by. priceis legitimately null on two verticals: 0 of 25 jobs ads and 1 of 25
community ads publish one. The coverage floor is per-vertical.- A hub URL has no result grid and honestly reports 0 rows and exit 4.
- A motors listing of 34,619 ads publishes only 400 pages of 25 — two thirds
of the catalogue is unreachable through pagination, and the run says so. dubizzle.com.bh,.omand.egcarry the same brand but run the OLX
platform; they are refused with that reason.
Testing
520 offline checks, passing with no engine library installed at all. CI runs
them on Python 3.9 and 3.13, does a per-engine pip check, and builds and
runs the Docker image. Fixtures are cut from real captures by
make_fixtures.py, which refuses to write one unless the trim parses
identically to the untrimmed original, column for column, and the scrub
removed the agents' names, the per-seller UUIDs, the Algolia key and the
Sentry instrumentation.
Full detail, including the defects found in inherited code, is in
CHANGELOG.md.