scrape-plan
Before you write a scraper, find out which of three approaches this page actually needs — and whether you're allowed to.
$ scrape-plan https://quotes.toscrape.com/scroll
https://quotes.toscrape.com/scroll
robots.txt allowed no robots.txt
body in HTML no 0% of the visible content is already in the raw HTML
Tier 1 Call the internal JSON API
This page calls a JSON endpoint directly. Hitting it without a browser is the
fastest option and survives redesigns far better than parsing markup.
internal JSON API (1)
https://quotes.toscrape.com/api/quotes?page=1
GET 200 · application/json seen 19x
shape {has_next, page, quotes, tag, top_ten_tags}
pagination query parameter ?page=
# ready-to-run code follows...
That page has 95 characters of HTML and 16,816 characters on screen. Every rendering-based tool concludes "client-side rendered, you need a browser" and you accept ~2 seconds per page. But the page is just calling a JSON API, and calling it directly takes ~0.1 seconds. Over 1,000 pages that is 33 minutes versus 1 minute 40.
Nothing told you the API was there. That is the gap this fills.
The three tiers
| Situation | What to do | Cost per page | |
|---|---|---|---|
| 1 | The page calls an internal JSON API | Call it directly | ~0.1s, rarely breaks |
| 2 | No API, but the body is in the raw HTML | Plain HTTP + a parser | ~0.3s |
| 3 | Neither | Headless browser | ~2s, breaks often |
Checking 1 and 2 before reaching for 3 is the highest-leverage decision in extraction work. Most people skip straight to 3 because nothing tells them not to.
Install
pip install scrape-plan[browser]
playwright install chromium
The browser is only needed to detect internal APIs (tier 1) — that requires
watching what the page actually requests. Without it, scrape-plan --no-browser
still evaluates robots.txt and can confirm tier 2 when the body is already in the
raw HTML. When the raw HTML is nearly empty it reports Tier ? rather than
guessing, because at that point the page is either JavaScript-rendered or a block
page and there is no way to tell from the HTML alone:
pip install scrape-plan # no browser, no Playwright download
Usage
scrape-plan https://example.com/products # full analysis + code
scrape-plan URL --code # only the generated code
scrape-plan URL --json # machine-readable
scrape-plan URL --no-browser # skip tier-1 detection
scrape-plan URL --scrolls 6 # more scrolling for lazy loaders
Exit codes, so it composes in scripts:
| Code | Meaning |
|---|---|
0 |
Analyzed, crawling permitted |
2 |
robots.txt disallows this path |
3 |
Could not reach or analyze the target |
scrape-plan "$URL" --no-browser >/dev/null || echo "skipping $URL"
It tells you when not to scrape
robots.txt is evaluated for your exact path, with correct group
boundaries, wildcards, $ anchors, and longest-match-wins precedence. The rule
that matched is printed, so you can check the verdict rather than trust it.
$ scrape-plan "https://apps.shopify.com/search?q=test" --no-browser
robots.txt DISALLOWED Disallow: *q=*
...
This path is disallowed. Do not crawl it.
No code generated. Re-run with --force if you have permission.
No runnable code is generated for a disallowed path. A tool that flags the problem and then hands you the scraper anyway has not actually flagged anything.
Generated code
The code is not a sketch. It runs, and it carries the things people add last and therefore never add: a request interval and a User-Agent with a contact address.
The tier-2 snippet reads resp.content, never resp.text. When a server
declares no charset, requests falls back to ISO-8859-1 per RFC 2616 and
silently corrupts non-Latin text — no exception is raised, the data is just
wrong, and you find out much later from someone who can read the language.
What it is not
This does not scrape anything for you. Tools like Scrapy, Crawlee and Firecrawl do that well. This runs before them and answers one question: given this page, what is the cheapest approach that works, and am I permitted to use it?
It also does not defeat bot protection, solve CAPTCHAs, or access anything behind a login.
How it works
- Fetches
robots.txtfrom the target's origin and evaluates your exact path. - Fetches the raw HTML and measures how much of the rendered page is already present in it, by comparing three-word sequences rather than raw lengths. Comparing lengths does not work: hidden menus and templates inflate the raw side, which made one server-rendered page look 4x larger than its own rendered output.
- Opens the page in Chromium, scrolls a few times to trigger lazy requests, and records every same-page XHR/fetch that returns JSON.
- Groups endpoints by parameter names rather than values, so a paginated API hit 19 times shows up once rather than 19 times.
- Picks the tier and emits matching code.
At most 8 endpoints are probed, so the tool never becomes a source of load on the site being inspected.
Tests
python3 tests/test_core.py # 24 cases, no network
The robots parser and the endpoint grouping carry most of the tests, because both fail silently: a wrong answer looks exactly like a right one.
Related
A Chrome extension with the same core, for pages you already have open: scrape-plan extension
License
MIT © Jinhyuk Sung
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file scrape_plan-0.1.2.tar.gz.
File metadata
- Download URL: scrape_plan-0.1.2.tar.gz
- Upload date:
- Size: 15.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf8c531ec10bd3c2dbbb6c83e8b213b8c89bb0d8739f0c545621d9e5a8540c5c
|
|
| MD5 |
209bc548d8ac83698c48f47baebacbd5
|
|
| BLAKE2b-256 |
6b543c55ab78d1c5a47634b04fbf61b33bc8f4d7b01f18ec610123f0c5eea86d
|
File details
Details for the file scrape_plan-0.1.2-py3-none-any.whl.
File metadata
- Download URL: scrape_plan-0.1.2-py3-none-any.whl
- Upload date:
- Size: 17.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e63d3548efdb92f0a425317771d24befb17efb1a73b9ecad2f0ba97568e2b53
|
|
| MD5 |
422835c55bd088fa0fed0aa456e7821f
|
|
| BLAKE2b-256 |
c8c1da28f3f8b0f5569b39bd3a3a8cb4bd809051fe8c22a68426ad9b2f49da54
|