Web Scraping with BeautifulSoup
Objectives
By the end of this chapter, you should be able to:
- Explain what web scraping is useful for
- Explain why
robots.txtmatters when scraping - Use BeautifulSoup to scrape web pages
💡 Why this matters: Plenty of useful data on the web has no API behind it. Scraping is what lets you turn an ordinary web page into structured data your program can actually use.
What Is Web Scraping?
Web scraping is downloading and extracting data from a website. It breaks down into three steps:
- Downloading the HTML document from a page
- Extracting the data you want from that HTML
- Doing something with the data, usually saving it somewhere
Ideally you’d get data through a site’s API instead, but plenty of sites don’t expose one. When there’s no programmatic way to get the data you need, scraping is the fallback.
Respect robots.txt
Before scraping any site, check its robots.txt file, a plain text file most sites publish that tells automated programs (like your scraper) what they’re allowed and not allowed to download. It typically lives at the root of a domain, e.g. https://example.com/robots.txt.
Different sites take very different stances here: some are wide open, others heavily restrict what bots may access. Always check a site’s robots.txt before scraping it, and honor what it says. You can read more about the standard at robotstxt.org.
BeautifulSoup
The library we’ll use for scraping is BeautifulSoup:
pip3 install beautifulsoup4
BeautifulSoup handles step two: extracting data from HTML you’ve already downloaded. Without a tool like it, parsing raw HTML by hand is a genuinely hard problem.
Here’s BeautifulSoup pulling text out of a list of <li> elements:
import bs4
html = """
<html>
<body>
<h3>Names</h3>
<ul>
<li>Erin</li>
<li>Jordan</li>
<li>Maya</li>
<li>Priya</li>
</ul>
</body>
</html>
"""
soup = bs4.BeautifulSoup(html, "html.parser")
for li in soup.find_all("li"):
print(li.text)
BeautifulSoup(html, "html.parser") takes the HTML as its first argument and the parser to use as its second. "html.parser" (built into Python) works fine for most purposes.
You can also find a single element by its id:
import bs4
html = """
<html>
<body>
<div id="interesting-data">Maya's favorite language is Python</div>
</body>
</html>
"""
soup = bs4.BeautifulSoup(html, "html.parser")
div = soup.find(id="interesting-data")
print(div.text) # "Maya's favorite language is Python"
A few other methods worth knowing:
| Method | Purpose |
|---|---|
.select() |
Find element(s) using CSS selectors |
.children |
Get all children of an element |
.parent |
Get the parent of an element |
Downloading and Scraping a Real Page
Combine urllib.request (to download a page) with bs4 (to parse it):
import urllib.request
import bs4
url = "https://news.ycombinator.com/"
data = urllib.request.urlopen(url).read()
soup = bs4.BeautifulSoup(data, "html.parser")
links = soup.select("span.titleline > a")
for link in links:
print(f"{link['href']} {link.text}")
"span.titleline > a" is a CSS selector, the same kind of syntax used to style web pages. span.titleline means “a <span> with class titleline,” and > a means “an <a> element that is a direct child of it.” .select() always returns a list, even when only one element matches.
A word of caution: the exact selector you need depends entirely on that page’s current HTML structure, which sites change over time. Before scraping any real page, open your browser’s dev tools and inspect the actual markup to find the right selector. Don’t assume an example selector will still match.
Saving Scraped Data
Once you’ve extracted what you want, save it. Here, we’ll use a TSV (tab-separated values) file, since article titles often contain commas:
import urllib.request
import bs4
import csv
url = "https://news.ycombinator.com/"
data = urllib.request.urlopen(url).read()
soup = bs4.BeautifulSoup(data, "html.parser")
links = soup.select("span.titleline > a")
with open("articles.tsv", "w") as tsvfile:
writer = csv.writer(tsvfile, delimiter="\t")
writer.writerow(("Link", "Title"))
for link in links:
writer.writerow((link["href"], link.text))
That’s the full scraping pipeline: download, extract, save.
Try It
- Write a small HTML string with a few elements, parse it with
BeautifulSoup, and extract text withfind_all. - Check the
robots.txtfile of a real site you’re familiar with, in your browser, at/robots.txt. - Download a real page with
urllib.request, inspect its HTML in your browser’s dev tools, and try selecting one element with.select().
Recap
- Scraping has three steps: download the HTML, extract the data you want, then save it.
- Always check and honor a site’s
robots.txtbefore scraping it. - BeautifulSoup parses downloaded HTML:
find,find_all, and.select()are the main ways to locate elements, and.textgets their content. - CSS selectors depend on a page’s current markup, which changes over time. Inspect the live page before assuming a selector will work.
Next lesson: making HTTP requests directly to APIs with the requests module.