Web Scraping Pagination
Web scraping pagination means collecting every page of a list, not only the first: follow the Next link until it disappears, loop a page-number or offset parameter until a page comes back empty, call the JSON endpoint behind infinite scroll or "Load more", or pass an API's cursor until it comes back empty. Each method needs a stop rule and a duplicate check, because many sites repeat items or link back to page 1. ProxyField residential proxies can give every page request a new IP when a long list runs into a per-IP rate limit, from $1.95/GB; start with the Easy Trial: 1 GB for $2.95.
- residential IPs
- 412M+
- from, traffic never expires
- $1.95/GB
- Easy Trial
- 1 GB for $2.95
On this page 9 sections
- The four kinds of pagination
- Next-button pagination: follow the link
- Page numbers and offsets: loop the parameter
- Infinite scroll and "Load more"
- Cursor and token pagination in APIs
- Pagination in Scrapy, Playwright and Node.js
- Don't miss items or collect them twice
- Rate limits and proxies on long paginated lists
- Web Scraping Pagination FAQ
The four kinds of pagination
Open the list in your browser, go to page 2, and watch two things: the address bar and the Network tab of DevTools. Together they tell you which of the four kinds you have.
| Kind | How you recognize it | How to scrape it | When to stop |
|---|---|---|---|
| Next button | A "Next" or "›" link with a real href | Follow the link from each page | The link is missing or disabled |
| Page numbers or offset | The URL changes to ?page=2, ?page_num=2, ?offset=50 or /page/2/ | Loop the parameter yourself | A page returns no items, or you reach the last page number |
| Infinite scroll or "Load more" | The URL stays the same; new items arrive as you scroll or click | Call the JSON request the page makes, or scroll a real browser | The response is empty or says there is no next page |
| Cursor or token | A JSON response carries next, cursor, after or nextPageToken | Pass the token back on the next request | The token is missing or null |
Most sites mix them: a page-number URL behind a Next button, or a cursor API behind infinite scroll. Prefer the lowest layer you can reach. A URL you build yourself or a JSON endpoint is faster and more stable than clicking through a browser.
Page numbers and offsets: loop the parameter
When the page number is in the URL, you don't need the Next link at all. Build each URL yourself, and stop when a page comes back with no items. Many sites also let you ask for more items per page, which cuts the number of requests:
import time
import requests
from bs4 import BeautifulSoup
base = "https://www.scrapethissite.com/pages/forms/"
s = requests.Session()
s.headers["User-Agent"] = "Mozilla/5.0"
rows, page = [], 1
while True:
r = s.get(base, params={"page_num": page, "per_page": 100}, timeout=30)
r.raise_for_status()
teams = BeautifulSoup(r.text, "lxml").select("tr.team")
if not teams: # an empty page means you are past the end
break
rows += [[td.get_text(strip=True) for td in tr.select("td")] for tr in teams]
page += 1
time.sleep(2)
print(len(rows), "teams")With 100 rows per page, the same 582 rows took 6 requests instead of 24 (run on 2026-10-23). Note the stop rule: a page past the end of this site (?page_num=25 at 25 per page) returns status 200 with an empty table, not a 404. Many sites behave the same way, so test for "no items", not only for an error code. Other sites redirect to page 1 or repeat the last page, which the duplicate key catches.
Offset pagination is the same loop with a different parameter: ?offset=0&limit=50, then offset=50, offset=100, until the response is empty. If the first page shows a total ("582 results") or a last page number, you can compute every URL up front and fetch them concurrently; make web scraping faster shows how, and asynchronous web scraping in Python has the asyncio version.
Infinite scroll and "Load more"
Infinite scroll and "Load more" buttons are pagination hidden behind JavaScript: the page asks a server endpoint for the next batch and appends it. Find that request and you can usually skip the browser.
- Open DevTools, go to the Network tab, filter by Fetch/XHR, and scroll or click "Load more".
- Look for a request that returns the new items as JSON. Note how it pages:
page=2,offset=40,cursor=...orafter=.... - Copy it ("Copy as cURL"), replay it in your code, and loop the paging parameter with the stop rules above.
The approach is covered in depth in scraping hidden APIs. When the endpoint needs tokens you can't reproduce, scroll a real browser instead and stop when the page stops growing:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
page = p.chromium.launch().new_page()
page.goto("https://example.com/feed")
last = 0
while True:
page.mouse.wheel(0, 5000)
page.wait_for_timeout(1500) # give the next batch time to load
count = page.locator("article").count()
if count == last: # nothing new arrived: the end
break
last = count
items = page.locator("article").all_inner_texts()Replace the URL and the article selector with the real ones. For a "Load more" button, click it instead of scrolling and stop when the button disappears. More browser patterns, including waiting for the network response instead of a fixed delay, are in scrape dynamic web pages.
Cursor and token pagination in APIs
APIs that hold large or changing lists often page with a cursor: each response includes an opaque token, and you send it back to get the next batch. You cannot compute the next URL; you have to read it from the last response.
import requests
items, cursor = [], None
while True:
params = {"limit": 100}
if cursor:
params["cursor"] = cursor
data = requests.get("https://api.example.com/v1/items", params=params, timeout=30).json()
items += data["items"]
cursor = data.get("next_cursor") # the field name differs per API
if not cursor:
breakRead the API's documentation for the field names (next, nextPageToken, after, has_more) and for how long a cursor stays valid. Some cursors expire after minutes, so a crawl that pauses too long has to start the list again.
Pagination in Scrapy, Playwright and Node.js
The patterns are the same in every tool; only the syntax changes.
| Tool | Next link | Page numbers |
|---|---|---|
| Scrapy | yield response. | An async def start() that yields one request per page number (start_requests() before Scrapy 2.13; removed in 2.16.0) |
| Playwright | page.click("text=Next"), then wait for the new rows | page.goto(url_for(n)) in a loop |
| Node.js with Cheerio | read $('a[aria-label="Next"]'). and fetch it | a for loop over the page parameter |
Scrapy removes duplicate requests for you (its default duplicate filter skips a URL it has already requested in the same run), which takes care of the visited-URL set. The full Scrapy walkthrough is the Scrapy tutorial; the Node.js version of the Next-link loop is in the Cheerio web scraping tutorial.
Don't miss items or collect them twice
Pagination bugs rarely crash a scraper. They quietly skip or repeat rows. The usual causes:
- The list changes while you page through it. A new item at the top pushes every other item down by one, so the last item of page 1 shows up again on page 2. If the site offers a stable sort (oldest first, or by ID), use it, and de-duplicate by ID.
- The sort order is unstable. Items with the same sort value can swap places between requests. Add a second sort key if the site allows it, or accept duplicates and remove them by ID.
- A result cap. Many search pages stop at a fixed number of pages even when they report more results. Split the query into smaller slices (by category, price range or date) so that each slice fits under the cap.
- A failed page. One timeout in the middle of 500 pages leaves a gap. Retry failed pages a few times, log the page numbers that still fail, and save progress (the last page or cursor) so a crash doesn't send you back to page 1.
Count what you got against what the site says it has. If page 1 reports 582 results and you saved 575, something was skipped.
Rate limits and proxies on long paginated lists
Deep pagination is where scrapers meet rate limits: hundreds of requests to the same path from one IP in a few minutes. Slow down first: add a delay, keep concurrency low, and respect the site's robots.txt and terms (see is web scraping legal?). A 429 response means you are going too fast; 429 Too Many Requests explains how to back off.
When a per-IP limit still stops a lawful crawl, route the requests through a proxy pool. Page-number and Next-link pages are independent requests, so each can leave from a new IP:
proxy = "http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000"
s.proxies.update({"http": proxy, "https": proxy})gate.proxyfield.io:7000 is the ProxyField gateway, and <login>-country-us-city-new_york asks for an exit IP in New York. Region, session and session time go in the same username: -region-<region>, -session-<id> and -ttl-<seconds> (30 to 129,600 seconds, 20 minutes by default), as in <login>-country-us-region-new_york-city-new_york-session-he9f2qga-ttl-600; every part after the login is optional. The gateway can give each request a new IP, or keep one IP for a sticky session from 30 s to 36 h. Use a sticky session when the site ties the paging state to your session: a cursor issued to one IP and checked on the next request, or a "Load more" that relies on a session cookie. Sticky vs rotating proxies explains when each fits.
Pagination costs little traffic: at an assumed 150 KB per list page, a 2,000-page crawl uses about 0.3 GB, so the 1 GB Easy Trial ($2.95) covers it three times over. Pages rendered in a browser load scripts and images too and weigh several times more.
Paging through long lists? ProxyField residential proxies: 412M+ residential IPs in 195+ countries, a new IP on every request or sticky sessions up to 36 h, from $1.95/GB with no subscription; start with the Easy Trial (1 GB). Traffic never expires, and all purchases are final.
Web Scraping Pagination FAQ
How do I handle pagination when scraping?
ProxyField residential proxies, from $1.95/GB, carry the page requests of whichever of four methods the site uses: follow the Next link, loop the page or offset parameter, call the JSON endpoint behind infinite scroll, or pass back the API's cursor. Stop when the link or cursor is missing or a page returns no items. Keep a set of visited URLs and item IDs so that loops and repeated items don't end up in your data.
What is pagination in web scraping?
Pagination is how a site splits a long list across several pages or batches, and in scraping it means fetching every one of them, not only the first. The scraper has to find how the site moves to the next batch and when the list ends.
What are the common types of pagination?
Four: a Next button, page numbers or offsets in the URL, infinite scroll or a "Load more" button, and cursor or token pagination in APIs. Many sites combine them, for example a page-number URL behind a Next button.
Should I increment page numbers or follow the Next link?
Increment the number when the URL exposes it and the site shows a total, because you can then fetch pages in parallel and resume from any page. Follow the Next link when the URLs are opaque or change format, and keep a visited set either way.
What is the difference between cursor and offset pagination?
Offset pagination asks for items by position (skip 100, take 50), so you can compute any page, but inserts and deletions shift the positions and cause skipped or repeated items. Cursor pagination returns a token that marks where the last batch ended; you can't jump ahead, but the list stays consistent while it changes.
What is the difference between pagination and infinite scroll?
Pagination puts each batch on its own page or URL; infinite scroll loads the next batch into the same page as you scroll, through a background request. For scraping, infinite scroll is still pagination: find that request and page it, or scroll a browser until nothing new loads.
How do I know when infinite scroll is finished?
In the background request, the last batch is empty or carries a flag such as has_more: false or no next cursor. In a browser, count the items after each scroll and stop when the count stops growing after a wait of a second or two.
Why does my pagination scraper return duplicate records?
Usually because the list changed while you paged (new items push old ones onto the next page), the sort order is unstable, or two URLs serve the same page, such as /list and /list?page=1. Sort by a stable key where the site allows it and de-duplicate by each item's ID or URL.
Do I need Playwright for pagination?
Only when the next batch appears through JavaScript and you can't call the request behind it directly. Next links, page-number URLs and most "Load more" endpoints work with a plain HTTP client, which is faster and uses less traffic.
Why do scrapers get blocked when handling pagination?
Paging sends many similar requests to the same path in a short time, which is the pattern rate limits look for. Add delays, keep concurrency low, and send realistic headers; if a per-IP limit still stops a lawful crawl, spread the pages across several IPs.
Show 2 more questionsShow fewer questions
How can developers overcome pagination limits when scraping large datasets?
When a search stops at a fixed number of pages, split the query into slices that each fit under the cap: by category, price range, date or location. Then page through each slice and de-duplicate across slices by item ID.
Does following pagination increase crawl depth?
Yes. Each Next link is one more level away from the start page, so page 50 of a list sits 50 clicks deep. Crawlers with a depth limit stop early on long lists, so raise the limit for pagination links or generate the page URLs directly.
Related pages
Easy Trial: 1 GB for $2.95.
Try for the price of 1 GB, no subscription, traffic never expires.