Skip to content

Releases: 2scraper/dubizzle-scraper

v0.1.0 — rewritten as a member of the 2scraper family

Choose a tag to compare

@jehrr jehrr released this 14 Sep 15:31
e01da35

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.json sidecar 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: 0 and
    still renders one ad — the promoted Car of the Week. Parsing it would
    write a phantom row per page a run overshot by.
  • price is 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, .om and .eg carry 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.