Logo OnlineCBT
PYTHON Tutorial
PYTHON · LESSON 55

Web Scraping and Responsible Data Collection

Extract public web data while respecting policy and site limits.

Level: Beginner to Advanced
Duration: ~60 Mins Deep-Dive
Updated: 26 Jul 2026

Lesson 55: Web Scraping and Responsible Data Collection

1. Introduction to Web Scraping & Unstructured Data Ingestion

In Lesson 54, we explored querying structured REST APIs using Python's requests library. Structured web APIs return clean JSON or XML payloads designed specifically for programmatic consumption. However, vast amounts of real-world public web data—such as financial market news, e-commerce catalog pricing, real estate listings, or academic press releases—are published strictly as human-readable HTML web pages without an underlying REST API.

To harvest and ingest data from HTML pages into structured databases, enterprise data engineers use Web Scraping. Web scraping is the automated technique of downloading raw HTML documents over HTTP and parsing their structural Document Object Model (DOM) trees to extract target text, numerical metrics, and link associations.

While web scraping is an indispensable data extraction tool, it comes with crucial ethical, legal, and operational responsibilities. Aggressive or reckless scraping scripts can overload target web servers, violate Terms of Service (ToS) agreements, bypass intellectual property permissions, or trigger Denial of Service (DoS) conditions.

In this lesson, we will master the Ethical & Responsible Scraping Code of Conduct (parsing robots.txt via urllib.robotparser, rate-limiting requests, identifying client agents), parse HTML DOM trees using BeautifulSoup4 (`bs4`), leverage CSS selectors (`.select()`, `.find_all()`), extract text and attribute nodes, and route extracted HTML data streams using Structural Pattern Matching (`match-case`).


2. Ethical & Responsible Web Scraping Framework

Before writing a single line of parsing code, enterprise data collection pipelines must adhere to the four pillars of **Responsible Web Ingestion**:

  1. Respect `robots.txt` Directives: Web servers publish a standard /robots.txt configuration file defining paths that automated web crawlers and scrapers are explicitly allowed or forbidden to access.
  2. Enforce Strict Rate Limiting: Never issue automated requests in rapid unthrottled loops! Insert deliberate delays (e.g., time.sleep(1.0) or randomized interval backoffs) between HTTP requests to prevent straining target server hardware.
  3. Identify Client via Descriptive User-Agent: Never disguise scrapers behind spoofed browser signatures or anonymous headers. Include contact information in your HTTP User-Agent string so system administrators can reach out if issues arise.
  4. Prefer Official APIs When Available: If a target service offers an official REST API, always use the API over scraping raw HTML markup!

1. Programmatically Parsing `robots.txt` with `urllib.robotparser`

Python includes a standard library module—urllib.robotparser—that evaluates whether a specific URI path can be legally crawled according to a site's robots.txt file.

ROBOTS_TXT_PARSER_DEMO.PY
from urllib.robotparser import RobotFileParser
import time

def verify_scraping_permission(target_url: str, user_agent: str = "EnterpriseDataScraper/1.0") -> bool:
    """
    Parses host /robots.txt programmatically to verify crawling permissions.
    Demonstrates responsible data collection practices.
    """
    # Extracting root origin URL (e.g., https://example.com/robots.txt)
    from urllib.parse import urlparse
    parsed_uri = urlparse(target_url)
    robots_url = f"{parsed_uri.scheme}://{parsed_uri.netloc}/robots.txt"

    rp = RobotFileParser()
    rp.set_url(robots_url)
    
    try:
        rp.read()
        is_allowed = rp.can_fetch(user_agent, target_url)
        print(f"[ETHICAL CHECK] User-Agent: '{user_agent}' allowed on '{target_url}' -> {is_allowed}")
        return is_allowed
    except Exception as err:
        print(f"[ETHICAL WARNING] Could not fetch robots.txt from '{robots_url}': {err}")
        return False

# Testing Ethical Crawler Check
sample_target = "https://httpbin.org/html"
can_scrape = verify_scraping_permission(sample_target)
print("Permission Status:", "ALLOWED" if can_scrape else "DENIED")
OUTPUT
[ETHICAL CHECK] User-Agent: 'EnterpriseDataScraper/1.0' allowed on 'https://httpbin.org/html' -> True
Permission Status: ALLOWED

3. HTML Structural Foundations & DOM Trees

HTML (Hypertext Markup Language) represents web page content as a nested tree of element nodes known as the Document Object Model (DOM):

  Market Portal
  
    

Tech Earnings Report

Quarterly revenue grew by 15%...

Download PDF

1. CSS Selectors Cheat Sheet

  • HTML Tag Name
  • h2, p, a, table
  • Matches all elements of that specific HTML tag type.
  • CSS Class Name
  • .article-card, .summary
  • Matches elements declaring class="article-card".
  • Unique Element ID
  • #art-101
  • Matches the unique element declaring id="art-101".
  • Nested Child Element
  • div.article-card > h2.title
  • Matches

    directly nested inside div.

  • Attribute Specifier
  • a[href$=".pdf"]
  • Matches tags whose href attribute ends with ".pdf".
Target Element Type CSS Selector Syntax DOM Matching Target Example

4. HTML Parsing Engine: BeautifulSoup4 (`bs4`)

To extract data from HTML text markup, install the industry-standard package: pip install beautifulsoup4.

BeautifulSoup converts raw HTML text strings into a navigable, queryable Python DOM object.

1. Core Extraction Methods: `.select()`, `.find_all()`, and `.get_text()`

  • soup.find(name, attrs): Returns the **first single matching tag node** (or None).
  • soup.find_all(name, attrs): Returns a list of ALL matching tag nodes.
  • soup.select(css_selector): Executes a CSS selector query and returns a list of matching DOM elements.
  • tag.get_text(strip=True): Strips inner HTML markup tags and returns clean text content.
  • tag.get("attribute_name") or tag["href"]: Extracts attribute values (such as URL links or image source paths).
BEAUTIFULSOUP_PARSING_DEMO.PY
from bs4 import BeautifulSoup

raw_html_document = """



    


"""

# Instantiating BeautifulSoup DOM parser object
soup = BeautifulSoup(raw_html_document, "html.parser")

print("=== 1. Extracting Page Header Text ===")
h1_tag = soup.find("h1")
print("Header Title:", h1_tag.get_text(strip=True) if h1_tag else "N/A")

print("\n=== 2. Parsing List Elements via CSS Selectors (.select) ===")
# CSS Selector matching all 
  • nodes stock_nodes = soup.select("ul.stock-list > li.stock-item") scraped_records = [] for node in stock_nodes: symbol = node.get("data-symbol") company_name = node.select_one("span.company").get_text(strip=True) price_str = node.select_one("span.price").get_text(strip=True) detail_href = node.select_one("a.details-link").get("href") scraped_records.append({ "symbol": symbol, "company": company_name, "price": price_str, "url": detail_href }) print("Extracted Structured Records:") for record in scraped_records: print(f" --> [{record['symbol']}] {record['company']:<15} | Price: {record['price']:<8} | URL: {record['url']}")
  • OUTPUT
    === 1. Extracting Page Header Text ===
    Header Title: Global Financial Index
    
    === 2. Parsing List Elements via CSS Selectors (.select) ===
    Extracted Structured Records:
      --> [AAPL] Apple Inc.        | Price: $185.50  | URL: /stocks/aapl
      --> [GOOGL] Alphabet Inc.     | Price: $142.20  | URL: /stocks/googl

    5. Combining Scraping Pipelines with `requests` & Rate Limiting

    In real-world data pipelines, downloading web pages via requests and parsing them via BeautifulSoup are joined into an integrated extraction loop.

    FULL_SCRAPING_PIPELINE.PY
    import requests
    from bs4 import BeautifulSoup
    import time
    
    def scrape_public_quotes_page(target_url: str) -> list[dict]:
        """
        Downloads HTML safely with descriptive User-Agent, enforces timeout,
        and extracts quote text records using BeautifulSoup.
        """
        headers = {"User-Agent": "EnterpriseMarketScraper/1.0 (Contact: data@enterprise.corp)"}
        
        try:
            print(f"[HTTP FETCH] Downloading HTML from: '{target_url}'...")
            response = requests.get(target_url, headers=headers, timeout=5.0)
            response.raise_for_status()
            
            # Enforcing rate limiting pause to be a courteous web citizen!
            time.sleep(1.0)
            
            soup = BeautifulSoup(response.text, "html.parser")
            quote_blocks = soup.select("div.quote")
            
            extracted_data = []
            for block in quote_blocks:
                text = block.select_one("span.text").get_text(strip=True)
                author = block.select_one("small.author").get_text(strip=True)
                extracted_data.append({"quote": text, "author": author})
                
            return extracted_data
    
        except requests.exceptions.RequestException as req_err:
            print(f"[SCRAPE FAILURE] Network or HTTP error occurred: {req_err}")
            return []
    
    # Testing Pipeline against public scraping sandbox
    quotes_dataset = scrape_public_quotes_page("https://quotes.toscrape.com/page/1/")
    
    print("\n=== Scraped Data Dataset Summary ===")
    print(f"Total Quotes Extracted: {len(quotes_dataset)}")
    if quotes_dataset:
        first = quotes_dataset[0]
        print(f"Sample Quote: \"{first['quote'][:50]}...\" — {first['author']}")
    OUTPUT
    [HTTP FETCH] Downloading HTML from: 'https://quotes.toscrape.com/page/1/'...
    
    === Scraped Data Dataset Summary ===
    Total Quotes Extracted: 10
    Sample Quote: "“The world as we have created it is a process of ...” — Albert Einstein

    6. Combining Scraped DOM Streams with `match-case` Pattern Matching

    Building automated web harvesting dispatchers involves extracting scraped element attribute dictionaries or data tuples and routing them using Python 3.10+ `match-case` Structural Pattern Matching.

    MATCH_CASE_SCRAPER_DISPATCHER.PY
    def process_scraped_element_payload(item_payload: dict) -> str:
        """
        Routes scraped HTML element dictionaries using Structural Pattern Matching.
        Demonstrates evaluating extracted DOM nodes dynamically.
        """
        match item_payload:
            case {"type": "ARTICLE", "title": str(t), "author": str(a), "premium": True}:
                return f"MATCH_PREMIUM_ARTICLE: Title '{t}' by Author '{a}' requires subscription pass."
    
            case {"type": "ARTICLE", "title": str(t), "author": str(a)}:
                return f"MATCH_PUBLIC_ARTICLE: Title '{t}' by Author '{a}' queued for database ingestion."
    
            case {"type": "STOCK", "symbol": str(sym), "price": float(p)} if p >= 500.0:
                return f"MATCH_HIGH_VALUE_STOCK: High-value ticker [{sym}] trading at ${p:,.2f}."
    
            case {"type": "STOCK", "symbol": str(sym), "price": float(p)}:
                return f"MATCH_STANDARD_STOCK: Ticker [{sym}] trading at ${p:,.2f}."
    
            case {"type": "LINK", "href": str(url)} if url.endswith(".pdf"):
                return f"MATCH_PDF_DOCUMENT: Found downloadable PDF resource -> '{url}'"
    
            case _:
                return f"UNRECOGNIZED_DOM_ELEMENT: Unable to route scraped payload: {item_payload}"
    
    # Testing Dispatcher with Simulated Scraped DOM Nodes
    print("=== Pattern Matched Web Scraper Dispatcher ===")
    print(process_scraped_element_payload({
        "type": "ARTICLE", "title": "AI Market Insights", "author": "Alice Smith", "premium": True
    }))
    print(process_scraped_element_payload({
        "type": "STOCK", "symbol": "NVDA", "price": 850.00
    }))
    print(process_scraped_element_payload({
        "type": "LINK", "href": "https://corp.com/financial_audit_2026.pdf"
    }))
    OUTPUT
    === Pattern Matched Web Scraper Dispatcher ===
    MATCH_PREMIUM_ARTICLE: Title 'AI Market Insights' by Author 'Alice Smith' requires subscription pass.
    MATCH_HIGH_VALUE_STOCK: High-value ticker [NVDA] trading at $850.00.
    MATCH_PDF_DOCUMENT: Found downloadable PDF resource -> 'https://corp.com/financial_audit_2026.pdf'

    7. Frequently Asked Interview Questions with Answers

    Q1: What is `robots.txt`, and how should web scrapers respect it?
    Answer: robots.txt is a plain-text configuration file stored at a web server's root directory that specifies crawling rules for automated bots. Ethical scrapers parse this file (using tools like urllib.robotparser) to ensure they do not request prohibited directory paths or violate crawler access policies.
    Q2: What is the difference between `.find_all()` and `.select()` in BeautifulSoup4?
    Answer: .find_all("tag", attrs={...}) searches the DOM tree using tag names and attribute keyword arguments. .select("css_selector") executes standard CSS selector strings (e.g., div.card > h2.title), providing a flexible syntax for querying complex nested DOM nodes.
    Q3: Why is rate limiting (e.g., using `time.sleep()`) essential in automated web scraping scripts?
    Answer: Executing unthrottled HTTP request loops can overload target web server CPUs, consume excessive bandwidth, trigger anti-bot security blocks, or cause unintended Denial of Service (DoS) outages. Rate limiting ensures scrapers operate at a polite, sustainable pace.
    Q4: How do you extract element attribute values (like link `href` or image `src` URLs) in BeautifulSoup?
    Answer: By treating the BeautifulSoup Tag object like a Python dictionary (e.g., tag["href"] or tag.get("src")). Using .get("attr") is safer because it returns None if the attribute is absent rather than raising a KeyError.
    Q5: Why should scrapers include a custom `User-Agent` HTTP header?
    Answer: Many web servers block default generic client headers (like Python's urllib or default requests headers) to block basic spam. A descriptive User-Agent header identifies the scraping bot and provides developer contact details, establishing transparency with web administrators.

    8. Homework & Practical Assignments

    Task 1: Ethical HTML Table Extractor Task

    Create a script named ethical_scraper_task.py inside your lesson_55 folder:

    • Import requests, BeautifulSoup from bs4, and time.
    • Define function scrape_html_table(html_content: str) -> list[dict] parsing table rows () and table data cells ().
    • Enforce rate limiting using time.sleep(1.0) and attach a custom descriptive User-Agent header.
    • Extract text from table rows into a list of dictionaries and print formatted outputs using f-strings.

    Task 2: Pattern-Matched DOM Element Dispatcher

    Create a script named dom_dispatcher_task.py:

    • Build function classify_scraped_node(node_dict: dict) -> str parsing DOM nodes.
    • Write a match-case dispatcher:
      • case {"tag": "a", "href": str(link)} if "download" in link → Return "ACTION_FILE_DOWNLOAD".
      • case {"tag": "img", "src": str(img_url)} → Return "ACTION_IMAGE_INGEST".
      • case {"tag": "h1" | "h2", "text": str(heading)} → Return "ACTION_INDEX_HEADING".
      • case _ → Return "ACTION_IGNORE".
    • Execute test calls across 4 sample node dictionaries and print result strings.

    Task 3: Master Review Capstone Project — Production Enterprise Web Ingestion & Data Collection OS (`web_ingestion_os.py`)

    Create a script named web_ingestion_os.py inside your lesson_55 folder. This assignment tests and integrates **ALL concepts learned across Lessons 1 through 55**.

    Project Architectural Requirements Specification:

    1. Web Scraping & Responsible Data Collection Architecture (Lesson 55):
      • Incorporate programmatically validated robots.txt permission checks via urllib.robotparser.
      • Extract DOM nodes using BeautifulSoup4 (`bs4`) CSS selectors (`.select()`) with rate limiting (`time.sleep`) and descriptive User-Agent headers.
    2. HTTP APIs & requests Integration (Lesson 54):
      • Manage web scraping requests via requests.Session() with connection pooling, network timeouts, and raise_for_status().
    3. SQLite Persistence Integration (Lesson 53):
      • Persist scraped HTML market records into an SQLite database table using parameterized SQL queries and sqlite3.Row.
    4. Security, Performance & Memory Hardening (Lesson 52):
      • Use __slots__ across internal scraped data model classes for memory optimization.
      • Read scraping system configuration keys dynamically from environment variables using os.environ.
    5. Packaging & Distribution Integration (Lesson 51):
      • Include a programmatically generated pyproject.toml package specification string and an argparse CLI entry point structure.
    6. Clean Code, Documentation & Refactoring (Lesson 50):
      • Apply SOLID principles, DRY, and Google Style Docstrings across all functions and classes.
    7. Testing & Quality Assurance (Lessons 47-48):
      • Include unit tests verified via pytest fixtures and unittest.mock (mocking requests.get and HTML responses).
    8. Asyncio & Concurrency Integration (Lessons 45-46):
      • Incorporate thread pool workers or async event loops for parallel multi-page crawling tasks.
    9. Temporal & RegEx Text Processing (Lessons 43-44):
      • Parse UTC-aware ingestion timestamps using datetime and zoneinfo.ZoneInfo.
      • Pre-compile RegEx patterns with Named Groups for parsing scraped text elements.
    10. Type Hints, Generics & Static Safety (Lesson 42):
      • Apply explicit modern type annotations (X | Y, list[str], dict[str, Any]) throughout all functions and classes.
    11. Metaprogramming, Closures & Decorators (Lessons 40-41):
      • Build function factories and 3-level configurable decorators with @functools.wraps.
    12. Iterators & Generators (Lesson 39):
      • Implement generator functions streaming scraped record batches lazily with $O(1)$ memory.
    13. Full OOP Architecture (Lessons 32-38):
      • Define abstract base class BaseScraperEngine(ABC) and concrete Data Classes ScrapedArticleRecord and HarvestAudit.
      • Build composite manager WebIngestionOS composing database persistence drivers and web parsing handles.
    14. Observability & Fault Tolerance (Lessons 29-31):
      • Set up multi-handler logging to console and web_ingestion.log file.
      • Incorporate developer assertions (assert) verifying DOM element count bounds.
      • Define custom exception hierarchy (WebIngestionOSError, EthicalScrapingViolationError).
    15. Context Managers & Persistence Layer (Lessons 27-30):
      • Use custom class-based context managers to manage network session handles.
      • Persist JSON payload backups to scraped_db.json and export CSV reports to harvest_audit.csv using pathlib.Path.
    16. Nested Collections & Comprehensions (Lessons 21-26):
      • Maintain in-memory registry dictionaries mapping IDs to Data Class Instances.
      • Use List and Dictionary Comprehensions to clean and transform records.
    17. Advanced Function Parameters & Scope (Lessons 15-17):
      • Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and *args / **kwargs logging.
    18. Indefinite & Definite Loops (Lessons 13-14):
      • Run the interactive CLI interface inside a continuous while True menu loop.
      • Iterate through output reports using for loops with enumerate() and .items().
    19. Pattern Matching CLI Dispatcher (Lesson 12):
      • Process user CLI command tokens inside a match-case block with web harvesting match guards:
        • case ["CHECK", "ROBOTS", target_url] → Verify crawling permissions programmatically via urllib.robotparser.
        • case ["SCRAPE", "PAGE", target_url] → Download HTML, parse DOM nodes via bs4, and persist.
        • case ["AUDIT", "ELEMENTS"] → Route DOM element dictionaries using match-case pattern dispatcher.
        • case ["EXPORT", "CSV"] → Export completed harvest history to CSV.
        • case ["EXIT" | "QUIT"] → Terminate session safely using a sentinel flag.
        • case _ → Output command error message.
    20. Conditionals, Formatting & Foundations (Lessons 1-11):
      • Sanitize all inputs using .strip() and .upper().
      • Format tabular reports using f-strings with field width alignment specifiers (:<15, :>10) and clear visual borders.

    9. File & Workspace Directory Structure

    Standard Course Workspace Layout:

    Ensure all exercise files are stored inside their corresponding lesson directories:

    python_mastery_course/
    │
    ├── lesson_01/ ... lesson_54/
    │
    └── lesson_55/
        ├── robots_txt_parser_demo.py
        ├── beautifulsoup_parsing_demo.py
        ├── full_scraping_pipeline.py
        ├── match_case_scraper_dispatcher.py
        ├── ethical_scraper_task.py       <-- (Task 1)
        ├── dom_dispatcher_task.py        <-- (Task 2)
        └── web_ingestion_os.py           <-- (Task 3: Master Review Capstone)
            

    10. What We Will Learn Next

    Next Up: Lesson 56 — Automation with Files, Spreadsheets and Email

    Congratulations on completing Lesson 55! Now that you master web scraping and responsible data collection, we will explore automating office productivity workflows across local files, Excel spreadsheets, and email servers!

    In the next lesson, we will cover:

    • Automating File System Management using shutil and pathlib.
    • Reading and Writing Excel Spreadsheets using openpyxl.
    • Generating PDF Documents and CSV Reports programmatically.
    • Automating Email Notifications via SMTP (`smtplib`) and email.message.
    • Combining office automation task streams with match-case structural pattern matchers.

    📝 Live Lesson Practice

    HTML/CSS JavaScript Python C++ C PHP
    ⌨️ Practice Inputs (लाइव इनपुट भरें) (खाली होने पर RED, भरने पर GREEN underline)
    💻 Code Editor (Monaco VS Code Engine)
    👀 Live Preview
    Address Contact

    +91 7877547686

    E-mail

    onlinecbtportal@gmail.com

    Helpline Number

    +91 7877547686


    Click To Download
    Get it on Google Play

    ऐप डाउनलोड करने
    के लिए Google Play पर
    उपलब्ध है

    WhatsApp Chat