DEV Community

Cover image for Python Web Scraping Techniques That Turn Any Website Into Structured Data
Nithin Bharadwaj
Nithin Bharadwaj

Posted on

Python Web Scraping Techniques That Turn Any Website Into Structured Data

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

When I first started collecting data from websites, I quickly learned that doing it manually was impossible. Clicking through pages and copying information was slow, error-prone, and couldn't handle more than a few items. That's when I discovered web scraping with Python. It lets me automate the whole process, turning websites into structured data I can actually use. Python is perfect for this because it has clear syntax and powerful libraries designed specifically for talking to the web and processing information.

Let me share with you some of the most effective methods I use every day. I'll explain them simply, as if I were showing a friend how it works, and include real code you can try yourself.

The first thing any scraper needs to do is talk to a website. Think of it like your web browser: it sends a request and gets back a page. In Python, the requests library is my go-to tool for this. It handles all the networking so I can focus on the data.

I don't just send a single request and hope it works. Websites can be slow or busy. My code needs to be patient and polite. I build a manager that can retry failed requests, wait between calls to avoid overwhelming the server, and handle different types of errors.

import requests
import time

# A simple, robust request function
def fetch_page(url):
    headers = {'User-Agent': 'My Scraper Bot/1.0'}
    try:
        # Be polite: wait a second before asking
        time.sleep(1)
        response = requests.get(url, headers=headers, timeout=10)
        # Check if the request was successful
        response.raise_for_status()
        return response.text
    except requests.exceptions.Timeout:
        print(f"Timeout for {url}, skipping.")
        return None
    except requests.exceptions.HTTPError as err:
        print(f"HTTP error for {url}: {err}")
        return None

# Using it
html_content = fetch_page("https://books.toscrape.com/")
if html_content:
    print(f"Fetched {len(html_content)} characters of HTML.")
Enter fullscreen mode Exit fullscreen mode

Once I have the raw HTML, it's just a blob of text. I need to find the specific pieces I care about, like product names or prices. This is where parsing comes in. I use a library called BeautifulSoup. It lets me navigate the HTML structure like a tree, picking out elements by their tags, classes, or IDs.

The key is to be specific. I examine the website's structure first, often by using my browser's "Inspect Element" feature, to find reliable patterns I can target.

from bs4 import BeautifulSoup

# Parse the HTML we fetched earlier
soup = BeautifulSoup(html_content, 'html.parser')

# Let's say I want all book titles from a fictional site.
# I see each title is in an <h3> tag inside an article with class 'product_pod'
books = []
for article in soup.select('article.product_pod'):
    title_tag = article.find('h3')
    if title_tag:
        # Get the text and clean up extra whitespace
        title = title_tag.get_text(strip=True)
        books.append(title)

print(f"Found {len(books)} books: {books[:3]}...")  # Print first three
Enter fullscreen mode Exit fullscreen mode

Doing things one page at a time is slow. If I need data from hundreds of pages, I want to fetch them simultaneously. This is called asynchronous scraping. Python's asyncio and aiohttp libraries let me manage multiple network requests at once without waiting for each to finish before starting the next.

It's like having several assistants all fetching different pages for you at the same time. The main trick is controlling how many requests I make at once to avoid problems.

import aiohttp
import asyncio

async def fetch_one_page(session, url):
    async with session.get(url) as response:
        return await response.text()

async def fetch_many_pages(url_list):
    connector = aiohttp.TCPConnector(limit=10)  # Don't open more than 10 connections at once
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for url in url_list:
            task = asyncio.create_task(fetch_one_page(session, url))
            tasks.append(task)
        # Gather all the results
        all_pages = await asyncio.gather(*tasks, return_exceptions=True)
        return all_pages

# Example list of URLs
urls_to_scrape = [f"https://books.toscrape.com/catalogue/page-{i}.html" for i in range(1, 6)]
pages_content = asyncio.run(fetch_many_pages(urls_to_scrape))
print(f"Asynchronously fetched {len([c for c in pages_content if c])} pages.")
Enter fullscreen mode Exit fullscreen mode

Many websites don't want to be scraped. They use defenses to block automated bots. To work with these sites, my scraper needs to mimic a real human user. This involves changing my digital fingerprint regularly.

I rotate between different user agent strings, which identify my browser. I add realistic delays between clicks and requests. Sometimes, I even need to use proxy servers to distribute my requests from different IP addresses.

import random

class PoliteScraper:
    def __init__(self):
        self.user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
        ]

    def get_headers(self):
        return {'User-Agent': random.choice(self.user_agents)}

    def human_delay(self):
        # Wait between 1 and 3 seconds, like a person reading
        time.sleep(random.uniform(1.0, 3.0))

scraper = PoliteScraper()
headers = scraper.get_headers()
print(f"Using user agent: {headers['User-Agent']}")
scraper.human_delay()  # Pause before next action
Enter fullscreen mode Exit fullscreen mode

The data I scrape is often messy. It has extra spaces, hidden HTML characters, or inconsistent formatting. Before I can analyze it, I need to clean it up. This involves normalizing text, converting dates and prices into standard formats, and removing duplicates.

I think of this as the "data laundry" phase. It's not glamorous, but it's essential for getting accurate results.

import re

def clean_text(raw_text):
    """A basic cleaner for scraped text."""
    if not raw_text:
        return ""
    # Remove HTML tags
    clean = re.sub(r'<[^>]+>', ' ', raw_text)
    # Replace multiple spaces with one
    clean = re.sub(r'\s+', ' ', clean)
    # Remove leading/trailing whitespace
    clean = clean.strip()
    return clean

def extract_price(price_string):
    """Convert a price string to a float."""
    # Find numbers and optional decimal point
    match = re.search(r'[\d,]+\.?\d*', price_string)
    if match:
        # Remove commas used as thousand separators
        number_str = match.group().replace(',', '')
        try:
            return float(number_str)
        except ValueError:
            return None
    return None

dirty_price = "Price: $1,299.99"
clean_price = extract_price(dirty_price)
print(f"Extracted price: ${clean_price}")
Enter fullscreen mode Exit fullscreen mode

Modern websites rely heavily on JavaScript to load content. If I just fetch the initial HTML, I might miss data that appears later. To handle this, I sometimes need tools that can actually run the JavaScript, like a browser. Selenium is a popular library for this.

It's heavier and slower than simple HTTP requests, but necessary for many sites. I use it only when I absolutely have to.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# Setup a browser driver (requires ChromeDriver installed)
driver = webdriver.Chrome()

try:
    driver.get("https://a-javascript-heavy-site.com")
    # Wait for a specific dynamic element to load
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "dynamic-content"))
    )
    # Now the page is fully loaded, get the HTML
    full_html = driver.page_source
    print(f"Page with JS loaded, HTML length: {len(full_html)}")
finally:
    driver.quit()  # Always close the browser
Enter fullscreen mode Exit fullscreen mode

After collecting and cleaning data, I need to save it somewhere. For small projects, a CSV or JSON file is fine. For larger ones, I might use a database. Python makes this easy with built-in modules for files and libraries for databases.

Organization is key. I always save my data in a structured format so I can find and use it later.

import csv
import json

def save_to_csv(data_list, filename):
    if not data_list:
        return
    # Use the keys from the first dictionary as column headers
    fieldnames = data_list[0].keys()
    with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(data_list)
    print(f"Data saved to {filename}")

def save_to_json(data, filename):
    with open(filename, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=4, ensure_ascii=False)
    print(f"Data saved to {filename}")

# Example data
scraped_books = [
    {'title': 'Test Book One', 'price': 19.99},
    {'title': 'Test Book Two', 'price': 9.99},
]
save_to_csv(scraped_books, 'books.csv')
save_to_json(scraped_books, 'books.json')
Enter fullscreen mode Exit fullscreen mode

Things will go wrong. Servers go down, website structures change, or I might get blocked. Good scrapers are built to handle failure gracefully. I add logging to record what happens, and I design my code to skip problematic items instead of crashing entirely.

I always start small. I test my scraper on a single page, then a few, before scaling up. I respect the website's robots.txt file, which tells bots what they can and cannot scrape.

import logging

# Setup logging to see what's happening
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def robust_scrape_task(url):
    logger.info(f"Attempting to scrape {url}")
    try:
        # Your scraping logic here
        result = fetch_page(url)
        if result:
            logger.info(f"Successfully scraped {url}")
            return result
        else:
            logger.warning(f"Failed to scrape {url}, no content returned.")
    except Exception as e:
        logger.error(f"Unexpected error scraping {url}: {e}")
    return None

# Check robots.txt
import urllib.robotparser

rp = urllib.robotparser.RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()
if rp.can_fetch("*", "https://example.com/data-page"):
    print("Scraping is allowed according to robots.txt.")
else:
    print("Scraping is disallowed by robots.txt.")
Enter fullscreen mode Exit fullscreen mode

Putting it all together, a real-world scraper combines these techniques. I start with a plan: what data do I need, and from where? I write code to fetch pages politely, parse the information, clean it, and save it. I make sure it can run for a long time without breaking.

The most important lesson I've learned is to be respectful. I scrape at a reasonable speed, I don't hammer servers, and I use data ethically. This approach has helped me build systems that gather information reliably for analysis, research, and business intelligence.

Web scraping with Python is a powerful skill. It turns the vast internet into a source of structured data. By mastering these fundamental techniques—managing requests, parsing HTML, working asynchronously, evading basic blocks, cleaning data, handling JavaScript, storing results, and planning for errors—you can automate data collection for almost any need. Start simple, be patient, and build up your code piece by piece.

📘 Checkout my latest ebook for free on my channel!

Be sure to like, share, comment, and subscribe to the channel!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | Java Elite Dev | Golang Elite Dev | Python Elite Dev | JS Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Top comments (0)