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.

Start with the catalogue No sign-up. Scrape it as hard as you like.

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.

01

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.

02

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.

03

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.

04

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.

05

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.

06

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.

07

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.

08

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.

09

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.

10

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.

11

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.

12

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.

13

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.

LevelPageFields only found there
The listCatalogueName, maker, price, rating, stock
→ 1ProductSKU, material, made in, weight, lead time, warranty
→ 2MakerFounded, based in, workshop, typical lead time, minimum order
→ 3SourcingPrimary material, sourced from, certification, recycled content, last audited
→ 4Material lotLot reference, region of origin, received, quantity, storage
→ 5SupplierFounded, based in, people, contract since, payment terms
→ 6Origin siteCoordinates, land, practice, season
→ 7CertificateNumber, scheme owner, issued, expires, scope
→ 8Certifying bodyAccreditation, scope, accredited since
→ 9Audit reportAudit date, outcome, findings, next audit due
→ 10StandardOwner, 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.