Table of contents

How can I debug HTTP requests and responses in web scraping?

Debugging HTTP requests and responses is crucial for successful web scraping. Whether you're dealing with failed requests, unexpected responses, or anti-bot measures, proper debugging techniques can save you hours of troubleshooting. This comprehensive guide covers various methods and tools to debug your web scraping HTTP communications effectively.

Understanding HTTP Request and Response Debugging

HTTP debugging involves examining the details of your requests (headers, parameters, body) and analyzing the responses (status codes, headers, content) to identify issues. Common problems include:

  • 403 Forbidden or 429 Too Many Requests errors
  • Unexpected response content or formats
  • Authentication failures
  • Rate limiting issues
  • SSL/TLS certificate problems

Browser Developer Tools

The most accessible debugging tool is your browser's developer tools. Here's how to use them effectively:

Chrome DevTools Network Tab

  1. Open DevTools (F12 or Ctrl+Shift+I)
  2. Navigate to the Network tab
  3. Visit the target website
  4. Examine the requests and responses

You can copy requests as cURL, fetch, or various programming languages to replicate them in your scraper.

Copying as cURL

curl 'https://example.com/api/data' \
  -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' \
  -H 'Accept: application/json' \
  -H 'Cookie: session_id=abc123'

Python Debugging Techniques

Using requests with debugging

import requests
import logging

# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

# Make request with session for better debugging
session = requests.Session()
response = session.get('https://example.com')

print(f"Status Code: {response.status_code}")
print(f"Headers: {response.headers}")
print(f"Content: {response.text[:500]}")

Custom debugging with requests

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

class DebugHTTPAdapter(HTTPAdapter):
    def send(self, request, **kwargs):
        print(f"Request URL: {request.url}")
        print(f"Request Headers: {dict(request.headers)}")
        if request.body:
            print(f"Request Body: {request.body}")

        response = super().send(request, **kwargs)

        print(f"Response Status: {response.status_code}")
        print(f"Response Headers: {dict(response.headers)}")
        print(f"Response Content (first 500 chars): {response.text[:500]}")

        return response

# Use the debug adapter
session = requests.Session()
session.mount('http://', DebugHTTPAdapter())
session.mount('https://', DebugHTTPAdapter())

response = session.get('https://example.com')

Using http.client for low-level debugging

import http.client
import ssl

# Enable debug output
http.client.HTTPConnection.debuglevel = 1

# Create connection
conn = http.client.HTTPSConnection('example.com')
conn.request('GET', '/')
response = conn.getresponse()

print(f"Status: {response.status}")
print(f"Headers: {response.getheaders()}")
data = response.read()
print(f"Body: {data.decode('utf-8')[:500]}")

Using httpx for modern async debugging

import httpx
import asyncio

async def debug_request():
    async with httpx.AsyncClient() as client:
        # Enable event hooks for debugging
        def log_request(request):
            print(f"Request event hook: {request.method} {request.url} - Waiting for response")

        def log_response(response):
            print(f"Response event hook: {response.status_code} {response.url}")
            print(f"Response headers: {dict(response.headers)}")

        client.event_hooks['request'] = [log_request]
        client.event_hooks['response'] = [log_response]

        response = await client.get('https://example.com')
        return response

# Run async debugging
response = asyncio.run(debug_request())

JavaScript/Node.js Debugging

Using axios with interceptors

const axios = require('axios');

// Request interceptor
axios.interceptors.request.use(request => {
    console.log('Starting Request:', {
        method: request.method,
        url: request.url,
        headers: request.headers,
        data: request.data
    });
    return request;
});

// Response interceptor
axios.interceptors.response.use(
    response => {
        console.log('Response:', {
            status: response.status,
            statusText: response.statusText,
            headers: response.headers,
            data: response.data ? response.data.toString().substring(0, 500) : 'No data'
        });
        return response;
    },
    error => {
        console.log('Response Error:', {
            message: error.message,
            status: error.response?.status,
            statusText: error.response?.statusText,
            headers: error.response?.headers,
            data: error.response?.data
        });
        return Promise.reject(error);
    }
);

// Make request
axios.get('https://example.com')
    .then(response => console.log('Success'))
    .catch(error => console.log('Error'));

Using fetch with debugging wrapper

const debugFetch = async (url, options = {}) => {
    console.log('Fetch Request:', {
        url,
        method: options.method || 'GET',
        headers: options.headers,
        body: options.body
    });

    try {
        const response = await fetch(url, options);

        console.log('Fetch Response:', {
            status: response.status,
            statusText: response.statusText,
            headers: Object.fromEntries(response.headers.entries()),
            url: response.url
        });

        // Clone response to read body without consuming it
        const clonedResponse = response.clone();
        const text = await clonedResponse.text();
        console.log('Response Body (first 500 chars):', text.substring(0, 500));

        return response;
    } catch (error) {
        console.error('Fetch Error:', error);
        throw error;
    }
};

// Usage
debugFetch('https://example.com')
    .then(response => response.text())
    .then(data => console.log('Final data length:', data.length));

Advanced Debugging with Puppeteer

When dealing with JavaScript-heavy sites, monitoring network requests in Puppeteer provides powerful debugging capabilities:

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    // Enable request interception
    await page.setRequestInterception(true);

    page.on('request', request => {
        console.log('Request:', {
            url: request.url(),
            method: request.method(),
            headers: request.headers(),
            postData: request.postData()
        });
        request.continue();
    });

    page.on('response', response => {
        console.log('Response:', {
            url: response.url(),
            status: response.status(),
            headers: response.headers()
        });
    });

    await page.goto('https://example.com');
    await browser.close();
})();

Command Line Debugging Tools

cURL for quick testing

# Basic request with verbose output
curl -v https://example.com

# Save response headers to file
curl -D headers.txt https://example.com

# Follow redirects and show headers
curl -L -I https://example.com

# Test with custom headers
curl -H "User-Agent: MyBot/1.0" -H "Accept: application/json" https://example.com/api

HTTPie for user-friendly debugging

# Install HTTPie
pip install httpie

# Basic request with pretty output
http GET https://example.com

# Request with headers
http GET https://example.com User-Agent:MyBot/1.0 Accept:application/json

# POST request with JSON data
http POST https://example.com/api name=John email=john@example.com

Proxy-Based Debugging

Using mitmproxy

# Install mitmproxy
pip install mitmproxy

# Start proxy
mitmproxy -p 8080

# Configure your scraper to use proxy

Python example with proxy:

import requests

proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080'
}

# Disable SSL verification for debugging proxy
response = requests.get('https://example.com', proxies=proxies, verify=False)

Common Debugging Scenarios

Handling Authentication Issues

import requests

session = requests.Session()

# Debug login process
login_data = {
    'username': 'your_username',
    'password': 'your_password'
}

# First, get the login page to check for CSRF tokens
login_page = session.get('https://example.com/login')
print(f"Login page cookies: {session.cookies}")

# Extract CSRF token if needed
from bs4 import BeautifulSoup
soup = BeautifulSoup(login_page.text, 'html.parser')
csrf_token = soup.find('input', {'name': 'csrf_token'})
if csrf_token:
    login_data['csrf_token'] = csrf_token['value']

# Perform login
login_response = session.post('https://example.com/login', data=login_data)
print(f"Login response status: {login_response.status_code}")
print(f"Login response cookies: {session.cookies}")

# Access protected page
protected_response = session.get('https://example.com/protected')
print(f"Protected page status: {protected_response.status_code}")

Debugging Rate Limiting

import requests
import time
from datetime import datetime

def debug_rate_limiting(url, requests_per_minute=60):
    session = requests.Session()
    request_times = []

    for i in range(100):  # Test with 100 requests
        start_time = time.time()

        try:
            response = session.get(url)
            end_time = time.time()

            request_times.append({
                'request_number': i + 1,
                'timestamp': datetime.now(),
                'status_code': response.status_code,
                'response_time': end_time - start_time,
                'rate_limit_remaining': response.headers.get('X-RateLimit-Remaining'),
                'retry_after': response.headers.get('Retry-After')
            })

            print(f"Request {i+1}: Status {response.status_code}, "
                  f"Time: {end_time - start_time:.2f}s")

            if response.status_code == 429:
                retry_after = int(response.headers.get('Retry-After', 60))
                print(f"Rate limited! Waiting {retry_after} seconds...")
                time.sleep(retry_after)

        except Exception as e:
            print(f"Request {i+1} failed: {e}")

        # Respect rate limiting
        time.sleep(60 / requests_per_minute)

    return request_times

# Usage
results = debug_rate_limiting('https://example.com/api/data')

Error Handling and Logging

Comprehensive error handling

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

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('scraper_debug.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

def robust_request(url, max_retries=3, **kwargs):
    """Make HTTP request with comprehensive error handling and debugging"""

    for attempt in range(max_retries):
        try:
            logger.info(f"Attempt {attempt + 1} for URL: {url}")

            response = requests.get(url, timeout=30, **kwargs)

            logger.info(f"Response status: {response.status_code}")
            logger.debug(f"Response headers: {dict(response.headers)}")

            # Check for successful response
            response.raise_for_status()

            return response

        except Timeout:
            logger.warning(f"Timeout on attempt {attempt + 1} for {url}")
        except ConnectionError:
            logger.warning(f"Connection error on attempt {attempt + 1} for {url}")
        except requests.HTTPError as e:
            logger.error(f"HTTP error {e.response.status_code} on attempt {attempt + 1}")
            if e.response.status_code == 429:
                # Handle rate limiting
                retry_after = int(e.response.headers.get('Retry-After', 60))
                logger.info(f"Rate limited, waiting {retry_after} seconds")
                time.sleep(retry_after)
            elif e.response.status_code in [403, 401]:
                logger.error("Authentication/authorization error, stopping retries")
                break
        except RequestException as e:
            logger.error(f"Request exception on attempt {attempt + 1}: {e}")

        if attempt < max_retries - 1:
            wait_time = 2 ** attempt  # Exponential backoff
            logger.info(f"Waiting {wait_time} seconds before retry")
            time.sleep(wait_time)

    logger.error(f"All {max_retries} attempts failed for {url}")
    return None

# Usage
response = robust_request('https://example.com/api/data')
if response:
    print("Success:", len(response.text))
else:
    print("Failed after all retries")

Best Practices for HTTP Debugging

  1. Always check response status codes before processing content
  2. Log request and response details systematically
  3. Use appropriate timeouts to avoid hanging requests
  4. Implement exponential backoff for retries
  5. Respect robots.txt and rate limits
  6. Handle different response encodings properly
  7. Use session objects for cookie persistence
  8. Monitor SSL certificate issues in production

Conclusion

Effective HTTP request and response debugging is essential for reliable web scraping. By combining browser developer tools, programming language-specific debugging techniques, and command-line utilities, you can quickly identify and resolve issues in your scrapers. Remember to implement proper error handling and logging to make debugging easier in production environments.

Whether you're using Python's requests library, JavaScript's fetch API, or tools like Puppeteer for handling AJAX requests, the debugging principles remain consistent: examine your requests, analyze responses, and implement robust error handling to build reliable web scraping applications.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon