Skip to content

Data Parsing

The core goal of a focused crawler is to extract specific data from HTML pages rather than storing entire pages. The general data-parsing workflow is:

  1. Locate the HTML element that contains the target data
  2. Extract the text content or attribute values from that element

Python offers three common parsing approaches: regular expressions, BeautifulSoup, and XPath.

Regular Expressions

Suitable for simple, fixed-pattern matching, but error-prone on complex HTML structures (the regex must be rewritten whenever the page structure changes).

import re
import requests

headers = {"User-Agent": "Mozilla/5.0"}
html = requests.get("https://example.com", headers=headers, timeout=10).text

# Extract all image URLs
pattern = r'<img[^>]+src="([^"]+)"'
img_urls = re.findall(pattern, html)
print(img_urls[:5])

BeautifulSoup (Recommended)

BeautifulSoup 4 is the most user-friendly HTML parsing library. Pairing it with the lxml parser gives better performance.

pip install beautifulsoup4 lxml

Basic Usage

import requests
from bs4 import BeautifulSoup

headers = {"User-Agent": "Mozilla/5.0"}
resp = requests.get("https://books.toscrape.com/", headers=headers, timeout=10)
resp.raise_for_status()

# lxml parser is recommended: fast and forgiving of malformed HTML
soup = BeautifulSoup(resp.text, "lxml")

Locating Elements

# Access the first matching tag directly
title = soup.title              # <title>...</title>
first_h1 = soup.h1             # First h1 on the page

# find: returns the first matching tag
tag = soup.find("div", class_="product_pod")
tag = soup.find("a", href=True)

# find_all: returns a list of all matching tags
articles = soup.find_all("article", class_="product_pod")
links = soup.find_all("a", limit=10)   # At most 10 results

# select: CSS selector (recommended, very flexible)
items = soup.select("article.product_pod")           # class selector
prices = soup.select(".price_color")                 # class
header = soup.select_one("div#page_header")          # id, returns first match
nested = soup.select("div.content > ul > li > a")    # hierarchical

Extracting Text and Attributes

for article in soup.select("article.product_pod"):
    # Text extraction
    title = article.h3.a["title"]           # Attribute value
    price = article.select_one(".price_color").get_text(strip=True)
    rating = article.p["class"][1]          # List attribute, take second element

    # tag.string: direct text (only valid when there is a single child text node)
    # tag.get_text(): combined text of all descendants (strip=True removes whitespace)
    print(f"{title}: {price} (rating: {rating})")

Full Example: Scraping a Book List

import requests
from bs4 import BeautifulSoup
from pathlib import Path

headers = {"User-Agent": "Mozilla/5.0"}
base_url = "https://books.toscrape.com/catalogue/page-{}.html"

results = []

for page in range(1, 4):   # Scrape the first 3 pages
    url = base_url.format(page) if page > 1 else "https://books.toscrape.com/"
    resp = requests.get(url, headers=headers, timeout=10)
    resp.raise_for_status()

    soup = BeautifulSoup(resp.text, "lxml")

    for article in soup.select("article.product_pod"):
        results.append({
            "title": article.h3.a["title"],
            "price": article.select_one(".price_color").get_text(strip=True),
            "availability": article.select_one(".availability").get_text(strip=True),
        })

print(f"Total books scraped: {len(results)}")
for book in results[:5]:
    print(book)

XPath (lxml)

XPath is an XML path language for locating nodes. It is highly precise on structured HTML and is widely used with Scrapy.

pip install lxml

Basic Syntax

ExpressionMeaning
//divAny div element anywhere in the document
/html/body/divAbsolute path from the root
//div[@class="box"]Attribute selector
//div[contains(@class, "item")]Fuzzy attribute match
//li[1]Index selector (1-based)
//a/text()Direct text node
//div//text()All descendant text nodes
//a/@hrefAttribute value

Usage Example

import requests
from lxml import etree

headers = {"User-Agent": "Mozilla/5.0"}
resp = requests.get("https://books.toscrape.com/", headers=headers, timeout=10)
resp.encoding = "utf-8"

# Build an etree object from the HTML string
tree = etree.HTML(resp.text)

# Global search
articles = tree.xpath('//article[@class="product_pod"]')

for article in articles:
    # Local parsing: use . to represent the current node
    title_nodes = article.xpath('.//h3/a/@title')
    price_nodes = article.xpath('.//p[@class="price_color"]/text()')

    if title_nodes and price_nodes:
        print(f"{title_nodes[0]}: {price_nodes[0].strip()}")

Parsing a Local HTML File

from lxml import etree

# Parse a local file
tree = etree.parse("local.html")   # use parse()
results = tree.xpath('//div[@class="item"]/text()')
print(results)

Comparison of the Three Approaches

ApproachProsConsBest For
RegexNo dependencies, flexibleHard to maintain with complex structuresSimple, fixed patterns
BeautifulSoupFriendly API, tolerant of bad HTMLSlightly slowerMost web-scraping tasks
XPathPrecise, good performanceMore complex syntaxStructured HTML, Scrapy
Last updated on