IP2Free

Build a Python Site Scraper That Crawls Pages and Exports Clean CSV

2025-12-21 05:27:14

Build a Python Site Scraper That Crawls Pages and Exports Clean CSV

If you want a python site scraper, you are usually trying to do more than pull a few H1 tags from a single homepage. You want a repeatable way to crawl a domain, discover internal links, extract consistent fields, and save data you can actually use in a database or spreadsheet.

What you will get in this guide:

  • A tool picker that tells you when to use standard libraries vs browser automation.
  • A site-scraper workflow (scope rules, URL queue, deduplication, and polite crawling).
  • A copy-ready Python scaffold that exports clean CSVs.
  • A troubleshooting table for the most common scraper failures (403s, loops, empty data).
  • A note on infrastructure and where LycheeIP fits for reliability.


           Let LycheeIP support your network layer


What is a python site scraper, and how is it different from a one-page scraper?


A python site scraper crawls multiple pages on a domain to extract data from the whole site, whereas a one-page scraper only fetches and parses a single specific URL.

A true "site scraper" requires crawler architecture. It does not just download; it navigates. A good mental model for this is a four-step pipeline:

  1. Fetch the page HTML (get the raw content).
  2. Parse the HTML into a searchable tree (make sense of the tags).
  3. Extract the fields you care about (pull the data).
  4. Discover next URLs and add them to a queue (find where to go next).

Note: If the website provides an official API, you should almost always prefer that over scraping. APIs are more stable, faster, and less prone to breaking when the site design changes.


How do you choose between Requests + Beautiful Soup, Scrapy, and Playwright?

Choose your tool based on how the site renders content and the scale of your project. Static pages are easiest to handle with lightweight libraries, while JavaScript-heavy sites often require a headless browser.

Use this decision matrix to pick the simplest tool that works for your target:

Your SituationBest Starting ToolWhy?
Static HTML is present in the source (View Source).Requests + Beautiful SoupFast to build, minimal overhead, easy to debug standard Python code.
You need to crawl 10k+ pages or manage complex pipelines.ScrapyBuilt-in asynchronous crawling, robust middleware, and export pipelines.
Content appears only after JavaScript runs (React/Vue/Angular).Playwright (or Selenium)Automates a real browser to render the DOM before extraction.
Data is fetched via JSON/XHR in the background.Direct HTTP CallsMost stable method; bypasses HTML parsing entirely by hitting the internal API.

Quick Test: Fetch the page with requests and print the content. If the data you need is missing from that printout, the site is likely dynamically rendered, and you will need Playwright or Selenium.


How do you inspect a website so you do not guess selectors?

Use your browser’s Developer Tools to confirm where the data lives and which HTML attributes remain stable across different pages. Guessing classes like div.wrapper-2 usually leads to broken scrapers when the site updates.

A practical inspection checklist:

  1. Elements Panel: Find the "container" that repeats (e.g., product cards, table rows).
  2. Stable Hooks: Look for data-* attributes, semantic tags (article, main), or consistent BEM-style classes.
  3. View Page Source: Right-click and "View Page Source." If the content isn't there, it is being rendered by JavaScript (Client-Side Rendering).
  4. Network Tab: Reload the page with the Network tab open and filter by "Fetch/XHR." If you see a JSON response containing your data, that is your golden ticket—scrape that endpoint directly.

Checkpoint: Before writing code, write down 2–3 CSS selectors you can explain in plain English (e.g., "The title is always inside the H1 tag within the .product-header class").


           Let LycheeIP support your network layer


How do you design crawl rules that keep you on the site and out of trouble?

Start strict. A scraper without boundaries can easily drift into irrelevant parts of a site or get stuck in infinite loops.

Core crawl rules:

  • Scope enforcement: Only crawl URLs on the specific domain you are targeting.
  • Filter traps: Ignore mailto:, tel:, # fragments, and obvious traps like calendar "next month" links that go on forever.
  • Deduplication: Maintain a seen set to ensure you never process the same URL twice.
  • Politeness: Set a time.sleep() delay between requests. Hammering a server is the fastest way to get your IP blocked.

About robots.txt: The robots.txt file defines which parts of a site crawlers are requested to avoid. While it is a voluntary standard, respecting it is a hallmark of professional scraping. Always check it to see if you are wandering into restricted directories.


How do you build the scraper scaffold in Python?

Build a small, readable scaffold first. The following example uses requests for fetching and BeautifulSoup for parsing. It implements a basic queue system to handle the "crawling" aspect.

Prerequisites:

Bash

pip install requests beautifulsoup4

The Scaffold:

Python

import csv

import time

from collections import deque

from urllib.parse import urljoin, urlparse

import requests

from bs4 import BeautifulSoup

# Configuration

START_URL = "https://example.com/blog/"

ALLOWED_DOMAIN = "example.com"

MAX_PAGES = 50

DELAY_SECONDS = 1.0

def is_allowed(url):

   """Checks if the URL is within scope and not a file/resource."""

   try:

       parsed = urlparse(url)

       # Ensure it's the right domain and http/https

       return (parsed.netloc == ALLOWED_DOMAIN or parsed.netloc == "") and parsed.scheme in ("http", "https", "")

   except:

       return False

def normalize_url(url, base_url):

   """Joins relative URLs and strips fragments."""

   full_url = urljoin(base_url, url)

   return full_url.split("#")[0]  # Remove anchor fragments

def run_scraper():

   # Use a Session for connection pooling (faster/reliable)

   session = requests.Session()

   session.headers.update({

       "User-Agent": "Mozilla/5.0 (compatible; MyPythonScraper/1.0)"

   })

   queue = deque([START_URL])

   seen = set([START_URL])

   

   # Open CSV for writing

   with open("scraped_data.csv", "w", newline="", encoding="utf-8") as f:

       writer = csv.DictWriter(f, fieldnames=["url", "title", "h1_text"])

       writer.writeheader()

       processed_count = 0

       while queue and processed_count < MAX_PAGES:

           current_url = queue.popleft()

           print(f"Crawling: {current_url}")

           try:

               response = session.get(current_url, timeout=10)

               if response.status_code != 200:

                   print(f"Failed: {response.status_code}")

                   continue

               

               # Parse

               soup = BeautifulSoup(response.text, "html.parser")

               

               # Extract Data

               data = {

                   "url": current_url,

                   "title": soup.title.string.strip() if soup.title else "N/A",

                   "h1_text": soup.h1.get_text(strip=True) if soup.h1 else "N/A"

               }

               writer.writerow(data)

               # Discover Links

               for link in soup.find_all("a", href=True):

                   next_url = normalize_url(link["href"], current_url)

                   if is_allowed(next_url) and next_url not in seen:

                       seen.add(next_url)

                       queue.append(next_url)

               processed_count += 1

               time.sleep(DELAY_SECONDS)

           except Exception as e:

               print(f"Error crawling {current_url}: {e}")

if __name__ == "__main__":

   run_scraper()

How do you extract links, images, and structured fields reliably?

Use narrow selectors and validate your data before saving it.

  • Links: Always use urljoin (from urllib.parse) to handle relative paths like /products/item1. If you simply append strings, you will break paths that use ../.
  • Images: Extract the src attribute but also grab the alt text. This is often where messy metadata lives.
  • Structured Fields: Prefer extracting from a container. Instead of soup.find_all('h2'), find the product card first: card = soup.find('div', class_='product'), then card.find('h2'). This ensures the title you grab belongs to the image you grabbed.


           Let LycheeIP support your network layer


How do you save scraped data to CSV without messy columns?

Define your schema first. A python site scraper is only as good as the data it outputs.

Rules for clean CSV exports:

  1. Fixed Headers: Define your fieldnames list upfront. Do not add columns dynamically during the crawl.
  2. UTF-8 Encoding: Always open files with encoding="utf-8". This prevents crashes when you encounter unexpected symbols or emojis.
  3. One Row, One Entity: Ensure each row represents exactly one item (a page, a product, a user). Do not mix hierarchy levels.
  4. Handle Missing Data: If a field is missing, write an empty string or "N/A". Do not let the columns shift left.


What are the most common failure modes, and how do you fix them?

Most scraper failures usually stem from blocking, dynamic rendering, or fragile code.

SymptomLikely CauseFix
403 ForbiddenUser-Agent blocking or WAF rules.Set a real User-Agent header. If persistent, check your request rate.
429 Too Many RequestsHitting the server too fast.Increase your sleep delay. Implement exponential backoff.
Extracted fields are emptySelector mismatch or JS rendering.Check DevTools. If the HTML is empty in view-source, switch to Playwright.
Infinite LoopCalendar/Filter traps.Implement a seen set (deduplication) and a MAX_DEPTH limit.
Connection Timed OutNetwork instability.Use requests.Session() and set explicit timeout= parameters.


When should you use proxies, and how does LycheeIP fit responsibly?

You should use proxies when you have a legitimate technical need, such as testing how a site renders for users in different countries (geo-testing), ensuring high availability for distributed systems, or keeping scraping traffic separate from your corporate network.

For production-grade scraping, relying on a single IP address often creates a single point of failure.

How LycheeIP fits:

  • Stable Infrastructure: LycheeIP provides static datacenter and residential proxies that help maintain consistent uptime for long-running scraper jobs.
  • Clean Pools: Because LycheeIP resources are allocated directly from operators with a cooling period, you encounter fewer "bad neighbor" issues where an IP is already flagged.
  • Global Coverage: If your python site scraper needs to verify content across 200+ regions, LycheeIP’s dynamic residential network handles the routing.

Always use proxies to enable legitimate data collection, not to bypass access controls that explicitly forbid your activity.


When should you not scrape a site at all?

Just because you can build a python site scraper does not mean you should. Avoid scraping if:

  • The Terms of Service (ToS) explicitly forbid automated collection for your use case.
  • The data contains Personally Identifiable Information (PII) (e.g., EU GDPR or California CCPA implications).
  • The content is behind a login you do not have authorization to automate.
  • Your scraper creates a heavy load that could degrade the site for real users.

Assumptions & Limitations

  • HTML Volatility: Websites change their structure frequently. Selectors that work today may break next week.
  • JavaScript Blindness: The requests library cannot see content generated by JavaScript.
  • Rate Limits: This guide assumes you are scraping politely. Aggressive scraping requires significantly more complex infrastructure.


           Let LycheeIP support your network layer


Frequently Asked Questions:

1. How do to scrape products from a website?

Start by identifying the repeating "product card" container in DevTools. Extract the name, price, and URL from within that container. Use pagination logic to visit subsequent pages and append the data to your CSV.

2. Web scraping using python code GitHub: where do I find good examples?

Search GitHub for "requests beautifulsoup scraper" or "scrapy spider example." Look for repositories that have a requirements.txt file and were updated recently. Treat these as learning references rather than production-ready code.

3. Can ChatGPT scrape websites?

ChatGPT can generate the code for a python site scraper, but it cannot execute the code to crawl the live web for you in the chat interface. You must run the generated Python code in your own local environment.

4. Is Python good for web scraping?

Yes, Python is widely considered the best language for web scraping due to its rich ecosystem of libraries like requests, BeautifulSoup, Scrapy, and Playwright, along with excellent data manipulation tools like pandas.

5. Should I check robots.txt before scraping?

Yes. While robots.txt is a voluntary protocol, checking it is standard professional practice. It tells you which paths the site owner prefers bots to avoid.

6. How do I scrape dynamic websites with Python?

If the data appears only after the page loads (Client-Side Rendering), standard requests will fail. You must use a browser automation tool like Playwright or Selenium, which loads the page in a headless browser and executes the JavaScript before extracting data.

7. How do I avoid getting blocked while scraping?

Respect the site. Use reasonable delays between requests, identify your bot with a user-agent string, and do not crawl faster than a human could reasonably browse. If you need high-volume access, verify if the site offers a paid API or enterprise data feed.

IP2free