Regional Settings

Language

No languages match your search.

Data collection · 2026-06-10 · Updated

Parsing HTML with BeautifulSoup: a practical guide for analysts

Turn web pages into clean rows for reports with BeautifulSoup: find and select patterns, tree navigation, defensive habits, and live pages fetched through a proxy.

Analysts and marketing-operations teams often hold HTML they need as data: a saved product listing, a competitor's public price page, a table in an exported report. BeautifulSoup is a Python library built for exactly that. It turns messy, real-world markup into a tree you can search with short, readable code, so the effort goes into the data instead of the parsing. This guide covers installation, the selection patterns you will use day to day, and how to pair BeautifulSoup with requests and a proxy for live pages.

Responsible use

Nothing is at stake while you parse files already on your machine. Fetching live pages is different: the site's robots.txt and Terms of Service apply, the data has to be public and permitted, requests need spacing to respect rate limits, and logins and bot protection are off limits. Teams that keep to this keep their jobs running, and the sites they rely on stay healthy.

Installing the library and a parser

You need BeautifulSoup itself plus a parser. html.parser ships with Python and does the job; lxml is faster and more tolerant of broken markup:

pip install beautifulsoup4 lxml requests

Turning a snippet into a soup object

Hand some HTML and a parser name to BeautifulSoup and you get a soup object back. Plain attribute access then reaches into the tree:

from bs4 import BeautifulSoup

html = """
<html>
  <body>
    <h1 class="title">Welcome</h1>
    <p class="intro">Hello <a href="/more">world</a>.</p>
  </body>
</html>
"""

soup = BeautifulSoup(html, "lxml")
print(soup.h1.text)            # Welcome
print(soup.p.a["href"])        # /more
print(soup.title)              # None (no title tag here)

Locating elements by tag and attribute

Most lookups use find for the first match or find_all for every match, filtered by tag name, by attributes or by both:

# first paragraph with class "intro"
intro = soup.find("p", class_="intro")

# every link on the page
for link in soup.find_all("a"):
    print(link.get("href"), "->", link.text)

# filter by any attribute
button = soup.find("button", attrs={"data-role": "submit"})

One quirk to know: class is a reserved word in Python, so filtering by class uses class_, with a trailing underscore. Writing class_ is correct, not a typo.

CSS selectors for people who know CSS

Anyone on the team who writes CSS can skip the method calls above. select and select_one take ordinary stylesheet selectors, which often makes them the shortest route to the data:

# all items inside a list with id "results"
for item in soup.select("#results li.product"):
    name = item.select_one(".name").get_text(strip=True)
    price = item.select_one(".price").get_text(strip=True)
    print(name, price)

The strip=True option on get_text removes leading and trailing whitespace, which means one less cleanup step in the spreadsheet.

Moving around the tree

Sometimes the value you want sits beside or inside another element, for example the cell next to a "Price" label. BeautifulSoup follows those relationships directly:

row = soup.find("td", string="Price")
value = row.find_next_sibling("td").text   # the cell after "Price"

parent_div = row.parent                     # go up one level
children = list(parent_div.children)        # immediate children

find_next_sibling, find_previous_sibling, parent, children and find_parent cover most of these moves. Reach for them when the element you need has no useful class of its own.

Live pages through a proxy

Learning on snippets is fine, but reports need live pages. Fetch them with requests, parse them with BeautifulSoup, and send the request through a proxy so that a day of repeated fetching does not get the office IP blocked. Datacenter proxies are quick and economical for straightforward sites, while residential proxies are the choice for sites that are stricter with data-centre traffic. The proxies page compares all four types.

import requests
from bs4 import BeautifulSoup

proxies = {
    "http": "http://USER:PASS@HOST:PORT",
    "https": "http://USER:PASS@HOST:PORT",
}
headers = {"User-Agent": "Mozilla/5.0 (compatible; MyScraper/1.0)"}

resp = requests.get(
    "https://example.com/products",
    headers=headers,
    proxies=proxies,
    timeout=20,
)
resp.raise_for_status()

soup = BeautifulSoup(resp.text, "lxml")
for card in soup.select("div.product-card"):
    title = card.select_one("h2")
    price = card.select_one(".price")
    if title and price:
        print(title.get_text(strip=True), price.get_text(strip=True))

The User-Agent in this example announces itself as a scraper, which is the transparent choice for a business collecting public data.

Habits for messy HTML

  • Test the result of find or select_one before reading text from it. When nothing matched you get None, and reading an attribute from that stops the script with an error.
  • When an attribute might be missing, get("href") is safer than ["href"]: get hands back None where square brackets would raise an error.
  • Prefer lxml when pages are large or have unclosed tags; it is quicker and more forgiving.
  • If a class name appears all over the page, start the selector from a nearby parent with a stable id or data attribute.

A two-minute pre-flight check

Run the endpoint through our proxy checker, and open the my IP tool through the same proxy, before the first real job. If either shows a problem, you have just saved an afternoon of confusing debugging.

From parsed rows to a report

Most analysis ends in a spreadsheet or dashboard, so collect each parsed item as a dictionary with the same keys every time. A list of those dictionaries writes straight to CSV with Python's csv module, or loads into pandas for further work. Once find, select and tree navigation feel familiar, most pages become routine to parse. Add polite request habits and dependable proxies, and you have a maintainable base for regular reporting.

Planning this for your team?

Compare the four proxy types, pick the one that fits the job and pay per GB. Credentials reach your dashboard as soon as payment clears.

Questions about your setup? Talk to our support team, available 24/7.

More reading for your team

Cheapest Proxies logo

or
Continue with Email

By continuing you agree to our Terms and Privacy Policy.

Already have an account?

New here?

Subscriber offer

10% off
your team’s next order

Enter your work email and we’ll send the code there. It applies at checkout to any proxy plan, from datacenter to residential premium.

We send the code, then an occasional offer. To stop those emails, write to support and we’ll remove your address.

Your code is ready

Copy the 10% code below and paste it into the promo code field at checkout. A copy goes to your inbox too, so you can forward it to whoever places the order.

Choose a residential plan →