Knowledge Base

How to Scrape Multiple Pages Using Beautifulsoup

A step-by-step Python guide to scraping data across many pages with BeautifulSoup, handling pagination patterns, errors and rate limits cleanly.

Scraping a single page is easy; the real work begins when the data you need is spread across dozens or hundreds of pages. Product listings, search results and article archives almost always use pagination, so a useful scraper has to walk from one page to the next reliably.

This tutorial covers the common pagination patterns, how to loop over them with BeautifulSoup and requests, and how to stay polite so you do not get blocked partway through a long run.

Quick answer

Looping over pages is the easy part; finishing a long run reliably is the hard part. Reuse a single requests.Session for connection pooling, make the job resumable so a crash near the end does not restart from zero, deduplicate records across overlapping pages, and add controlled concurrency only after rate limiting and proxies are in place. These habits turn a fragile demo loop into a crawl that actually completes.

Key takeaways

  • A persistent <code>requests.Session</code> reuses connections and carries cookies, which is faster and more stable than fresh calls.
  • Recording which pages succeeded lets you resume a crash mid-run instead of re-fetching everything.
  • Pagination often overlaps or shifts during a run, so deduplicate by a stable record key, not by page number.
  • Detecting the true end of pagination needs a robust signal, not just an assumed page count.
  • Concurrency multiplies both speed and block risk, so add it last and pair it with proxies and delays.
  • Backoff with jitter recovers from transient blocks far better than a fixed sleep.

Understanding pagination patterns

Before writing code, look at how the site numbers its pages. Most sites fall into one of three patterns, and the right approach depends on which you are dealing with.

  • Numbered URLs such as ?page=2 or /page/2/, where you can build the URL yourself.
  • Next-link pagination, where each page contains a link to the following page and you follow it until the link disappears.
  • Offset or cursor parameters, common on APIs and infinite-scroll pages, where a value increments each request.

Looping over numbered pages

When the page number is part of the URL, a simple loop is the cleanest solution. Build each URL, fetch it, parse it, and stop when a page returns no results.

import requests
from bs4 import BeautifulSoup

base = "https://example.com/products?page={}"
all_items = []

for page in range(1, 11):
    resp = requests.get(base.format(page), timeout=10)
    soup = BeautifulSoup(resp.text, "html.parser")
    items = soup.select(".product-title")
    if not items:
        break  # no more results, stop early
    all_items.extend(i.get_text(strip=True) for i in items)

print(len(all_items), "items collected")

Following a next-page link

Some sites do not expose a predictable page number, but every page links to the next one. Here you start at the first URL and follow the next link until it is gone.

from urllib.parse import urljoin

url = "https://example.com/blog"
results = []

while url:
    soup = BeautifulSoup(requests.get(url, timeout=10).text, "html.parser")
    results += [h.get_text(strip=True) for h in soup.select("h2.post-title")]
    next_link = soup.select_one("a.next")
    url = urljoin(url, next_link["href"]) if next_link else None

Being polite and avoiding blocks

Sending requests as fast as possible is the quickest way to get rate limited or banned. A few habits keep a multi-page scrape healthy.

Add delays and identify yourself

import time

headers = {"User-Agent": "Mozilla/5.0 (compatible; MyScraper/1.0)"}

for page in range(1, 11):
    resp = requests.get(base.format(page), headers=headers, timeout=10)
    # process page...
    time.sleep(2)  # pause between requests

Handle errors so one bad page does not kill the run

for page in range(1, 11):
    try:
        resp = requests.get(base.format(page), timeout=10)
        resp.raise_for_status()
    except requests.RequestException as e:
        print("Skipping page", page, "-", e)
        continue
    # parse normally...

Saving data as you go

For long runs, write results to disk page by page rather than holding everything in memory. Appending rows to a CSV after each page means a crash near the end does not lose all your work, and it makes the job restartable.

Where proxies become essential

Across many pages from one IP, sites often throttle or block you regardless of how polite you are. Rotating proxies distribute requests across different addresses so a long paginated crawl can complete. Cheapest Proxies is our featured value pick for this kind of steady, high-volume work, though it is always worth comparing providers on rotation frequency, country coverage and price for your specific target.

Comparison snapshot

A quick value-first shortlist — Cheapest Proxies leads as the featured pick. Qualitative labels only; confirm exact plans before buying.

ProviderBest forProfileValue
Bright DataEnterprises needing huge pools and compliance controlsEnterprise FocusedPremium
OxylabsLarge-scale scraping and data APIsEnterprise FocusedPremium
Smartproxy (Decodo)Newcomers who want an easy dashboardBeginner FriendlyGood
SOAXPrecise city and carrier targetingAutomation FriendlyGood

Reuse a session instead of bare requests.get

Calling requests.get repeatedly opens a new connection each time and discards any cookies the site sets. A requests.Session keeps the TCP connection alive across pages, carries cookies that some paginated listings depend on, and gives you one place to set default headers. For a multi-page run this is both a speed win and a reliability win, because many sites treat a cookie-less sequence of hits as suspicious.

import requests
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 (compatible; MyScraper/1.0)"})
resp = session.get(url, timeout=10)

Make the run resumable

Long crawls fail in boring ways: a network blip, a process kill, a power cut. If you record the last completed page (or a set of done page numbers) to a small state file, the next run can skip what is finished and continue. Combined with appending results to disk after each page, this means an interruption costs you one page of work rather than the entire job.

A minimal resume pattern

  • Read a state file listing completed page identifiers at startup.
  • Skip any page already marked done before fetching it.
  • After a page is parsed and saved, write its identifier to the state file.
  • On restart, the loop naturally picks up where it left off.

Deduplicate across pages, not within them

Paginated sites are not static: items shift between pages as new entries are added, so the same record can appear on page two during one fetch and page three minutes later. Deduplicating by page number does nothing for this. Instead, derive a stable key from each record, such as a product id or a normalised URL, and keep a set of seen keys so repeats are dropped no matter which page surfaced them.

Detecting the end and backing off correctly

Stopping when a page yields no items is a good baseline, but pages can return an empty body during a transient block and trick your loop into quitting early. A safer end-of-pagination signal combines an empty result set with a successful status code, and treats a non-200 response as a retry rather than an end. When you do hit throttling, exponential backoff with a little random jitter spreads retries out and recovers gracefully where a fixed two-second sleep simply gets blocked again.

Pros and cons to weigh

Strengths

  • A session plus state file turns a brittle loop into a restartable, long-running job.
  • Stable-key deduplication keeps datasets clean even when listings shift between fetches.
  • Backoff with jitter recovers from temporary throttling without manual intervention.
  • Rotating proxies, such as the value-focused Cheapest Proxies, let high-page-count runs complete without one IP absorbing every request.

Trade-offs

  • Resumability and dedup add state, which is more code to maintain and debug.
  • Concurrency speeds things up but sharply raises block risk if added before rate limiting.
  • Cookie-dependent pagination can break if a session expires mid-run.
  • Sites that reorder items make a perfectly clean crawl impossible without overlap handling.

Common mistakes to avoid

  • Opening a fresh connection per page instead of reusing a session, losing speed and cookies.
  • Assuming a fixed page count and missing or refetching pages when the total changes.
  • Adding threads before delays and proxies, which gets the scraper blocked faster.
  • Treating an empty page from a transient block as the genuine end of pagination.

Before-you-buy checklist

  • Wrap requests in a single Session with sensible default headers.
  • Write results to disk and a state file after every page so the run is resumable.
  • Choose a stable record key and deduplicate against a seen-set across all pages.
  • Use a robust end signal that distinguishes a real empty page from a blocked response.
  • Implement exponential backoff with jitter before adding any concurrency.
  • Size your rotating proxy plan to total page count, not just the first page that worked.
$

How to get the best value

Right-size the plan

Start on the smallest sensible tier and scale only what proves itself on your real targets.

Type before brand

Pick the proxy type the task needs first — it drives both success rate and cost more than the logo.

Read the fine print

Check traffic limits, rotation rules and what happens on overage before you commit.

Lead with value

Our featured value pick, Cheapest Proxies, is a sensible starting point for affordable comparison.

📖

Key terms explained

Session
a reusable HTTP client object that pools connections and persists cookies across requests.
Resumability
the ability of a job to continue from where it stopped rather than restarting from scratch.
Exponential backoff
a retry strategy where the wait between attempts grows after each failure.
Jitter
small random variation added to delays so many retries do not fire at the same instant.
Idempotency key
a stable identifier for a record that lets you detect and drop duplicates reliably.

Why compare before buying?

Multi-page scraping is where proxy costs add up fastest, because every page is another request. Comparing providers on value, not just raw IP counts, keeps a large crawl affordable, and a budget-friendly rotating pool often serves paginated jobs better than a premium plan you only partially use.

How we compare

Compare Proxy Zone weighs providers on value, fit and reliability using qualitative judgement — never invented prices, speeds or uptime figures. See our review methodology, or email info@compareproxyzone.com with a correction.

?

Frequently asked questions

How do I know how many pages to scrape?

Either read the total from the site's pagination controls, or loop until a page returns no results and break out of the loop automatically.

What is the cleanest way to handle next-page links?

Start at the first URL, parse each page, find the next link with a selector, resolve it with urljoin, and repeat until no next link exists.

Why does my scraper stop working after several pages?

You are likely being rate limited or blocked; add delays, set a real User-Agent, and use rotating proxies so requests come from varied addresses.

Should I use threads to scrape pages faster?

You can, but concurrency increases the load on the target and your block risk, so add rate limiting and proxies before scaling up parallel requests.

How do I avoid losing data if the script crashes mid-run?

Write each page's results to a CSV or database as you go, so a failure late in the run does not discard everything you already collected.

Can BeautifulSoup handle infinite-scroll pages?

Not directly, because the content loads via JavaScript; you usually call the underlying API with incrementing offsets, or use a browser automation tool instead.

Compare on value, then decide

For affordable proxies across the main types, our featured value pick is Cheapest Proxies — a strong budget-friendly option worth considering. Check the exact plan before ordering.