Saltar al contenido
Guía intermedia

Guía de Procesamiento por Lotes

Escale el scraping web a miles de URL con una gestión de colas eficiente, recuperación ante errores y estrategias de optimización del rendimiento.

1. Uso de la herramienta batch_scrape

La herramienta batch_scrape obtiene hasta 50 URL de forma concurrente en una única solicitud síncrona. Cada URL vuelve con su propio estado, de modo que una página caída nunca hunde todo el lote.

Scraping por lotes básico

5 credits por URL intentada (50 URL = 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 }
  }'

2. Gestión de colas

Procese miles de URL dividiéndolas en lotes y gestionando una cola.

Estrategia de división en fragmentos

Divida listas grandes de URL en lotes manejables

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`);

3. Recuperación ante errores

Gestione los fallos con elegancia mediante lógica de reintentos y seguimiento de errores.

Gestión robusta de errores

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. Optimización del rendimiento

Maximice el rendimiento y minimice los costos con estas estrategias de optimización.

Optimice la concurrencia
Comience con batch_config.concurrency: 5. La API acepta de 1 a 10 y ejecuta como máximo 8 trabajadores
Tenga en cuenta el presupuesto de tiempo
Una solicitud descarga durante unos 20 s. Las URL que nunca llega a iniciar vuelven como skipped, y las URL omitidas no se cobran
Vuelva a leer, no a hacer scraping
Los resultados se guardan 24 h. Recórralos con get_batch_results (1 credit) en lugar de repetir el lote (5 credits por URL)
Almacene los resultados en caché
Guarde los datos extraídos en Redis o en una base de datos para evitar volver a hacer scraping de las mismas URL
Evite el exceso de lotes
No supere las 50 URL por lote; divídalas en varias solicitudes en su lugar
No ignore los límites de tasa
Respete los límites de tasa de su plan (Free: 1/s, Hobby: 2/s, Pro: 4/s, Business: 10/s)

Rendimiento esperado

EscenarioTiempoConfiguración
Lote pequeño (10 URL)~5 segundosconcurrency: 5
Lote mediano (50 URL)~15 segundosconcurrency: 8
Lote grande (500 URL)~3 minutos10 lotes × 50 URL
Lote masivo (5,000 URL)~30 minutos100 lotes × 50 URL

Próximos pasos

Continúe aprendiendo con más guías avanzadas