0Pricing
AI Agents · Lesson

Respectful Scraping Practices

robots.txt compliance, user agent headers, request delays, and caching.

Respectful Scraping Practices is a free AI Agents lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Agents learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Respectful Scraping Matters

Web scraping can strain servers, violate terms of service, and get your agent's IP banned. Responsible scrapers respect rate limits, identify themselves, and honor the rules websites publish.

This lesson covers the tools and techniques for scraping ethically and sustainably.

Checking robots.txt

Every website can publish a robots.txt file specifying which paths automated agents may or may not access. Python's standard library includes urllib.robotparser to parse this file.

Always check robots.txt before scraping a site.

import urllib.robotparser

rp = urllib.robotparser.RobotFileParser()
rp.set_url('https://example.com/robots.txt')
rp.read()

# Check if our bot can fetch a specific path
can_fetch = rp.can_fetch('MyAgent/1.0', 'https://example.com/public-data')
print(f'Can fetch: {can_fetch}')  # True or False

cannot_fetch = rp.can_fetch('*', 'https://example.com/private/admin')
print(f'Admin allowed: {cannot_fetch}')  # Often False

Setting a Descriptive User-Agent

The User-Agent HTTP header identifies who is making a request. An honest User-Agent lets site administrators know what your bot is and how to contact you if there are issues. Disguising as a browser to evade blocks raises ethical concerns.

import httpx

headers = {
    # Honest, descriptive User-Agent
    'User-Agent': 'MyResearchBot/1.0 (contact: me@example.com; purpose: academic research)'
}

response = httpx.get(
    'https://example.com/data',
    headers=headers,
    timeout=10.0
)

print(response.status_code)

Rate Limiting with time.sleep()

Sending hundreds of requests per second can overwhelm a server and is often treated as a denial-of-service attack. Add delays between requests using time.sleep(). Use random.uniform() to make the delay less robotic and more human-like.

import time
import random
import httpx

urls = [
    'https://example.com/page/1',
    'https://example.com/page/2',
    'https://example.com/page/3'
]

for url in urls:
    response = httpx.get(url, timeout=10.0)
    print(f'Fetched {url}: {response.status_code}')

    # Wait between 1 and 3 seconds before the next request
    delay = random.uniform(1, 3)
    print(f'Sleeping {delay:.1f}s...')
    time.sleep(delay)

Respecting Retry-After Headers

When a server responds with 429 Too Many Requests, it often includes a Retry-After header specifying how many seconds to wait. Your agent should read this and honor it rather than immediately retrying.

import time
import httpx

def fetch_with_retry(url: str) -> httpx.Response:
    while True:
        response = httpx.get(url, timeout=10.0)

        if response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', 5))
            print(f'Rate limited. Waiting {retry_after}s...')
            time.sleep(retry_after)
            continue  # retry

        response.raise_for_status()
        return response

Caching Responses to Avoid Repeat Fetches

requests-cache automatically caches HTTP responses to disk. If your agent needs to fetch the same URL multiple times (e.g., during development or re-runs), cached responses are returned instantly without hitting the server.

# pip install requests-cache
import requests_cache

# Install the cache globally — cached responses expire after 1 hour
requests_cache.install_cache(
    'agent_cache',
    backend='sqlite',
    expire_after=3600  # seconds
)

import requests

# First call: fetches from network and caches
response = requests.get('https://api.example.com/data')
print(response.from_cache)  # False

# Second call: returns cached result instantly
response2 = requests.get('https://api.example.com/data')
print(response2.from_cache)  # True

Manual File-Based Caching with httpx

If you prefer not to use requests-cache, a simple file-based cache works well. Store responses as JSON files keyed by URL hash and reload them if the file is fresh enough.

import hashlib
import json
import os
import time
import httpx

CACHE_DIR = '/tmp/agent_cache'
os.makedirs(CACHE_DIR, exist_ok=True)

def cached_get(url: str, ttl_seconds: int = 3600) -> dict:
    key = hashlib.md5(url.encode()).hexdigest()
    cache_file = os.path.join(CACHE_DIR, key + '.json')

    if os.path.exists(cache_file):
        age = time.time() - os.path.getmtime(cache_file)
        if age < ttl_seconds:
            with open(cache_file) as f:
                return json.load(f)

    response = httpx.get(url, timeout=10.0)
    response.raise_for_status()
    data = response.json()
    with open(cache_file, 'w') as f:
        json.dump(data, f)
    return data

Reading the Terms of Service

Before scraping any site, read its Terms of Service. Many explicitly forbid automated access or commercial use of their data. Violating ToS can result in legal action, not just an IP ban.

If an official API exists, always prefer it over scraping the HTML — it is faster, more stable, and legally safer.

Exponential Backoff on Errors

When a request fails, don't retry immediately. Use exponential backoff: wait 1s, then 2s, then 4s, etc. This avoids hammering a struggling server and is a professional pattern in production agents.

import time
import httpx

def fetch_with_backoff(url: str, max_retries: int = 4) -> dict:
    for attempt in range(max_retries):
        try:
            response = httpx.get(url, timeout=10.0)
            response.raise_for_status()
            return response.json()
        except (httpx.HTTPStatusError, httpx.RequestError) as e:
            if attempt == max_retries - 1:
                raise
            wait = 2 ** attempt  # 1, 2, 4, 8 seconds
            print(f'Attempt {attempt+1} failed: {e}. Retrying in {wait}s...')
            time.sleep(wait)
    return {}

Scraping Only What You Need

Minimize server load by fetching only what your agent actually needs. Avoid downloading entire pages when a small API endpoint returns the same data. Use HEAD requests to check if a resource has changed before fetching the full body.

import httpx

def has_page_changed(url: str, last_etag: str = None) -> bool:
    headers = {}
    if last_etag:
        headers['If-None-Match'] = last_etag

    response = httpx.head(url, headers=headers, timeout=5.0)

    if response.status_code == 304:  # Not Modified
        return False

    new_etag = response.headers.get('ETag')
    print(f'New ETag: {new_etag}')
    return True

Summary of Respectful Scraping Rules

A quick checklist before your agent scrapes any site:

  • Check robots.txt and honor disallow rules
  • Set an honest, descriptive User-Agent
  • Add random delays between requests (time.sleep(random.uniform(1,3)))
  • Honor Retry-After headers on 429 responses
  • Cache responses to avoid redundant fetches
  • Read the site's Terms of Service
  • Prefer official APIs over HTML scraping when available

Knowledge Check: Respectful Scraping

Test your understanding of ethical and respectful scraping practices.

Recap: Respectful Scraping Practices

You now have a complete toolkit for scraping responsibly:

  • robotparser to check what you are allowed to access
  • Descriptive User-Agent headers that identify your bot
  • time.sleep(random.uniform(1,3)) between requests
  • requests-cache or manual caching to avoid duplicate fetches
  • Exponential backoff for error handling
  • Legal awareness: always check the ToS

Responsible scraping builds a healthier web and keeps your agents running without bans.

Frequently asked questions

Is the “Respectful Scraping Practices” lesson free?

Yes — the full text of “Respectful Scraping Practices” is free to read here on the web, and the AI Agents course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Agents course, upgrade to CoddyKit PRO.

What will I learn in “Respectful Scraping Practices”?

robots.txt compliance, user agent headers, request delays, and caching. You practise AI Agents with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Agents?

No prior experience is required. AI Agents on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Respectful Scraping Practices” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Agents lesson?

Yes. Every AI Agents lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. HTTP Clients for Agents: httpx and requests
  2. Parsing HTML with BeautifulSoup
  3. Handling Pagination and Dynamic Content
  4. Respectful Scraping Practices
← Back to AI Agents