Repository navigation
Behaviour changes for scripts.
- A search the site answers with its own "nothing matched"
(search-count:0) now exits 4 (no_results, an empty result) in all
three browser engines. It exited 5 ("never obtained"), which said the
opposite of what happened.- A
--concurrencyrun in which some planned pages were never fetched by
any worker now ends partial (exit 6), listing them inpages_failed
and the sidecar'spages_unattempted. It used to reportcompleteand
exit 0 with those pages missing.- The Selenium and pyppeteer engines now exit 5 when every navigation
failed (a dead network, a timeout), like the Playwright engine. They
reported exit 3 (blocked), sending the reader after a proxy problem.- A paid captcha solve is now bought at most once per page. With the
default--retries 2, one gated page used to buy a token on every
attempt (three solves); the run now stops at exit 3 after one.--retries,--delayand--retry-delaybelow 0, and--pagesor
--concurrencybelow 1, are now usage errors (exit 2) naming the flag,
in all engines andscraper_api_client.py.--retries -1used to run
no navigation at all and report exit 3.--retries 0(try once) is
unchanged.- Output files are written to a temporary file in the same directory and
renamed into place. A new file gets the modeopen()would have given it
under your umask; an existing file keeps its mode.
Fixed
- An interrupted write (a crash, a kill, a full disk) no longer destroys the
previous good output:out.json,out.csvandout.meta.jsonare each
written atomically. Measured before the fix: an 852-byteout.jsoncame
back as 12 bytes of invalid JSON. - In the Selenium and pyppeteer engines, rotating to the next proxy exit now
relaunches the browser.--proxy-serveris fixed at launch, so the old
code advanced the pool and kept using the first exit. Both engines also
rotate on a blocked page 1 when a pool is given, as Playwright does. diff_runs.pynow has thesource_changedbucket its documentation
described: a price difference that comes with aprice_sourcedifference
(the API's exact figure against a card's rounded one) is reported there and
does not trip--fail-on-change.- The credential scan in
.github/ci_checks.pytested every ancestor of the
checkout against its skip list, so a clone under a directory namedvenv
scanned 0 files and reported clean. It now tests paths relative to the
repository, fails when it scanned nothing, and also reads.json,.csv,
.toml,.cfgand.shfiles. - CONTRIBUTING.md, the bug-report and site-change issue templates and the
review workflow's prompt now describe Indiegogo (modessearch,
campaign,rewards, the inlined search payload and the
searchProjectsForCardsPOST) instead of another site's pages, flags and
modes. - The exit-code tables in README.md and TROUBLESHOOTING.md now say what exit
4 and 5 mean in the code: 4 is "the site answered and nothing matched", 5
is "the content was never obtained", including a served page that parsed
to nothing (parser_found_nothing). - The help text and docs no longer say Scraping Browser credentials last
"about a day"; how long one lasts varies, and a 401 is the signal to fetch
a fresh endpoint. --proxy-block-retriesis accepted for compatibility with the other
scrapers in this family; its help now says it has no effect here.- The daily canary was red from 2026-10-07 to 2026-10-12 although the run
itself was healthy (72 rows,complete, 3 of 3 pages). Its sanity check
rejectedpct_funded <= 0, and a real campaign that raised 10,000 against
a goal of 900,000,000 rounds to 0.0. The check now fails on a negative
percentage, or one computed from a goal that is not positive; zero is a
figure. The parser was right and is unchanged, with a check pinning the case. - Scraper API:
waitForis now sent as a JSON object. Measured
2026-09-23 againstscraper.2captcha.com/tasks/sync: the JSON-encoded
string this client sent (the form older docs described) is refused with
HTTP 422 "params.waitFor must be an object" -- and the task is still
billed ($0.0005) -- so every run with--wait-textor its sibling wait flags
failed with exit 5. The object form answers HTTP 200. - Scraper API: the target page's status is read from
http_code. The
response'sstatusfield is the API's own verdict string ("success"),
not the target site's HTTP code, so a target 403/503 was never seen by
this client.http_code(an int) is read now, withstatuskept as a
fallback only when it is an int. - A rendered enterprise widget went undetected. The iframe rung matched
recaptcha/api2/anchor; this site servesrecaptcha/enterprise/anchor,
where the sitekey can be only in the frame'sk=parameter. - A sitekey inside the site's own
<captcha-*>mount went undetected. categoryis localised and differed BETWEEN MODES. The same German
campaign readProduktivitätfrom--mode search(the API's localised
name) andProductivityfrom--mode campaign(a numeric code resolved
through an English table).pyprojectextras had drifted fromrequirements-*.txt
(playwright>=1.44against>=1.40.0,selenium>=4.20against
>=4.15.0). CI installs the requirements files, so the drift was invisible.- The offline suite's credential check scanned gitignored files and
failed on a--dump-htmlcapture containing a site nonce — a file that
cannot be committed..github/ci_checks.pyhad already learned this;
the suite now matches it. .gitignoredid not cover this repo's own default output prefixes.- Leftovers from the repo this one was bootstrapped from.
.gitignore
listed transfermarkt-scraper'stransfermarkt_products.*output prefixes
(this repo's ownindiegogo_projects.*was already listed) and described
sample_output.*as a--mode market-valuesrun; it is--mode search.
tools/waf_probe.shandtools/verify_browser_api.shwere transfermarkt's
AWS WAF probes — transfermarkt URLs,TRANSFERMARKT_*variables that this
repo's loader does not read, and a--mode market-valuesthis repo does
not have. Nothing referenced either, and this site's gate is a Cloudflare
managed challenge rather than AWS WAF, so they are removed rather than
ported.
Added
- CSV output prefixes any text cell that begins with
=,+,-,@, a
tab or a line break with an apostrophe, so a spreadsheet does not run it as
a formula. Numbers are untouched, JSON keeps the site's text, and the
sidecar'scsv_cells_escapedcounts the cells that differ. category_codeonCampaign— the site's numeric, locale-independent
catalogue id. Join on it;categoryis display text.- Checks pinning all of the above, each verified to fail when its fix is
reverted.
Changed
- The daily canary no longer skips green when the runner is blocked (exit
3): the README's claim is that a local browser needs no key and no proxy
here, and bare runners have been served on every scheduled run checked, so
a block is now a failure. Its exit 4 and 5 messages match the codes above. scraper_api_client.pywarns on stderr when the key is passed with--key
(visible tops, kept in shell history).TWOCAPTCHA_KEYin.envis
unchanged and remains the documented way.
Correction to v0.1.0's release notes. They said the 2Captcha balance
was "unchanged to five decimal places". That was measured across the four
bare-browser runs and is true of them, but it is not true of the session as
a whole: the balance moved $0.0024 over the day, which is Scraping
Browser session usage from--cdp-endpointruns. The claim that matters is
narrower and now stated that way — 2Captcha's own statistics endpoint
reports 0 captcha solves and $0.00000 for the day. A run without
--cdp-endpointstill costs nothing at all.
The site has a SECOND captcha, configured but never rendered
Asked plainly which captcha this site uses, and answered by measuring. There
are two, at different layers:
- Cloudflare Turnstile, in a managed challenge at the edge. This is the
one v0.1.0 documents: met, self-cleared, never solved. - reCAPTCHA Enterprise, wired into the application and enabled on every
page the site serves — site key6LeRruUr…, the enterprise loader on
recaptcha.net (not google.com), and an empty<captcha-widgets>mount
point. It is never shown to an anonymous reader.
"No challenge rendered" is not "no captcha configured", so the useful
question was whether this repo would RECOGNISE it. Constructing the shapes
this site's own captcha would take found two the static detector missed.
The proxy path, now exercised live
v0.1.0 shipped with --proxy untested against a real gateway -- there was
no credential to test with. There is now, and the path works, but testing it
found a defect first.
- Fixed: a pasted proxy-LIST line exited 1 (crash) instead of 2 (bad
usage). Proxy lists arescheme://host:port:login:password; this
expectshttp://login:password@host:port, and the extra colons land in
the port.proxy_poolalready validated it and raised with a message
naming the fix -- but no engine caughtProxyErrorat its entry point, so
it reached the interpreter as a traceback.proxy_pool's own comment said
this should be exit 2; now it is, in all three engines, with the password
absent from the message.
Measured 2026-09-17 through a 2Captcha residential gateway:
- The site served content on the FIRST response, with no challenge --
better than the datacentre path, which met one on 2 of 4 runs. -region-XXis honoured:de→ Munich (M-net),us→ Reston (AT&T),
gb→ Glossop (Plusnet),tr→ Istanbul (Türk Telekom), all residential
ISPs. The Scraping Browser'scountry-segment was NOT honoured on the
accounts tested (acountry-uslogin exited from Turkey), which is worth
knowing before trusting either.- One credential mints many exits: 5
-session-logins → 5 distinct
addresses (RU, US, US, IN, LK). --concurrency 3gave each worker its own session and returned 144
rows over 6 pages,(page, position)unique, status complete.- A dead exit rotates rather than retrying: a pool with an unreachable
entry first reportedERR_PROXY_CONNECTION_FAILED, rotated to the next
exit and completed with 24 rows -- the "a proxy failure is not a timeout"
invariant, exercised rather than asserted. - Selenium's documented limitation confirmed: it cannot authenticate a
proxy, says so, and then legitimately returns 39 bytes and exit 3 where
the other two engines returned 24 rows through the same exit. - No credential reached any log across every run above.
Verified, not assumed
--fingerprintapplies a real user agent, locale and timezone
(de-DE/Europe/Berlinfor a German fingerprint — not theen-DEbug
this family shipped four times).--concurrency 3over 6 pages returns 144 rows with unique
(page, position)and no duplicate skus./de/and/zh/each return 48/48 fully populated rows.SECURITY.mdsaid this project has no releases or version tags; it has
both. "Supported versions" now names the latest release andmain.captcha_solver.py's docstring pointed at a "No DataDome solver" section
that does not exist in this repo (it came with the copied core). Removed.