Conversation
added 6 commits
September 14, 2026 15:53
… clears a block here
jehrr
marked this pull request as ready for review
September 14, 2026 15:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the previous four-file scraper with a member of the 2scraper family:
one parser, one page-state policy, one writer, three browser engines plus a
browserless client, and the family's row schema and exit codes.
Nothing of
dubizzle_core.py/dubizzle_playwright.py/dubizzle_selenium.py/dubizzle_pyppeteer.pysurvives. The old flags(
--categories,--max-pages,--output,--details) are gone.What the site turned out to be
Captured 15 pages across six verticals and two languages before writing any
parser, then ran it live. The measurements that changed the design:
__NEXT_DATA__, Redux actionlistings/fetchListingDataForQuery/fulfilled), universal across all sixbrand,seller_name,in_stockpricenull by designTwo of those were nearly shipped wrong and were caught by measuring:
/ar/motors/…in itsJSON-LD while its payload's
absolute_url.aris byte-identical to its.en. Keyed on the full URL the join matched 25 of 25 ads in English and0 of 25 in Arabic, silently emptying three columns while the run
reported success. Keyed on the path it is 26/26 in both, verified by a live
Arabic run.
Car of the Week, with a price node and a JSON-LD item. Parsing an
emptypage would have written one plausible phantom row per page a run overshot
by. It is classified
emptyand not parsed.Live verification
Every run through a UAE residential exit (
country-aeScraping Browser).brand26/26, exit 0273 rows in total; every
skuunique, everypage+positionpair 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: the machine this was built on could not give
a local chromedriver a UAE exit, and Selenium cannot use the authenticated
Scraping Browser endpoint the other two engines use. Its refusal path was
exercised end to end — exit 2 on a credentialled
--cdp-endpointwith thereason, and on a refused address a retry, exit 3, a debug dump and a refusal
to overwrite previous good output.
The browserless
scraper_api_client.pydoes not get into this site, alsomeasured: four requests, all returning the "Pardon Our Interruption" page,
including two routed through a
country-aeScraping Browser with--cdp-url.It reports exit 3 honestly rather than writing rows it did not get.
Defects found in inherited code (only a live run saw these)
Browser refused a connection, where Playwright printed one sentence and
exited 5.
AttributeError: 'NoneType' object has no attribute 'current'on a blocked page with no proxy pool. The branch wasunreachable in the repo they came from (no-pool block budget 0); it is 1
here, so it ran on the first try.
--localedefaulted toid-ID, inherited through two repos from a site inIndonesia.
page_flow.BLOCK_RETRIES_WITH_POOLwas documented policy that nothingread. It is now the default for
--proxy-block-retries.logger.warningwith one placeholder and two arguments printed its rawtemplate on a real run — Python's logging swallows that and carries on.
32-hex string is both an API key's shape and every dubizzle ad's id), so it
failed on its own repository.
Testing
510 offline checks, no engine library required. Two new ones earned their
place during this work and were verified by reverting the fix they catch:
%placeholders counted against its arguments, by AST— needs no branch to execute;
Fixtures are cut from real captures by
make_fixtures.py, which refuses towrite anything unless each 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.
Not done
builds and runs it, and the offline suite checks its
COPYlist againstthe entrypoint's real import graph.