CrawlForge MCP
Intermediate Guide

Batch Processing Guide

Scale web scraping to thousands of URLs with efficient queue management, error recovery, and performance optimization strategies.

Using batch_scrape Tool
Queue Management
Error Recovery
Performance Optimization

1. Using batch_scrape Tool

The batch_scrape tool fetches up to 50 URLs concurrently in a single synchronous request. Every URL comes back with its own status, so one dead page never sinks the batch.

Basic Batch Scraping

5 credits per URL attempted (50 URLs = 250 credits)

Bash
curl -X POST https://crawlforge.dev/api/v1/tools/batch_scrape \
  -H "X-API-Key: cf_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      { "url": "https://example.com/page1" },
      { "url": "https://example.com/page2" },
      { "url": "https://example.com/page3" }
    ],
    "batch_config": { "concurrency": 5 }
  }'
No async mode on the REST API: batch_scrape over HTTP is synchronous - one request in, finished results out. There is no job id to poll and no webhook: batch_id is a retrieval key for get_batch_results, not a job handle. Chunk anything larger than 50 URLs as shown below. The CrawlForge MCP server can run the same 50-URL batch in the background with mode: 'async' and an optional webhook.

2. Queue Management

Process thousands of URLs by chunking them into batches and managing a queue.

Chunking Strategy

Break large URL lists into manageable batches

Typescript
// Chunk array into batches of 50
function chunkArray<T>(array: T[], size: number): T[][] {
  const chunks: T[][] = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
}

// Process all URLs in batches
async function processBatches(urls: string[]) {
  const batches = chunkArray(urls, 50); // Max 50 URLs per batch
  const allResults: any[] = [];

  for (let i = 0; i < batches.length; i++) {
    const batch = batches[i];
    console.log(`Processing batch ${i + 1}/${batches.length}...`);

    const response = await fetch('https://crawlforge.dev/api/v1/tools/batch_scrape', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.CRAWLFORGE_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        urls: batch.map(url => ({ url })),
        batch_config: { concurrency: 8 },
      }),
    });

    const data = await response.json();
    allResults.push(...data.data.results);

    // Wait between batches to respect rate limits
    if (i < batches.length - 1) {
      await new Promise(resolve => setTimeout(resolve, 2000));
    }
  }

  return allResults;
}

// Usage
const urls = [...]; // 500 URLs
const results = await processBatches(urls);
console.log(`Scraped ${results.length} total pages`);
Pro Tip: Use Redis or a database to store your queue. This allows you to resume processing if your script crashes or needs to restart.

3. Error Recovery

Handle failures gracefully with retry logic and error tracking.

Robust Error Handling

Typescript
interface BatchResult {
  successful: any[];
  failed: { url: string; error: string }[];
}

async function batchScrapeWithRetry(
  urls: string[],
  maxRetries = 3
): Promise<BatchResult> {
  const successful: any[] = [];
  const failed: { url: string; error: string }[] = [];
  let remainingUrls = [...urls];
  let retries = 0;

  while (remainingUrls.length > 0 && retries <= maxRetries) {
    try {
      const response = await fetch('https://crawlforge.dev/api/v1/tools/batch_scrape', {
        method: 'POST',
        headers: {
          'X-API-Key': process.env.CRAWLFORGE_API_KEY!,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          urls: remainingUrls.map(url => ({ url })),
          batch_config: { concurrency: 5 },
        }),
      });

      const data = await response.json();

      // Each result carries its own status: 'success' | 'failed' | 'skipped'.
      // 'skipped' means the request's time budget ran out before that URL was
      // started — those are not charged, so they are always worth retrying.
      const batchSuccessful = data.data.results.filter((r: any) => r.status === 'success');
      const batchUnfinished = data.data.results.filter((r: any) => r.status !== 'success');

      successful.push(...batchSuccessful);

      // Only retry the URLs that did not come back successful
      remainingUrls = batchUnfinished.map((r: any) => r.url);

      if (remainingUrls.length > 0) {
        console.log(`Retrying ${remainingUrls.length} failed URLs...`);
        retries++;
        await new Promise(resolve => setTimeout(resolve, 2000 * retries));
      }

    } catch (error) {
      console.error('Batch request failed:', error);
      retries++;

      if (retries > maxRetries) {
        // Mark all remaining URLs as failed
        failed.push(...remainingUrls.map(url => ({
          url,
          error: String(error)
        })));
        break;
      }

      await new Promise(resolve => setTimeout(resolve, 2000 * retries));
    }
  }

  return { successful, failed };
}

// Usage
const { successful, failed } = await batchScrapeWithRetry(urls);
console.log(`Success: ${successful.length}, Failed: ${failed.length}`);

// Save failed URLs for manual review
if (failed.length > 0) {
  fs.writeFileSync('failed-urls.json', JSON.stringify(failed, null, 2));
}

4. Performance Optimization

Maximize throughput and minimize costs with these optimization strategies.

Optimize Concurrency
Start with batch_config.concurrency: 5. The API accepts 1-10 and runs at most 8 workers
Mind the Time Budget
One request fetches for about 20s. URLs it never starts come back skipped - and skipped URLs are not charged
Re-read, Don't Re-scrape
Results are stored for 24h. Page through them with get_batch_results (1 credit) instead of repeating the batch (5 credits per URL)
Cache Results
Store scraped data in Redis/database to avoid re-scraping same URLs
Avoid Over-Batching
Don't exceed 50 URLs per batch - split into multiple requests instead
Don't Ignore Rate Limits
Respect your plan's rate limits (Free: 1/s, Hobby: 2/s, Pro: 4/s, Business: 10/s)

Expected Performance

ScenarioTimeSettings
Small Batch (10 URLs)~5 secondsconcurrency: 5
Medium Batch (50 URLs)~15 secondsconcurrency: 8
Large Batch (500 URLs)~3 minutes10 batches × 50 URLs
Massive Batch (5,000 URLs)~30 minutes100 batches × 50 URLs
Next Steps
Continue learning with more advanced guides
Credit Optimization →
Minimize costs
Stealth Techniques →
Bypass anti-bot systems

Footer

CrawlForge MCP

Enterprise web scraping for AI Agents. 29 specialized MCP tools designed for modern developers building intelligent systems.

Product

  • Features
  • Playground
  • Pricing
  • Use Cases
  • Integrations
  • Alternatives
  • Changelog

Resources

  • Getting Started
  • API Reference
  • Templates
  • Guides
  • Blog
  • Glossary
  • FAQ
  • Sitemap

Developers

  • MCP Protocol
  • Claude Desktop
  • Cursor IDE
  • LangChain
  • LlamaIndex

Company

  • About
  • Contact
  • Privacy
  • Terms
  • Acceptable Use
  • Cookies

Stay updated

Get the latest updates on new tools and features.

Built with Next.js and MCP protocol

© 2025-2026 CrawlForge. All rights reserved.