scrapeunblocker-scrapy-middleware package lets you keep your spiders unchanged - every routed Request is rewritten to go through /getPageSource, and your callbacks receive the HTML as if the spider had fetched the URL directly.
The middleware is open source at github.com/ScrapeUnblocker/scrapeunblocker-scrapy-middleware and published on PyPI. The README there has the full reference.
Install
Enable in settings.py
Per-request overrides
Pass options viaRequest.meta["scrapeunblocker"] to override defaults for a single request. A value of None removes a default.
/getPageSource parameters: proxy_country, parsed_data, get_cookies, steps (a list of browser actions) and list_elements. Booleans are sent as true / false and steps as a JSON array. Browser rendering always happens, so there is no option for it. A key the API does not know has no effect, so the middleware logs a warning for it.
To send a request directly, without ScrapeUnblocker, set meta={"scrapeunblocker": False} (or meta={"dont_scrapeunblocker": True}).
What the middleware does
For every routed request, the middleware:- Rewrites the URL to
https://api.scrapeunblocker.com/getPageSource?url=<original>. - Changes the method to
POST. - Adds the
x-scrapeunblocker-keyheader. The target site’s own headers and cookies are not sent to the API. - Merges
SCRAPEUNBLOCKER_DEFAULTSandmeta["scrapeunblocker"]into the query string. - On response, restores the original URL and request on the Scrapy
Response, so selectors,response.follow()and relative links work against the URL you requested, not the API URL.
CONCURRENT_REQUESTS_PER_DOMAIN and DOWNLOAD_DELAY still apply per target site. Requests already addressed to the API host are never wrapped twice, so retries are safe.
These requests are sent directly, because /getPageSource cannot serve them:
- requests that are not
GET(for example aFormRequestpost) - to submit a form through ScrapeUnblocker, use browser steps; - requests Scrapy components make for themselves:
robots.txtandFilesPipeline/ImagesPipelinedownloads; - URLs that are not
http/https.
The API returns the HTML re-indented, so text nodes carry surrounding whitespace and newlines. Strip them with
.get(default="").strip() or normalize-space() in XPath.Handling parsed data
Whenparsed_data is set, the response body is JSON, not HTML, and response.json() works as usual. response.meta["scrapeunblocker_params"] holds the options the response was fetched with, defaults included:
{"data": {"page_type": ..., "data": {...}}} - see parsed data for the fields, and for the 200 with data_extracted: false and the page’s html when nothing could be extracted.
Status codes
When the target site itself answers with an error, the API adds anX-Origin-Status header and the middleware makes it the response status, so Scrapy handles it as it would the site’s own answer:
- a dead URL (
404/410withX-Origin-Status) reaches your spider as404/410; - an origin error page or the target’s own
401/451/500(a200withX-Origin-Status) becomes that status.
dont_retry. Scrapy’s HttpErrorMiddleware drops them unless you allow the status, for example meta={"handle_httpstatus_list": [404, 410]}. See dead URLs.
API errors (401, 402, 403, 429, 5xx, 523, …) keep their status and are logged with the API’s message and a hint. See errors.
Retry behavior
The middleware does not override Scrapy’sRetryMiddleware. Scrapy’s default RETRY_HTTP_CODES already retry 429 and the 5xx failures; add 523 (target unreachable for the moment) and, if you like, 403:
403 retry hits ScrapeUnblocker again, which independently rotates through bypass routes - so a second 403 usually means the target is truly hard-blocked on that day. A request with steps runs its actions again on every retry. See handling failures.
Recommended settings
429 - see concurrency limits.
