Study interactive :: Progress tools open in the Study Hub reader.

Comprehensive Web Scraping Guide

Web scraping from basics to advanced techniques, covering Requests, Beautiful Soup, Selenium, and Scrapy.

Table of Contents


Introduction to Web Scraping

What is Web Scraping?

Web scraping is the process of extracting data from websites automatically. Instead of manually copying information, you write programs to fetch and parse web pages.

Common Use Cases:

Types of Web Scraping

1. Static Web Scraping

2. Dynamic Web Scraping

3. API-Based Scraping

Ethical Considerations

Always:

  1. Check robots.txt: https://website.com/robots.txt
  2. Read Terms of Service: Ensure scraping is allowed
  3. Respect rate limits: Don't overload servers
  4. Use delays: Add time.sleep() between requests
  5. Identify yourself: Use proper User-Agent headers
  6. Respect copyright: Don't republish copyrighted content

Legal Considerations:

Advantages of Web Scraping

Disadvantages of Web Scraping

Alternatives to Web Scraping

  1. Official APIs: Best option if available
  2. RSS Feeds: For news and blog content
  3. Data Exports: Some sites offer CSV/JSON exports
  4. Third-party Services: Paid data providers
  5. Partnerships: Direct data sharing agreements

Setting Up Your Environment

Required Libraries

# Core libraries
pip install requests beautifulsoup4 lxml

# Selenium for dynamic content
pip install selenium

# Scrapy for large-scale scraping
pip install scrapy

# Additional utilities
pip install pandas openpyxl  # For data handling
pip install fake-useragent  # For user agent rotation

Installing Selenium WebDriver

Chrome WebDriver:

# Install ChromeDriver
# macOS
brew install chromedriver

# Or download from: https://chromedriver.chromium.org/
# Add to PATH

Using Selenium Manager (Automatic):

# Selenium 4.6+ automatically manages drivers
from selenium import webdriver
driver = webdriver.Chrome()  # No need to specify driver path

Primer on Web Technologies

Client-Server Architecture

Client (Browser/Scraper)  ←→  Server (Website)
     Request                    Response

How it works:

  1. Client sends HTTP request
  2. Server processes request
  3. Server sends HTTP response
  4. Client receives and displays/processes data

HTTP Request & Response

HTTP Request Structure:

GET /page.html HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0...
Accept: text/html

HTTP Response Structure:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234

<html>...</html>

HTTP Methods

Method Purpose Use Case
GET Retrieve data Fetching web pages, API data
POST Send data Form submissions, API calls
PUT Update resource API updates
DELETE Remove resource API deletions

HTTP Status Codes

Code Meaning Scraping Implication
200 OK Success, proceed
301/302 Redirect Follow redirect
404 Not Found Page doesn't exist
403 Forbidden Access denied, may need authentication
429 Too Many Requests Rate limited, slow down
500 Server Error Server issue, retry later

Web Technologies Overview

HTML: Structure of web pages

<div class="product">
  <h2>Product Name</h2>
  <p class="price">$99.99</p>
</div>

CSS: Styling (selectors used in scraping)

.product .price { color: green; }

JavaScript: Dynamic content loading

fetch('/api/data').then(response => response.json())

Understanding these helps you:


Mastering Requests Library

About Requests

The requests library is the standard Python library for making HTTP requests. It's simple, intuitive, and powerful.

Basic GET Request

import requests

# Simple GET request
resp>'https://example.com')
print(response.status_code)  # 200
print(response.text)  # HTML content
print(response.headers)  # Response headers

Working with GET Method

import requests

# GET with parameters
params = {
    'q': 'python',
    'page': 1
}
resp>'https://example.com/search', params=params)
print(response.url)  # https://example.com/search?q=python&page=1

# GET with custom headers
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept': 'text/html,application/xhtml+xml'
}
resp>'https://example.com', headers=headers)

# GET with timeout
resp>'https://example.com', timeout=10)

Working with POST Method

import requests

# POST with form data
data = {
    'username': 'user',
    'password': 'pass'
}
resp>'https://example.com/login', data=data)

# POST with JSON
js>
    'name': 'Product',
    'price': 99.99
}
resp>'https://api.example.com/products', json=json_data)

# POST with files
files = {'file': open('data.csv', 'rb')}
resp>'https://example.com/upload', files=files)

Working with PUT and DELETE Methods

import requests

# PUT request (update)
data = {'name': 'Updated Product'}
resp>'https://api.example.com/products/1', json=data)

# DELETE request
resp>'https://api.example.com/products/1')

Working with HTTP Headers

import requests
from fake_useragent import UserAgent

# Custom headers
headers = {
    'User-Agent': UserAgent().random,  # Random user agent
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.5',
    'Accept-Encoding': 'gzip, deflate',
    'Connection': 'keep-alive',
    'Referer': 'https://google.com'
}

resp>'https://example.com', headers=headers)

# View request headers sent
print(response.request.headers)

Working with Response Object

import requests

resp>'https://example.com')

# Status code
print(response.status_code)

# Headers
print(response.headers)
print(response.headers['Content-Type'])

# Content
print(response.text)  # String (for text)
print(response.content)  # Bytes (for binary)
print(response.json())  # JSON (if applicable)

# URL
print(response.url)  # Final URL (after redirects)
print(response.history)  # Redirect history

# Cookies
print(response.cookies)

Working with Public APIs

import requests
import json

# Example: GitHub API
url = 'https://api.github.com/users/octocat'
resp>

if response.status_code == 200:
    data = response.json()
    print(f"Username: {data['login']}")
    print(f"Followers: {data['followers']}")
    print(f"Repositories: {data['public_repos']}")
else:
    print(f"Error: {response.status_code}")

# Example: REST API with authentication
headers = {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json'
}
resp>'https://api.example.com/data', headers=headers)

Error Handling

import requests
from requests.exceptions import RequestException, Timeout, ConnectionError

try:
    resp>'https://example.com', timeout=5)
    response.raise_for_status()  # Raises exception for bad status codes
    print("Success!")
except Timeout:
    print("Request timed out")
except ConnectionError:
    print("Connection error")
except RequestException as e:
    print(f"Request failed: {e}")

Beautiful Soup for HTML Parsing

About Beautiful Soup

Beautiful Soup is a Python library for parsing HTML and XML documents. It creates a parse tree that makes it easy to extract data.

Why Beautiful Soup?

Creating a Soup Object

from bs4 import BeautifulSoup
import requests

# From string
html = '<html><body><p>Hello World</p></body></html>'
soup = BeautifulSoup(html, 'html.parser')

# From file
with open('page.html', 'r') as f:
    soup = BeautifulSoup(f, 'html.parser')

# From URL (using Requests)
resp>'https://example.com')
soup = BeautifulSoup(response.text, 'html.parser')

# Using different parsers
soup = BeautifulSoup(html, 'html.parser')  # Built-in (slower)
soup = BeautifulSoup(html, 'lxml')  # Fast, requires lxml
soup = BeautifulSoup(html, 'html5lib')  # Most lenient, slowest

Exploring the Soup Object

from bs4 import BeautifulSoup
import requests

resp>'https://example.com')
soup = BeautifulSoup(response.text, 'html.parser')

# Access tags
print(soup.title)  # <title>Example</title>
print(soup.title.string)  # Example
print(soup.h1)  # First <h1> tag

# Find single element
first_p = soup.find('p')
print(first_p.text)

# Find all elements
all_links = soup.find_all('a')
for link in all_links:
    print(link.get('href'))

# Find by class
products = soup.find_all('div', class_='product')

# Find by ID
header = soup.find(id='header')

# Find by attributes
images = soup.find_all('img', src=True)

# CSS selectors (powerful!)
prices = soup.select('.price')
titles = soup.select('h2.product-title')

Common Beautiful Soup Operations

from bs4 import BeautifulSoup

html = '''
<div class="product">
    <h2>Laptop</h2>
    <p class="price">$999.99</p>
    <a href="/product/1">View Details</a>
</div>
'''

soup = BeautifulSoup(html, 'html.parser')

# Get text content
product = soup.find('div', class_='product')
print(product.get_text())  # All text
print(product.get_text(strip=True))  # Text without extra whitespace

# Get attribute values
link = soup.find('a')
print(link.get('href'))  # /product/1
print(link['href'])  # Alternative syntax

# Navigate the tree
product = soup.find('div', class_='product')
title = product.find('h2')
price = product.find('p', class_='price')

# Find parent, siblings
price = soup.find('p', class_='price')
parent = price.parent
siblings = price.next_siblings

# Extract data
products = []
for div in soup.find_all('div', class_='product'):
    products.append({
        'title': div.find('h2').text,
        'price': div.find('p', class_='price').text,
        'link': div.find('a')['href']
    })

Mini Project: Scraping Product Listings

import requests
from bs4 import BeautifulSoup
import pandas as pd

def scrape_products(url):
    """Scrape product information from a webpage"""
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    
    resp headers=headers)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    products = []
    
    # Find all product containers (adjust selector based on website)
    product_divs = soup.find_all('div', class_='product-item')
    
    for product in product_divs:
        try:
            name = product.find('h3', class_='product-name').text.strip()
            price = product.find('span', class_='price').text.strip()
            rating = product.find('div', class_='rating').get('data-rating', 'N/A')
            link = product.find('a')['href']
            
            products.append({
                'name': name,
                'price': price,
                'rating': rating,
                'link': link
            })
        except AttributeError:
            continue  # Skip if element not found
    
    return products

# Example usage
url = 'https://example-store.com/products'
products = scrape_products(url)

# Convert to DataFrame
df = pd.DataFrame(products)
print(df)

# Save to CSV
df.to_csv('products.csv', index=False)

Selenium for Dynamic Content

About Selenium

Selenium is a tool for automating web browsers. It's essential for scraping JavaScript-rendered content that doesn't appear in the initial HTML.

When to Use Selenium:

Getting Started with Selenium

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

# Initialize driver
driver = webdriver.Chrome()  # Or Firefox(), Edge(), etc.

# Navigate to page
driver.get('https://example.com')

# Get page source
html = driver.page_source

# Get current URL
print(driver.current_url)

# Get page title
print(driver.title)

# Close browser
driver.quit()

Strategies for Locating Web Elements

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get('https://example.com')

# By ID
element = driver.find_element(By.ID, 'username')

# By Class Name
elements = driver.find_elements(By.CLASS_NAME, 'product')

# By Tag Name
links = driver.find_elements(By.TAG_NAME, 'a')

# By CSS Selector
price = driver.find_element(By.CSS_SELECTOR, '.price')
products = driver.find_elements(By.CSS_SELECTOR, 'div.product-item')

# By XPath
title = driver.find_element(By.XPATH, '//h1[@class="title"]')

# By Link Text
link = driver.find_element(By.LINK_TEXT, 'Click Here')

# By Partial Link Text
link = driver.find_element(By.PARTIAL_LINK_TEXT, 'Click')

Understanding XPath

XPath is a language for finding elements in XML/HTML documents.

# Absolute XPath (fragile)
element = driver.find_element(By.XPATH, '/html/body/div[1]/div[2]/p')

# Relative XPath (better)
element = driver.find_element(By.XPATH, '//p[@class="description"]')

# XPath examples
# Find by text
element = driver.find_element(By.XPATH, '//button[text()="Submit"]')

# Find by attribute
element = driver.find_element(By.XPATH, '//input[@type="text"]')

# Find by contains
element = driver.find_element(By.XPATH, '//div[contains(@class, "product")]')

# Find parent
parent = driver.find_element(By.XPATH, '//span[@class="price"]/parent::div')

# Find following sibling
next = driver.find_element(By.XPATH, '//h2/following-sibling::p')

Basic Interaction with Web Elements

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()
driver.get('https://example.com')

# Click element
button = driver.find_element(By.ID, 'submit-btn')
button.click()

# Type text
search_box = driver.find_element(By.ID, 'search')
search_box.send_keys('python')
search_box.send_keys(Keys.RETURN)  # Press Enter

# Clear text
search_box.clear()

# Get text
title = driver.find_element(By.TAG_NAME, 'h1').text

# Get attribute
link = driver.find_element(By.TAG_NAME, 'a')
href = link.get_attribute('href')

# Check if element is displayed
if button.is_displayed():
    button.click()

Working with Dropdowns

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select

driver = webdriver.Chrome()
driver.get('https://example.com')

# Select by visible text
dropdown = Select(driver.find_element(By.ID, 'country'))
dropdown.select_by_visible_text('United States')

# Select by value
dropdown.select_by_value('us')

# Select by index
dropdown.select_by_index(0)

# Get all options
opti>
for option in options:
    print(option.text)

# Get selected option
selected = dropdown.first_selected_option.text

Working with Multiselect

from selenium.webdriver.support.ui import Select

multiselect = Select(driver.find_element(By.ID, 'languages'))
multiselect.select_by_visible_text('Python')
multiselect.select_by_visible_text('JavaScript')

# Deselect
multiselect.deselect_by_visible_text('Python')
multiselect.deselect_all()

Basic Scrolling

from selenium.webdriver.common.keys import Keys

# Scroll to element
element = driver.find_element(By.ID, 'footer')
driver.execute_script("arguments[0].scrollIntoView();", element)

# Scroll by pixels
driver.execute_script("window.scrollBy(0, 1000);")

# Scroll to bottom
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

# Scroll to top
driver.execute_script("window.scrollTo(0, 0);")

# Using keyboard
driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.PAGE_DOWN)

Infinite Scrolling

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome()
driver.get('https://example.com/infinite-scroll')

last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    # Scroll to bottom
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    
    # Wait for new content
    time.sleep(2)
    
    # Calculate new height
    new_height = driver.execute_script("return document.body.scrollHeight")
    
    if new_height == last_height:
        break  # No more content
    
    last_height = new_height

# Now scrape all loaded content
products = driver.find_elements(By.CLASS_NAME, 'product')
print(f"Found {len(products)} products")

Explicit Waits

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

driver = webdriver.Chrome()
driver.get('https://example.com')

# Wait for element to be present
wait = WebDriverWait(driver, 10)
element = wait.until(EC.presence_of_element_located((By.ID, 'content')))

# Wait for element to be clickable
button = wait.until(EC.element_to_be_clickable((By.ID, 'submit')))

# Wait for element to be visible
title = wait.until(EC.visibility_of_element_located((By.TAG_NAME, 'h1')))

# Wait for text to be present
wait.until(EC.text_to_be_present_in_element((By.ID, 'status'), 'Loaded'))

Implicit Waits

from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(10)  # Wait up to 10 seconds for elements

driver.get('https://example.com')
# All find_element calls will wait up to 10 seconds
element = driver.find_element(By.ID, 'content')

Working with IFrames

from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get('https://example.com')

# Switch to iframe by index
driver.switch_to.frame(0)

# Switch to iframe by name or ID
driver.switch_to.frame('iframe-name')

# Switch to iframe by element
iframe = driver.find_element(By.TAG_NAME, 'iframe')
driver.switch_to.frame(iframe)

# Do something in iframe
element = driver.find_element(By.ID, 'content')

# Switch back to main content
driver.switch_to.default_content()

Working with Alerts

from selenium.webdriver.common.alert import Alert

driver = webdriver.Chrome()
driver.get('https://example.com')

# Wait for alert
alert = driver.switch_to.alert

# Get alert text
print(alert.text)

# Accept alert
alert.accept()

# Dismiss alert
alert.dismiss()

# Send text to prompt
alert.send_keys('input text')
alert.accept()

Best Practices & Optimization

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# Configure Chrome options
chrome_opti>
chrome_options.add_argument('--headless')  # Run without GUI
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--disable-blink-features=AutomationControlled')
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)

# Set user agent
chrome_options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)')

driver = webdriver.Chrome(options=chrome_options)

# Execute script to hide webdriver property
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
    'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'
})

Scrapy for Large-Scale Scraping

Introduction to Scrapy

Scrapy is a powerful framework for large-scale web scraping. It's faster and more efficient than Requests + Beautiful Soup for scraping multiple pages.

When to Use Scrapy:

Installation

pip install scrapy

Basic Setup & Project Structure

# Create new Scrapy project
scrapy startproject myproject

# Project structure:
# myproject/
#   scrapy.cfg
#   myproject/
#     __init__.py
#     items.py          # Define data structure
#     middlewares.py    # Custom middleware
#     pipelines.py      # Data processing pipelines
#     settings.py       # Project settings
#     spiders/          # Spiders directory
#       __init__.py

Running a Spider

# Create spider
cd myproject
scrapy genspider example example.com

# Run spider
scrapy crawl example

# Save output
scrapy crawl example -o items.json
scrapy crawl example -o items.csv

Spiders

Definition: A spider is a class that defines how to scrape a website.

Anatomy of a Spider:

import scrapy

class ExampleSpider(scrapy.Spider):
    name = 'example'  # Unique identifier
    allowed_domains = ['example.com']
    start_urls = ['https://example.com']
    
    def parse(self, response):
        # Extract data
        title = response.css('h1::text').get()
        
        # Follow links
        next_page = response.css('a.next::attr(href)').get()
        if next_page:
            yield response.follow(next_page, self.parse)
        
        # Yield items
        yield {
            'title': title,
            'url': response.url
        }

Types of Spiders

1. Spider (Basic)

import scrapy

class BasicSpider(scrapy.Spider):
    name = 'basic'
    start_urls = ['https://example.com']
    
    def parse(self, response):
        yield {'data': response.css('p::text').getall()}

2. CrawlSpider (Rule-based)

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class CrawlSpiderExample(CrawlSpider):
    name = 'crawl'
    start_urls = ['https://example.com']
    
    rules = (
        Rule(LinkExtractor(allow=r'/page/'), callback='parse_item', follow=True),
    )
    
    def parse_item(self, response):
        yield {'title': response.css('h1::text').get()}

3. XMLFeedSpider

from scrapy.spiders import XMLFeedSpider

class XMLSpider(XMLFeedSpider):
    name = 'xml'
    start_urls = ['https://example.com/feed.xml']
    iterator = 'iternodes'
    
    def parse_node(self, response, node):
        yield {'title': node.xpath('title/text()').get()}

Scrapy Shell

# Interactive shell for testing selectors
scrapy shell 'https://example.com'

# In shell:
>>> response.css('h1::text').get()
>>> response.xpath('//h1/text()').get()
>>> response.css('a::attr(href)').getall()

Scrapy Spider with Python

import scrapy

class ProductSpider(scrapy.Spider):
    name = 'products'
    start_urls = ['https://example-store.com/products']
    
    def parse(self, response):
        # Extract product links
        product_links = response.css('a.product-link::attr(href)').getall()
        
        for link in product_links:
            yield response.follow(link, self.parse_product)
        
        # Follow pagination
        next_page = response.css('a.next::attr(href)').get()
        if next_page:
            yield response.follow(next_page, self.parse)
    
    def parse_product(self, response):
        yield {
            'name': response.css('h1.product-title::text').get(),
            'price': response.css('.price::text').get(),
            'description': response.css('.description::text').getall(),
            'url': response.url
        }

Advanced Features

Custom Spider Settings

# In spider
class MySpider(scrapy.Spider):
    custom_settings = {
        'DOWNLOAD_DELAY': 2,  # 2 second delay
        'CONCURRENT_REQUESTS': 16,
        'USER_AGENT': 'MyBot 1.0'
    }

Data & Item Pipelines

# items.py
import scrapy

class ProductItem(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    url = scrapy.Field()

# pipelines.py
class PricePipeline:
    def process_item(self, item, spider):
        # Clean price
        price = item['price']
        item['price'] = float(price.replace('$', '').replace(',', ''))
        return item

class DuplicatesPipeline:
    def __init__(self):
        self.ids_seen = set()
    
    def process_item(self, item, spider):
        if item['url'] in self.ids_seen:
            raise DropItem(f"Duplicate item: {item['url']}")
        else:
            self.ids_seen.add(item['url'])
            return item

User-Agent Rotation & Proxy Usage

# settings.py
ROTATING_PROXY_LIST = [
    'proxy1:port',
    'proxy2:port',
]

DOWNLOADER_MIDDLEWARES = {
    'rotating_proxies.middlewares.RotatingProxyMiddleware': 610,
    'rotating_proxies.middlewares.BanDetectionMiddleware': 620,
}

# Or use fake-useragent
from fake_useragent import UserAgent

class RandomUserAgentMiddleware:
    def process_request(self, request, spider):
        ua = UserAgent()
        request.headers['User-Agent'] = ua.random
        return None

Login Pages

import scrapy

class LoginSpider(scrapy.Spider):
    name = 'login'
    start_urls = ['https://example.com/login']
    
    def parse(self, response):
        return scrapy.FormRequest.from_response(
            response,
            formdata={'username': 'user', 'password': 'pass'},
            callback=self.after_login
        )
    
    def after_login(self, response):
        if 'Welcome' in response.text:
            # Login successful, start scraping
            yield scrapy.Request('https://example.com/dashboard', self.parse_dashboard)
    
    def parse_dashboard(self, response):
        # Scrape protected content
        pass

Handling APIs

import scrapy
import json

class APISpider(scrapy.Spider):
    name = 'api'
    
    def start_requests(self):
        # Make API request
        yield scrapy.Request(
            'https://api.example.com/data',
            headers={'Authorization': 'Bearer TOKEN'},
            callback=self.parse_api
        )
    
    def parse_api(self, response):
        data = json.loads(response.text)
        for item in data['results']:
            yield item

Mini Project: Scrapy E-commerce Scraper

import scrapy
from scrapy.crawler import CrawlerProcess

class EcommerceSpider(scrapy.Spider):
    name = 'ecommerce'
    start_urls = ['https://example-store.com']
    
    custom_settings = {
        'DOWNLOAD_DELAY': 1,
        'RANDOMIZE_DOWNLOAD_DELAY': 0.5,
        'USER_AGENT': 'Mozilla/5.0...'
    }
    
    def parse(self, response):
        # Extract product links
        products = response.css('div.product-item')
        
        for product in products:
            yield {
                'name': product.css('h3::text').get(),
                'price': product.css('.price::text').get(),
                'rating': product.css('.rating::text').get(),
                'link': product.css('a::attr(href)').get()
            }
        
        # Pagination
        next_page = response.css('a.next-page::attr(href)').get()
        if next_page:
            yield response.follow(next_page, self.parse)

# Run spider
if __name__ == '__main__':
    process = CrawlerProcess()
    process.crawl(EcommerceSpider)
    process.start()

Handling Challenges

Understanding CAPTCHAs

CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) is used to prevent automated access.

Types:

Preventing CAPTCHAs

# Best practices to avoid CAPTCHAs:
# 1. Use delays
import time
time.sleep(2)  # Between requests

# 2. Rotate user agents
from fake_useragent import UserAgent
ua = UserAgent()
headers = {'User-Agent': ua.random}

# 3. Use proxies
proxies = {
    'http': 'http://proxy:port',
    'https': 'https://proxy:port'
}

# 4. Respect robots.txt
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('https://example.com/robots.txt')
rp.read()
if rp.can_fetch('*', 'https://example.com/page'):
    # Scrape allowed
    pass

Handling CAPTCHAs (Theory)

Options:

  1. Manual solving: Pause script, solve manually
  2. CAPTCHA solving services: 2Captcha, Anti-Captcha
  3. OCR libraries: pytesseract (for simple text CAPTCHAs)
  4. Browser automation: Selenium with manual intervention

Handling CAPTCHAs using input()

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get('https://example.com')

# Wait for user to solve CAPTCHA manually
input("Please solve the CAPTCHA and press Enter...")

# Continue scraping
c 'content').text
print(content)

Handling CAPTCHAs using pytesseract & OpenCV

from selenium import webdriver
from PIL import Image
import pytesseract
import cv2
import numpy as np

driver = webdriver.Chrome()
driver.get('https://example.com')

# Find CAPTCHA image
captcha_img = driver.find_element(By.ID, 'captcha-image')

# Take screenshot
captcha_img.screenshot('captcha.png')

# Preprocess image
img = cv2.imread('captcha.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# OCR
text = pytesseract.image_to_string(thresh)
print(f"CAPTCHA text: {text}")

# Enter CAPTCHA
driver.find_element(By.ID, 'captcha-input').send_keys(text)

Note: OCR-based CAPTCHA solving has low success rate. Consider using CAPTCHA solving services for production.


Best Practices

1. Respect Rate Limits

import time
import random

def scrape_with_delay(urls):
    for url in urls:
        resp>
        # Random delay between 1-3 seconds
        time.sleep(random.uniform(1, 3))

2. Handle Errors Gracefully

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_session():
    session = requests.Session()
    retry_strategy = Retry(
        total=3,
        backoff_factor=1,
        status_forcelist=[429, 500, 502, 503, 504]
    )
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    return session

3. Use Sessions

import requests

session = requests.Session()
session.headers.update({'User-Agent': 'MyBot'})

# Reuse session for multiple requests (faster)
for url in urls:
    resp>

4. Cache Responses

import requests_cache

session = requests_cache.CachedSession('cache', expire_after=3600)
resp>'https://example.com')  # Cached for 1 hour

5. Monitor and Log

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def scrape_with_logging(url):
    try:
        resp>
        logger.info(f"Scraped {url}: {response.status_code}")
        return response
    except Exception as e:
        logger.error(f"Error scraping {url}: {e}")
        return None

6. Data Validation

def validate_scraped_data(data):
    """Validate scraped data before saving"""
    required_fields = ['name', 'price', 'url']
    
    for field in required_fields:
        if field not in data or not data[field]:
            return False
    
    # Validate price format
    try:
        float(data['price'].replace('$', ''))
    except ValueError:
        return False
    
    return True

Real-World Projects

Project 1: Yahoo Finance Stock Data

Goal: Scrape stock prices and financial data from Yahoo Finance.

import requests
from bs4 import BeautifulSoup
import pandas as pd
import time

def scrape_stock_data(symbol):
    """Scrape stock data for a given symbol"""
    url = f'https://finance.yahoo.com/quote/{symbol}'
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    
    resp headers=headers)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # Extract data (adjust selectors based on actual page structure)
    data = {
        'symbol': symbol,
        'price': soup.find('span', {'data-field': 'regularMarketPrice'}).text,
        'change': soup.find('span', {'data-field': 'regularMarketChange'}).text,
        'volume': soup.find('span', {'data-field': 'regularMarketVolume'}).text
    }
    
    return data

# Scrape multiple stocks
symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN']
stocks_data = []

for symbol in symbols:
    data = scrape_stock_data(symbol)
    stocks_data.append(data)
    time.sleep(1)  # Be respectful

df = pd.DataFrame(stocks_data)
print(df)

Project 2: Real Estate Listings

Goal: Scrape property listings with Selenium for dynamic content.

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
import pandas as pd
import time

def scrape_real_estate(url):
    """Scrape real estate listings"""
    driver = webdriver.Chrome()
    driver.get(url)
    
    properties = []
    
    # Wait for listings to load
    wait = WebDriverWait(driver, 10)
    listings = wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, 'property-item')))
    
    for listing in listings:
        try:
            property_data = {
                'title': listing.find_element(By.CLASS_NAME, 'title').text,
                'price': listing.find_element(By.CLASS_NAME, 'price').text,
                'location': listing.find_element(By.CLASS_NAME, 'location').text,
                'bedrooms': listing.find_element(By.CLASS_NAME, 'bedrooms').text,
                'bathrooms': listing.find_element(By.CLASS_NAME, 'bathrooms').text,
                'area': listing.find_element(By.CLASS_NAME, 'area').text
            }
            properties.append(property_data)
        except Exception as e:
            print(f"Error extracting property: {e}")
            continue
    
    # Handle pagination
    try:
        next_button = driver.find_element(By.CLASS_NAME, 'next-page')
        if next_button.is_enabled():
            next_button.click()
            time.sleep(2)
            # Recursively scrape next page
            properties.extend(scrape_real_estate(driver.current_url))
    except:
        pass
    
    driver.quit()
    return properties

# Usage
url = 'https://example-realestate.com/listings'
properties = scrape_real_estate(url)
df = pd.DataFrame(properties)
df.to_csv('real_estate.csv', index=False)

Project 3: News Article Aggregator

Goal: Scrape news articles from multiple sources.

import requests
from bs4 import BeautifulSoup
import pandas as pd
from datetime import datetime

def scrape_news_article(url):
    """Scrape a single news article"""
    headers = {'User-Agent': 'Mozilla/5.0...'}
    resp headers=headers)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    article = {
        'title': soup.find('h1').text.strip(),
        'author': soup.find('span', class_='author').text if soup.find('span', class_='author') else 'Unknown',
        'date': soup.find('time').get('datetime') if soup.find('time') else None,
        'content': ' '.join([p.text for p in soup.find_all('p', class_='article-content')]),
        'url': url
    }
    
    return article

def scrape_news_site(base_url):
    """Scrape all articles from a news site"""
    headers = {'User-Agent': 'Mozilla/5.0...'}
    resp headers=headers)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    articles = []
    article_links = soup.find_all('a', class_='article-link')
    
    for link in article_links[:10]:  # Limit to 10 for demo
        article_url = link['href']
        if not article_url.startswith('http'):
            article_url = base_url + article_url
        
        article = scrape_news_article(article_url)
        articles.append(article)
        time.sleep(1)
    
    return articles

# Scrape multiple news sites
news_sites = [
    'https://news-site-1.com',
    'https://news-site-2.com'
]

all_articles = []
for site in news_sites:
    articles = scrape_news_site(site)
    all_articles.extend(articles)

df = pd.DataFrame(all_articles)
df.to_csv('news_articles.csv', index=False)

Key Takeaways

  1. Start Simple: Use Requests + Beautiful Soup for static content
  2. Use Selenium: When content is loaded by JavaScript
  3. Use Scrapy: For large-scale, multi-page scraping
  4. Be Ethical: Respect robots.txt, add delays, use proper headers
  5. Handle Errors: Implement retries and error handling
  6. Validate Data: Check data quality before saving
  7. Monitor: Log your scraping activities
  8. Legal: Always check Terms of Service

Resources


Try next: Scrape one allowed page with rate limiting and a robots.txt check. Store raw HTML before parsing.