Prompt

How do I scrape thousands of URLs efficiently?

Latest observation

Jul 15, 2026 · OpenAI APIWeb search: off

To scrape thousands of URLs efficiently, focus on concurrency, reuse, and politeness rather than just “making requests faster.”

Best practices

1) Use asynchronous or concurrent requests

For many URLs, don’t fetch them one-by-one.

  • Python: asyncio + aiohttp
  • Node.js: built-in async I/O
  • Go: goroutines + worker pools
  • Thread pool also works for mostly I/O-bound scraping

2) Reuse connections

Use an HTTP client that supports:

  • keep-alive
  • connection pooling
  • timeouts

This avoids reconnecting for every URL.

3) Limit concurrency

More concurrency isn’t always better.

  • Start with something like 10–50 concurrent requests
  • Increase gradually
  • Watch for:
    • rate limits
    • bans / CAPTCHAs
    • memory spikes
    • server errors

4) Handle retries carefully

Use retries only for transient errors:

  • 429 Too Many Requests
  • 503 Service Unavailable
  • timeouts

Add:

  • exponential backoff
  • jitter
  • max retry count

5) Cache and deduplicate

Before fetching:

  • remove duplicate URLs
  • normalize URLs
  • skip already-processed pages
  • store results incrementally

6) Respect robots.txt and rate limits

For public websites:

  • check robots.txt
  • throttle requests per domain
  • add delays if needed
  • identify your scraper with a user-agent

7) Separate fetching from parsing

Use a pipeline:

  1. fetch URL
  2. save raw response
  3. parse later

This helps with debugging and avoids re-scraping if parsing logic changes.

8) Use a queue/worker model for scale

For very large jobs:

  • put URLs in a queue
  • have worker processes consume them
  • store results in a database or object storage

Good tools:

  • Redis queue / Celery
  • RabbitMQ
  • SQS
  • Kafka for larger systems

Example: Python with asyncio + aiohttp

import asyncio
import aiohttp

CONCURRENCY = 20

async def fetch(session, url):
    try:
        async with session.get(url, timeout=aiohttp.ClientTimeout(total=20)) as resp:
            text = await resp.text()
            return url, resp.status, text[:200]
    except Exception as e:
        return url, "error", str(e)

async def bound_fetch(sem, session, url):
    async with sem:
        return await fetch(session, url)

async def main(urls):
    sem = asyncio.Semaphore(CONCURRENCY)
    connector = aiohttp.TCPConnector(limit=CONCURRENCY, limit_per_host=5)

    async with aiohttp.ClientSession(connector=connector, headers={
        "User-Agent": "MyScraper/1.0"
    }) as session:
        tasks = [bound_fetch(sem, session, url) for url in urls]
        for task in asyncio.as_completed(tasks):
            url, status, result = await task
            print(url, status, result)

urls = ["https://example.com"] * 1000
asyncio.run(main(urls))

Practical architecture for thousands of URLs

  • Input: list of URLs from file/db
  • Normalizer: deduplicate + canonicalize
  • Fetcher: async workers with per-host limits
  • Retry layer: only on transient failures
  • Parser: extract data
  • Storage: DB/files
  • Monitoring: track status codes, latency, error rate

If pages are dynamic

If content is rendered by JavaScript:

  • try to find the underlying API first
  • otherwise use browser automation:
    • Playwright
    • Selenium

But browser automation is much slower, so use it only when needed.


Rule of thumb

If you’re scraping:

  • 1,000–50,000 URLs: async requests + worker pool
  • Dynamic sites: try API first, browser only if necessary
  • Large recurring jobs: queue-based distributed system

If you want, I can give you:

  1. a production-ready Python scraper template,
  2. a Node.js version, or
  3. a version that handles retries, rate limiting, and saving to CSV/DB.

Observation history

Unlock observation history

Create a free Obsurfable account to unlock historical responses, model comparisons, and deeper brand intelligence.

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, and Meta AI.