Python Web Scraping Basics
Table of Contents + โ
In the last lesson you learned how to send mail straight from your code in Sending Emails with Python. Now letโs go the other way and read data from the web. A lot of useful information lives on web pages. But it does not come in a tidy file you can download. So how do you get it into your program? That is what web scraping is for.
๐ค Why Web Scraping?
Say Alex wants the title of every article on a news page. The data is sitting right there on the screen. But there is no button that says โgive me this as a list.โ
Copy-pasting by hand works for one page. It stops working the moment you have fifty pages, or the page updates every hour.
Web scraping fixes that. Your program reads a web page the same way your browser does. Then it picks out just the pieces you want.
These tools do most of the work:
- requests fetches the page. It downloads the raw HTML, which is the code that describes the page.
- BeautifulSoup reads that HTML and lets you pull out tags and text by name.
๐งฉ What HTML Looks Like
Before scraping, it helps to see what you are actually pulling apart. A web page is just text wrapped in tags. A tag is a label like <h1> or <p> that marks what a piece of content is.
Here is a tiny page:
<html> <body> <h1>Top Stories</h1> <p class="headline">Markets rise today</p> <p class="headline">New phone launches</p> </body></html>See the pattern? The title sits inside an <h1> tag. Each headline sits inside a <p> tag with class="headline". Scraping is mostly about saying โfind me the h1โ or โfind me every p with this class,โ then reading the text inside.
๐ฆ Installing the Tools
Both libraries are third-party, so they do not come with Python. You install them once with pip. Run this in your terminal:
pip install requests beautifulsoup4Notice that the package is called beautifulsoup4. But inside your code you import it from bs4. That trips up a lot of people, so keep it in mind.
๐ Fetching a Page
Letโs start by just downloading a page. This code asks a website for its HTML and prints the first part of it.
import requests
url = "https://example.com"response = requests.get(url)
print(response.status_code)print(response.text[:200])Reading it line by line:
requests.get(url)sends a request to the site and waits for the reply. The reply is stored inresponse.response.status_codeis a number that says how it went.200means OK.404means the page was not found.response.textis the full HTML of the page as one big string. We slice[:200]to print just the first 200 characters. That way it does not flood the screen.
Here is the sort of thing it prints:
Output
200<!doctype html><html><head> <title>Example Domain</title> <meta charset="utf-8" /> <meta http-equiv="Content-type" content="text/html;Note
That is example output. The exact HTML depends on the live site, so what you see will not match character for character. The shape is what matters.
Always check the status code before you trust the data. If it is not 200, the page did not load. Then there is nothing useful to read.
๐ Parsing With BeautifulSoup
Right now response.text is one long messy string. You could search it by hand, but that is painful and breaks easily. BeautifulSoup turns that string into something you can search by tag.
This code parses the HTML and grabs the title of the page:
import requestsfrom bs4 import BeautifulSoup
url = "https://example.com"response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
title = soup.find("h1")print(title.text)Walking through the new parts:
BeautifulSoup(response.text, "html.parser")takes the raw HTML and builds asoupobject you can search. The"html.parser"part just tells it which parser to use. This one comes built into Python.soup.find("h1")returns the first<h1>tag it finds on the page..textpulls out the readable text inside the tag, without the angle brackets.
Here is the output:
Output
Example DomainSo <h1>Example Domain</h1> becomes just Example Domain. That is the clean piece Alex actually wanted.
๐๏ธ Getting Many Elements With find_all
find gives you one tag. But usually you want all of them, like every headline on the page. For that you use find_all, which returns a list of every matching tag.
Imagine a page with several <p> tags marked class="headline". This code grabs them all and prints each oneโs text:
import requestsfrom bs4 import BeautifulSoup
url = "https://news.example.com"response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
headlines = soup.find_all("p", class_="headline")
for headline in headlines: print(headline.text)The key parts here:
soup.find_all("p", class_="headline")finds every<p>tag whose class isheadline. It hands back a list.- Notice it is
class_with an underscore, notclass. That is becauseclassis already a reserved word in Python, so BeautifulSoup spells itclass_. - We loop over the list and print
.textfor each tag. So we get one clean line per headline.
Here is example output for a news page like that:
Output
Markets rise todayNew phone launchesCity council votes on new parkNote
Again, this is representative output. A real news site changes its headlines all the time, so your run will show whatever is live right then.
That loop is the heart of most scraping jobs. Fetch the page, parse it, find the tags you care about, then read their text.
โณ Scrape Slowly and Politely
This part matters as much as the code. When you scrape, you are using someone elseโs server. If your script asks for hundreds of pages as fast as it can, you put load on them. And you might get blocked.
Caution
Always respect the site you are scraping. Keep these in mind:
- Read the rules. Many sites have a
robots.txtfile (for examplehttps://example.com/robots.txt) and terms of service that say what you may and may not collect. Check them first. - Go slow. Do not request page after page with no pause. Add a small delay between requests so you are not hammering the server.
Adding a pause is easy. This loop waits one second between pages using time.sleep:
import requestsimport timefrom bs4 import BeautifulSoup
pages = [ "https://example.com/page1", "https://example.com/page2", "https://example.com/page3",]
for url in pages: response = requests.get(url) soup = BeautifulSoup(response.text, "html.parser")
title = soup.find("h1") print(title.text)
time.sleep(1) # wait 1 second before the next pagetime.sleep(1) simply pauses the program for one second. So between each page, your script takes a short breath instead of firing requests back to back. It is a small thing that keeps you a good guest on someone elseโs server.
โ ๏ธ Common Mistakes
A few traps catch almost everyone the first time:
- Importing the wrong name. You install
beautifulsoup4but import frombs4.import beautifulsoup4will fail.
# โ Avoid: this name does not existimport beautifulsoup4
# โ
Good: import from bs4from bs4 import BeautifulSoup- Using
classinstead ofclass_. Plainclassis a Python keyword, so it errors out.
# โ Avoid: class is a reserved wordsoup.find_all("p", class="headline")
# โ
Good: use class_ with an underscoresoup.find_all("p", class_="headline")- Calling
.texton nothing. Iffinddoes not match anything, it returnsNone, andNone.textcrashes. Check first.
# โ Avoid: crashes if there is no h1title = soup.find("h1")print(title.text)
# โ
Good: make sure you found somethingtitle = soup.find("h1")if title is not None: print(title.text)- Forgetting to check the status code. If the page returned
404, parsing it gives you junk. Look atresponse.status_codebefore you parse.
โ Best Practices
Small habits that make scraping smoother:
- Check
response.status_codeis200before you parse the page. - Always test that
findactually returned a tag before reading.textfrom it. - Add a delay between requests with
time.sleepso you do not overload the server. - Read the siteโs
robots.txtand terms of service first, and only collect what you are allowed to. - When a site offers an official API, prefer that over scraping. An API gives you clean data and is the intended way in.
๐งฉ What Youโve Learned
You can now read data straight off a web page:
- โ Web scraping means your program reads a web page and pulls out the parts you want.
- โ
requests.get(url)downloads a page, andresponse.status_codetells you if it worked. - โ
BeautifulSoup(response.text, "html.parser")turns raw HTML into something you can search. - โ
findreturns the first matching tag, andfind_allreturns a list of every match. - โ
.textgives you the clean readable text inside a tag. - โ
Scrape politely: respect
robots.txtand the terms, and add a delay so you do not hammer the server.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Which library do you use to fetch the raw HTML of a web page?
Why: requests.get(url) downloads the page; BeautifulSoup then parses the HTML you got back.
- 2
You installed beautifulsoup4. How do you import it in your code?
Why: The package is beautifulsoup4 but the import name is bs4, so you write from bs4 import BeautifulSoup.
- 3
What does soup.find_all('p', class_='headline') return?
Why: find_all returns a list of all matching tags, while find returns only the first one.
- 4
Why add time.sleep(1) between requests when scraping many pages?
Why: Pausing between requests reduces load on the server you are scraping and lowers the chance of getting blocked.
๐ Whatโs Next?
Web pages are one source of data. Another big one is documents, and PDFs are everywhere. Next you will learn how to read and create them from Python.