Python Data Mining Tutorial: Extract, Clean, and Analyze Web Data with BeautifulSoup and Pandas
Python Data Mining Tutorial: Extract, Clean, and Analyze Web Data with BeautifulSoup and Pandas
Data mining is one of the most practical skills a Python developer can have. Whether you're tracking prices, aggregating news headlines, or building datasets for machine learning, the ability to pull structured data from the web and make sense of it is immediately useful.
This tutorial walks through a complete pipeline: scraping HTML content with BeautifulSoup, cleaning and structuring that data with Pandas, and running basic analysis to surface meaningful insights. By the end, you'll have a reusable pattern you can adapt to almost any data source.
Prerequisites
You'll need Python 3.8+ and the following packages:
pip install requests beautifulsoup4 pandas lxml
No prior experience with web scraping is required, but familiarity with Python functions and basic Pandas concepts will help.
Step 1: Understand the Target Page Structure
Before writing a single line of code, inspect the page you want to scrape. Open the page in your browser, right-click an element you care about, and choose Inspect. Look for consistent patterns in the HTML: class names, tag types, and nesting structures.
For this tutorial, we'll scrape a publicly available books dataset from books.toscrape.com, a site built specifically for practicing web scraping.
Step 2: Fetch the Page with Requests
Start by fetching the raw HTML:
import requests
from bs4 import BeautifulSoup
BASE_URL = "http://books.toscrape.com/catalogue/"
START_URL = "http://books.toscrape.com/catalogue/page-1.html"
def get_soup(url: str) -> BeautifulSoup:
response = requests.get(url, timeout=10)
response.raise_for_status()
return BeautifulSoup(response.text, "lxml")
Using raise_for_status() ensures you catch HTTP errors early rather than parsing an error page silently.
Step 3: Parse Book Data from the HTML
Each book on the page is contained in an <article> tag with the class product_pod. Extract the title, price, rating, and availability:
RATING_MAP = {"One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5}
def parse_books(soup: BeautifulSoup) -> list[dict]:
books = []
for article in soup.select("article.product_pod"):
title = article.h3.a["title"]
price_text = article.select_one(".price_color").text.strip()
price = float(price_text.replace("Â", "").replace("£", ""))
rating_word = article.p["class"][1]
rating = RATING_MAP.get(rating_word, 0)
availability = article.select_one(".availability").text.strip()
books.append({
"title": title,
"price": price,
"rating": rating,
"availability": availability,
})
return books
Note the price cleaning step. Scraped text often contains encoding artifacts that need to be stripped before converting to a numeric type.
Step 4: Paginate Across Multiple Pages
The site has 50 pages of books. Loop through them by detecting the "next" button:
def scrape_all_books() -> list[dict]:
all_books = []
url = START_URL
while url:
soup = get_soup(url)
all_books.extend(parse_books(soup))
next_btn = soup.select_one("li.next a")
if next_btn:
url = BASE_URL + next_btn["href"]
else:
url = None
return all_books
This pattern follow the "next" link until it disappears, works for the majority of paginated sites.
Step 5: Load Data into a Pandas DataFrame
With raw data collected, move it into Pandas for cleaning and analysis:
import pandas as pd
books = scrape_all_books()
df = pd.DataFrame(books)
print(df.shape) # (1000, 4)
print(df.dtypes)
print(df.head())
Expected output:
(1000, 4)
title object
price float64
rating int64
availability object
dtype: object
Step 6: Clean and Validate the Data
Even well-structured scrapes produce dirty data. Check for nulls, duplicates, and outliers:
# Check for missing values
print(df.isnull().sum())
# Drop duplicates if any
df = df.drop_duplicates(subset=["title"])
# Confirm availability values
print(df["availability"].value_counts())
# Filter to in-stock books only
df = df[df["availability"] == "In stock"]
In most real-world scraping projects, data cleaning takes longer than the actual scraping. Build cleaning steps as discrete, testable functions whenever possible.
Step 7: Analyze the Data
Now the useful part... surfacing patterns in the dataset.
Price Distribution by Rating
price_by_rating = df.groupby("rating")["price"].agg(["mean", "median", "count"])
print(price_by_rating.round(2))
Sample output:
mean median count
rating
1 35.47 34.44 196
2 37.82 36.99 198
3 37.24 35.00 209
4 37.80 36.57 203
5 36.50 34.61 194
Interesting: price and rating have almost no correlation on this dataset. A useful reminder that higher price doesn't mean higher quality.
Top 10 Most Affordable 5-Star Books
top_rated = df[df["rating"] == 5].sort_values("price").head(10)
print(top_rated[["title", "price"]])
Summary Statistics
print(df["price"].describe())
Step 8: Export Results
Save your cleaned dataset for reuse or further analysis:
df.to_csv("books_data.csv", index=False)
You can also export directly to Excel:
df.to_excel("books_data.xlsx", index=False)
Handling Common Scraping Issues
Rate limiting: Add a short delay between requests with time.sleep(1) to avoid hitting servers too aggressively.
User-Agent headers: Some sites block the default python-requests user agent. Pass a browser-like header:
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers, timeout=10)
Dynamic content: BeautifulSoup only parses static HTML. If data is loaded via JavaScript, you'll need Selenium or Playwright instead.
robots.txt: Always check a site's robots.txt file before scraping. Only scrape data you have a legitimate reason to access.
Extending the Pipeline
This pipeline is intentionally minimal. Here's how to extend it:
- Visualize results with Plotly Dash or Matplotlib for interactive charts
- Store data in a SQLite or PostgreSQL database instead of CSV
- Schedule scrapes with a cron job or a tool like APScheduler
- Add logging with Python's built-in
loggingmodule to track runs - Wrap it in a CLI using Click so you can pass arguments like page count or output format
Key Takeaways
- Use
requests+BeautifulSoupfor static HTML pages; switch to Playwright or Selenium for JavaScript-rendered content. - Clean data as close to the source as possible — fix encoding issues before loading into Pandas.
- The pagination loop pattern (
follow next until gone) applies to most paginated sites. - Always check
robots.txtand scrape responsibly with rate limiting. - Pandas
groupbyandaggare your best friends for quick exploratory analysis.
This pattern: fetch, parse, clean, analyze, export, forms the foundation of most data mining workflows. Once you have it down, you can apply it to almost any structured web source.