Free practice site
Somewhere safe to point a scraper.
A fictional shop, built to be scraped. Eleven pages, each one a different thing that breaks scrapers — up to and including shadow DOM, iframes and a returns log with nothing tidy in it. Nothing here is real, nothing is rate-limited, and there is no login and no bot detection. Use it with any tool — a browser extension, Python, Scrapy, Playwright, curl, whatever you are learning.
Why not just practise on a real site?
Because a real shop will rate-limit you, ban your IP, or quietly serve you different markup once it decides you are a bot — and none of that teaches you anything about scraping. You end up debugging someone's defences instead of your selector.
There is also the question of whether you should be pointing a half-finished script at a business's servers at all. Here, that question does not arise.
Start here: the four common ones
Each page is a different thing that breaks scrapers. Expected results are listed so you can tell whether yours is right.
Paginated card grid
Products in <article> cards, not a table — nine per page,
six pages, a Next button at the bottom. Tests detection on repeated markup and
following pagination.
Expected: 54 rows across 6 pages.
A plain HTML table
A real <table> with a <thead>. The easy case,
and a good first target for anything you are writing from scratch.
Expected: 50 rows and 8 columns — the seven in the table, plus the product link the extension keeps as a column of its own.
Infinite scroll
Eight rows on load, then more appended each time you reach the bottom, five batches
before it stops. Tests whether your scraper scrolls, waits, and knows when to give
up.
Expected: 8 rows on load, 40 after scrolling to the end.
A value outside the table
A discount that applies to every row but is written once, above the table, and never
repeated inside it. No amount of automatic detection can infer that — it is what
manual selection is for.
Expected: 10 rows, plus one discount no row contains.
Then the seven that actually catch people out
These are the ones people discover their tool cannot do, usually halfway through a job. Same shop, same styling — what changes is the markup underneath.
Products inside a shadow root
The gallery is a web component, and its markup lives in an open shadow root.
document.querySelectorAll('article') returns nothing: there is not one
product in the light DOM. A closed root is beyond any extension. An open one is only
beyond the ones that never look.
Expected: 12 products, none of them in the page's own DOM.
A table inside an iframe
The stock table is a separate document in a same-origin iframe. Point a
scraper at the page URL and it will report that there is no table here, and it will be
telling the truth about this document.
Expected: 24 rows and 7 columns (six in the table, plus the product link), and 0 tables in the parent page.
JSON-LD and microdata
Twenty products marked up twice over: schema.org microdata in the HTML and
JSON-LD in the head. The two agree exactly, so you can check one against the
other — and the machine-readable price sits in a content attribute,
not in the visible text.
Expected: 20 products, identical read either way.
A Load more button
Not pagination and not infinite scroll: a button that fetches the next batch, with a
deliberate delay, so clicking without waiting gets you nothing. Three clicks to the
end, and then the button is removed rather than just going quiet.
Expected: 12 on load, 48 after three clicks.
A grid with no table in it
<div> elements wearing role="table",
role="row" and role="cell". This is how most JavaScript data
grids render, and why table tr td comes back empty on half the web apps
you will ever point a scraper at.
Expected: 30 rows and 7 columns (six of data, plus the link), 0 <table> elements.
Data that is a mess
Dates in three formats, money in three more, empty cells, N/A where a
number should be, entity-encoded names, and two parcels logged twice by two different
people. Getting it out is the easy part. This page is about what happens next.
Expected: 24 rows, 13 empty cells, 2 rows that repeat exactly.
Two tables on one page
Different widths, both real. Automatic detection takes the bigger one, so getting the
other means asking for it — which is what an Another table control is
for, in any tool that has one.
Expected: 8 rows and 5 columns, or 14 rows and 6.
A long one: 2,000 rows
Forty pages of fifty, with a pager that shows a window rather than every page number
— so the Next control is the only reliable way through, which is how most
real shops of this size work. Long enough to see what the others are too small to
show: the preview capping at 100 rows while the export stays whole, and the row counter
climbing past what any one page holds.
Expected: 2,000 rows and 11 columns, across 40 pages.
A board that changes every minute
Every other page here is fixed, which makes comparing one run with the next
impossible to show. This one derives its own prices, stock and line-up from the
clock: a few move each minute, one sells out, one goes, one arrives. Scrape it,
wait for the minute to turn, scrape it again. It prints what moved underneath, so
you can check the answer rather than take it.
Expected: 20–24 rows and 6 columns, different every minute.
A ten-level link chain
One hop proves nothing, and three is where most practice sites stop. Northwind traces a material all the way back to the standard its auditor worked to, which is what a real traceability record looks like — and gives you ten levels of genuinely different fields to chain through.
| Level | Page | Fields only found there |
|---|---|---|
| The list | Catalogue | Name, maker, price, rating, stock |
| → 1 | Product | SKU, material, made in, weight, lead time, warranty |
| → 2 | Maker | Founded, based in, workshop, typical lead time, minimum order |
| → 3 | Sourcing | Primary material, sourced from, certification, recycled content, last audited |
| → 4 | Material lot | Lot reference, region of origin, received, quantity, storage |
| → 5 | Supplier | Founded, based in, people, contract since, payment terms |
| → 6 | Origin site | Coordinates, land, practice, season |
| → 7 | Certificate | Number, scheme owner, issued, expires, scope |
| → 8 | Certifying body | Accreditation, scope, accredited since |
| → 9 | Audit report | Audit date, outcome, findings, next audit due |
| → 10 | Standard | Owner, audit type, sampling rule, cycle, published |
Every product links to its maker through an identically-worded “About the maker →”, not through the maker's name. That is deliberate, and it is how real shops label such links: a chain you confirm on one row only works on the rest if the label is the same everywhere. If your scraper works on row one and fails on row two, that is usually why. Every hop below it works the same way — Material lot, Supplier, Origin site and the rest are worded identically on every page at that level.
120 product pages, 8 makers, 8 sourcing pages and 30 traceability pages behind them. Ten levels is expensive on purpose: a row costs ten page loads, so fifty rows is five hundred. If you are going to learn what pacing a run means, learn it here rather than on somebody's shop.
Fair use
Take what you like. It is a static site with no database behind it and no bill attached to traffic. If you are load-testing something enormous, a moment's delay between requests is polite rather than necessary.
It was built to demonstrate the Magic Scraper extension, but it is not restricted to it and never will be. A practice site that only works with one tool is not a practice site.
Northwind Supply is invented. The products, makers, prices, SKUs and audit dates are all generated, and any resemblance to a real business is coincidental.