How to Build a Web Scraper
To build a web scraper, pick the page you need, find the data in its HTML with your browser's developer tools, download the page with an HTTP library, pull out the fields with CSS selectors, follow the pagination, and save the rows to a CSV or JSON file; in Python that is about 40 lines with Requests and Beautiful Soup. Once the scraper fetches more than a few hundred pages a day from one site, send its requests through rotating proxies. ProxyField residential proxies plug into any scraper over HTTP, HTTPS or SOCKS5 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 15 sections
- What a web scraper does
- Before you start: is the page yours to scrape?
- Step 1: Inspect the page to find the elements
- Step 2: Set up Python
- Step 3: Fetch the page
- Step 4: Parse the fields
- Step 5: Follow the pagination
- Step 6: Save the data
- Step 7: Make it reliable
- Step 8: Add proxies when you scale
- When the page needs a browser
- Build it, use a no-code tool, or call an API?
- Is web scraping hard to learn?
- Next steps
- How to Build a Web Scraper FAQ
What a web scraper does
A web scraper is a program that does three things in a loop: it fetches a page, extracts the fields you want from it, and stores them. Everything else (pagination, retries, proxies, scheduling) is there to keep that loop running on more pages without breaking.
| Part | What it does | In this tutorial |
|---|---|---|
| Fetcher | Downloads the page's HTML over HTTP | requests |
| Parser | Finds elements in the HTML and reads their text or attributes | Beautiful Soup with CSS selectors |
| Crawler logic | Decides which page to fetch next (next page, detail pages) | A page_num loop |
| Storage | Writes the rows somewhere you can use them | A CSV file |
| Network layer | Sends requests from other IPs when one IP is not enough | ProxyField gateway |
The tutorial uses two pages of scrapethissite.com, a sandbox built for scraping practice: a list of countries on one page and a table of hockey teams spread over many pages. Its robots.txt blocks only the lesson and FAQ pages, and the selectors below were checked against the live HTML the same day.
Before you start: is the page yours to scrape?
Check two things before you write code: the site's robots.txt (at /robots.txt on the domain) and its terms of use. Scrape public pages only, never data behind a login you are not entitled to, and keep personal data out unless you have a lawful reason to collect it. For the details, see is web scraping legal and the guide to checking whether scraping is allowed.
Step 1: Inspect the page to find the elements
Open the page in Chrome or Firefox, right-click the first value you want (a country name) and choose Inspect. The Elements panel jumps to its tag. Look for a class or attribute that repeats on every item:
<div class="col-md-4 country">
<h3 class="country-name">Andorra</h3>
<div class="country-info">
<strong>Capital:</strong> <span class="country-capital">Andorra la Vella</span>
<strong>Population:</strong> <span class="country-population">84000</span>
<strong>Area (km2):</strong> <span class="country-area">468.0</span>
</div>
</div>Every country sits in a div.country, and each field has its own class. Those classes are your selectors: div.country, h3.country-name, span.country-capital.
Two checks save hours later:
-
Is the data in the page source?
Press Ctrl+U (Cmd+Option+U on a Mac) and search for a value. If it is there, a plain HTTP request is enough. If it appears only in the Elements panel, JavaScript adds it after the page loads: read how to scrape dynamic web pages.
-
Does the page call a JSON API?
Open the Network panel, filter by Fetch/XHR and reload. If a request returns the data as JSON, call that URL directly instead of parsing HTML.
Step 2: Set up Python
Install Python 3.10 or newer, create a virtual environment and install two libraries. Requests 2.34.2 needs Python 3.10+, and Beautiful Soup 4.15.0 is MIT-licensed (PyPI, 2026-10-23).
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install requests beautifulsoup4Step 3: Fetch the page
Send a GET request with a timeout and a browser-like User-Agent, and stop if the status is not 200.
import requests
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/141.0 Safari/537.36"}
r = requests.get("https://www.scrapethissite.com/pages/simple/", headers=HEADERS, timeout=30)
r.raise_for_status()
html = r.text
print(len(html), "characters")raise_for_status() turns a 403, 404 or 429 into an error, so you notice a block instead of parsing an error page.
Step 4: Parse the fields
Beautiful Soup turns the HTML into a tree; select() takes a CSS selector and returns every match, and select_one() returns the first.
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
rows = []
for card in soup.select("div.country"):
rows.append({
"name": card.select_one("h3.country-name").get_text(strip=True),
"capital": card.select_one("span.country-capital").get_text(strip=True),
"population": int(card.select_one("span.country-population").get_text(strip=True)),
"area_km2": float(card.select_one("span.country-area").get_text(strip=True)),
})
print(len(rows), rows[0])The page lists 250 countries. get_text(strip=True) drops the whitespace around the name, and the int() and float() calls turn text into numbers you can sort. For every way to find and select elements, see the Beautiful Soup web scraping tutorial.
Step 5: Follow the pagination
Most lists are split into pages. On the hockey table, the page number is a URL parameter, ?page_num=2, so the scraper can count up until a page comes back with no rows. Where a site has a "Next" link instead, read its href and join it to the current URL with urllib.parse.urljoin.
import time
def scrape_teams(session):
teams, page = [], 1
while True:
r = session.get("https://www.scrapethissite.com/pages/forms/",
params={"page_num": page}, timeout=30)
r.raise_for_status()
found = BeautifulSoup(r.text, "html.parser").select("tr.team")
if not found:
break
for tr in found:
teams.append({
"team": tr.select_one("td.name").get_text(strip=True),
"year": int(tr.select_one("td.year").get_text(strip=True)),
"wins": int(tr.select_one("td.wins").get_text(strip=True)),
"losses": int(tr.select_one("td.losses").get_text(strip=True)),
})
page += 1
time.sleep(1) # one request per second: polite and enough for a practice site
return teamsA requests.Session reuses the connection between pages, which makes a long run faster.
Step 6: Save the data
Write the rows to CSV with the standard library; Excel and Google Sheets open the file as is.
import csv
def save_csv(rows, path):
with open(path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
with requests.Session() as s:
s.headers.update(HEADERS)
save_csv(scrape_teams(s), "teams.csv")For JSON, json.dump(rows, f, indent=2) does the same job. Once a scraper runs every day, a database (SQLite to start) makes it easy to keep only new or changed rows.
Step 7: Make it reliable
A scraper that runs once on your laptop and a scraper you trust every day differ in four habits:
- Retry with backoff. Retry timeouts and 429 or 5xx responses 3 times, waiting longer each time; give up on 403 and 404.
- Check what you parsed. If a selector returns
None, the layout changed: log the URL and stop, rather than writing empty rows. - Pace the requests. Keep a delay between requests to the same site and honor a
Retry-Afterheader. Speed comes from fetching several sites in parallel, not from hammering one; see how to make web scraping faster. - Log every run. Pages fetched, rows written, errors by status code. When a number drops, you know where to look.
Step 8: Add proxies when you scale
Sites limit how many requests one IP may send. A scraper that fetches a few pages from your home IP is fine; one that fetches thousands of pages a day from one site gets 429 and 403 responses, then a block. Rotating proxies spread the requests over many IPs, and a location in the username lets you see the page the way local visitors see it.
With Requests, the proxy is one argument. The ProxyField gateway is gate.proxyfield.io:7000; replace <login> and <password> with your credentials. <login>-country-us-city-new_york asks for an exit IP in New York, United States. Region, session and session-time options: -region-<region>, -session-<id> and -ttl-<seconds> (30 to 129,600 seconds, 20 minutes by default) after the location, as in <login>-country-us-region-new_york-city-new_york-session-he9f2qga-ttl-600.
PROXY = "http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000"
with requests.Session() as s:
s.headers.update(HEADERS)
s.proxies.update({"http": PROXY, "https": PROXY})
print(s.get("https://api.ipify.org", timeout=30).text) # the exit IP the site sees
save_csv(scrape_teams(s), "teams.csv")The gateway can give each request a new IP, or keep one IP for a sticky session from 30 s to 36 h when a site needs the same visitor across pages. Full setup, SOCKS5 included: Python proxy setup. Which proxy type to pick, and how much traffic a job needs: proxies for web scraping.
When the page needs a browser
If Step 1 showed the data only in the Elements panel, Requests will fetch an empty template. You have three ways out, in order of cost: call the JSON API the page uses, parse JSON embedded in a <script> tag, or render the page with Playwright or Selenium. The Python versions are in Python dynamic web scraping. If you would rather not run browsers at all, the ProxyField Web Scraping API fetches any URL with plain HTTP or with JavaScript rendering, billed on successful results only; ask for it through Contact sales.
Build it, use a no-code tool, or call an API?
| Option | Good for | Limits |
|---|---|---|
| Your own script (this tutorial) | Full control, any site, low running cost | You fix it when the site changes |
| A no-code scraper or browser extension | A one-off table without code | Templates break; large jobs cost more; see no-code web scrapers |
| A scraping API | No proxies or browsers to run | You pay per request or result |
| A framework (Scrapy, Crawlee) | Many pages, queues, retries built in | More to learn up front; see the best web scraping tools |
Is web scraping hard to learn?
The basics take a weekend if you already write a little code: the script above covers fetching, parsing, pagination and saving. What takes longer is the part this tutorial only touches: pages that need JavaScript, sites that block automated traffic, and keeping scrapers working as layouts change. Practice on a sandbox, then on one real site you care about; the web scraping projects list has ideas by level.
Next steps
- Run it every day: how to schedule and deploy web scrapers.
- Follow links across a whole site: how to build a web crawler.
- Other languages: Ruby web scraping, web scraping in Rust.
- Images rather than text: how to scrape images from a website.
Ready to scrape past your home IP? ProxyField residential proxies: 412M+ residential IPs in 195+ countries, a new IP per request or sticky sessions up to 36 h, unlimited concurrent threads. Pay per GB, no subscription; start with the Easy Trial (1 GB). Traffic never expires, and all purchases are final.
How to Build a Web Scraper FAQ
How do I build a web scraper?
ProxyField residential proxies, from $1.95/GB, plug into a web scraper with one argument, and the scraper itself takes 8 steps: inspect the page to find the elements, set up Python, fetch the page with an HTTP library, parse the fields with CSS selectors, follow the pagination, save the rows to CSV or JSON, add retries and delays, and send the requests through rotating proxies once one IP is not enough. In Python, Requests and Beautiful Soup cover the first steps in about 40 lines.
How do I start web scraping?
Start with one public page whose data is in the HTML, such as a practice sandbox, and write a script that fetches it and prints one field. Then add the rest of the fields, a second page and a CSV file. Python with Requests and Beautiful Soup is the most common first stack.
How do I inspect a web page to find the elements to scrape?
Right-click the value in Chrome or Firefox and choose Inspect: the Elements panel shows its tag, and you look for a class or attribute that repeats on every item, such as div.country. Then check the page source (Ctrl+U) to see whether the value is in the raw HTML or added later by JavaScript.
Is web scraping hard to learn?
Not the basics: if you can write a short script, you can fetch a page, select elements and save them in a weekend. The hard parts come later: JavaScript-rendered pages, sites that block automated traffic, and selectors that break when a layout changes.
How long does it take to learn web scraping?
A few days to scrape static pages with Python, if you already know basic programming. Handling JavaScript pages, logins you are entitled to, rate limits and proxies takes a few weeks of practice on real sites.
What is the easiest way to scrape a website?
For one table on one page, a browser extension or the import function of a spreadsheet, with no code. For anything you will run again, a short Requests and Beautiful Soup script is easier to keep working than a point-and-click template.
What are the first steps in setting up a web scraping project?
Write down the fields you need and the pages they are on, check the site's robots.txt and terms, and inspect the page to see whether the data is in the HTML or a JSON request. Then set up a virtual environment, install your libraries and scrape one page before you add pagination.
What is a good web scraping example to start with?
A list page whose items share one class, such as the countries page on scrapethissite.com: every country is a div.country with the name, capital, population and area inside. It teaches selectors, cleaning text into numbers and writing a CSV in one exercise.
Where is the documentation for web scraping libraries?
Each library documents itself: Requests at requests.readthedocs.io, Beautiful Soup at crummy.com/software/BeautifulSoup/bs4/doc, Scrapy at docs.scrapy.org and Playwright at playwright.dev. For HTTP itself, MDN's HTTP reference explains status codes and headers.
How do I save scraped data to CSV or Excel?
Collect each item as a dictionary and write the list with Python's csv.DictWriter; Excel and Google Sheets open the file directly. For a real .xlsx file, pandas.DataFrame(rows).to_excel("data.xlsx") works once pandas and openpyxl are installed.
Show 1 more questionShow fewer questions
When should I add proxies to my scraper?
When one IP is not enough: the site answers with 429 or 403 responses at a polite pace, or you need the page as it looks from another country or city. Before that, a polite delay between requests is usually all a small scraper needs.
Related pages
Easy Trial: 1 GB for $2.95.
Try for the price of 1 GB, no subscription, traffic never expires.