Créer un Web Scraper Intelligent avec Playwright et l'API Claude en TypeScript

Équipe Noqta
Par Équipe Noqta ·

Chargement du lecteur de synthèse vocale...

Créer un Web Scraper Intelligent avec Playwright et l'API Claude en TypeScript

Les scrapers web traditionnels cassent à chaque mise à jour d'un site. Vous écrivez des sélecteurs CSS, le site modifie un nom de classe, et votre pipeline est mort. Ça vous dit quelque chose ?

Il existe une meilleure approche. Au lieu de dire à votre scraper exactement où se trouvent les données, laissez un modèle d'IA comprendre la page et extraire ce dont vous avez besoin — structuré, propre, et résistant aux changements de mise en page.

Dans ce tutoriel, vous allez construire un scraper web prêt pour la production qui combine Playwright pour l'automatisation du navigateur headless avec l'API Claude d'Anthropic pour l'extraction intelligente de données. À la fin, vous aurez un outil CLI en TypeScript capable de scraper n'importe quel site et de retourner du JSON structuré — quelle que soit la structure du HTML.

Ce que vous allez construire

Un outil CLI appelé ai-scraper qui :

  1. Navigue vers n'importe quelle URL avec un vrai navigateur headless (gère le contenu rendu par JavaScript)
  2. Capture le contenu de la page et prend optionnellement des captures d'écran
  3. Envoie le contenu à Claude avec un prompt d'extraction structuré
  4. Retourne des données JSON propres et typées
  5. Supporte la pagination et le scraping multi-pages
  6. Inclut une logique de retry et de limitation de débit

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20+ installé (vérifiez avec node --version)
  • Des bases en TypeScript (types, interfaces, async/await)
  • Une clé API Anthropic — obtenez-en une sur console.anthropic.com
  • Une familiarité de base avec le terminal
  • Environ 30 minutes de temps concentré

Étape 1 : Initialiser le projet

Créez un nouveau répertoire et initialisez le projet :

mkdir ai-scraper && cd ai-scraper
npm init -y

Installez les dépendances :

npm install playwright @anthropic-ai/sdk zod commander dotenv
npm install -D typescript @types/node tsx

Voici le rôle de chaque package :

PackageRôle
playwrightAutomatisation de navigateur headless
@anthropic-ai/sdkClient pour l'API Claude
zodValidation de schéma à l'exécution
commanderParsing des arguments CLI
dotenvChargement des variables d'environnement
tsxExécution TypeScript sans compilation

Installez les navigateurs Playwright :

npx playwright install chromium

Initialisez TypeScript :

npx tsc --init

Mettez à jour votre tsconfig.json :

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "resolveJsonModule": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Créez la structure du projet :

mkdir -p src/{extractors,utils}
touch .env src/index.ts src/scraper.ts src/ai-extractor.ts

Étape 2 : Configurer les variables d'environnement

Ajoutez votre clé API Anthropic dans .env :

ANTHROPIC_API_KEY=sk-ant-your-key-here
MAX_RETRIES=3
RATE_LIMIT_MS=1000

⚠️ Ne commitez jamais votre fichier .env. Ajoutez-le immédiatement à .gitignore.

Étape 3 : Définir les types

Créez src/types.ts — la colonne vertébrale de votre scraper type-safe :

import { z } from "zod";
 
// Schéma d'un élément extrait — personnalisez selon vos besoins
export const ScrapedItemSchema = z.object({
  title: z.string(),
  description: z.string().optional(),
  price: z.string().optional(),
  url: z.string().url().optional(),
  imageUrl: z.string().url().optional(),
  metadata: z.record(z.string()).optional(),
});
 
export type ScrapedItem = z.infer<typeof ScrapedItemSchema>;
 
// Schéma du résultat d'extraction complet
export const ExtractionResultSchema = z.object({
  items: z.array(ScrapedItemSchema),
  totalFound: z.number(),
  pageInfo: z.object({
    title: z.string(),
    url: z.string(),
    scrapedAt: z.string(),
  }),
});
 
export type ExtractionResult = z.infer<typeof ExtractionResultSchema>;
 
// Configuration du scraper
export interface ScraperConfig {
  url: string;
  prompt: string;
  schema?: z.ZodSchema;
  waitForSelector?: string;
  maxPages?: number;
  screenshot?: boolean;
  timeout?: number;
}
 
// Contenu d'une page du navigateur
export interface PageContent {
  html: string;
  text: string;
  url: string;
  title: string;
  screenshot?: Buffer;
}

Zod vous donne une validation à l'exécution. C'est crucial — la sortie de Claude est une chaîne qui devrait être du JSON, mais vous devez vérifier la structure avant de lui faire confiance.

Étape 4 : Construire la couche d'automatisation du navigateur

Créez src/scraper.ts :

import { chromium, Browser, Page } from "playwright";
import type { PageContent, ScraperConfig } from "./types";
 
export class BrowserScraper {
  private browser: Browser | null = null;
 
  async launch(): Promise<void> {
    this.browser = await chromium.launch({
      headless: true,
      args: [
        "--no-sandbox",
        "--disable-setuid-sandbox",
        "--disable-dev-shm-usage",
      ],
    });
  }
 
  async scrape(config: ScraperConfig): Promise<PageContent> {
    if (!this.browser) {
      throw new Error("Navigateur non lancé. Appelez launch() d'abord.");
    }
 
    const context = await this.browser.newContext({
      userAgent:
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " +
        "AppleWebKit/537.36 (KHTML, like Gecko) " +
        "Chrome/122.0.0.0 Safari/537.36",
      viewport: { width: 1280, height: 720 },
    });
 
    const page = await context.newPage();
 
    try {
      await page.goto(config.url, {
        waitUntil: "networkidle",
        timeout: config.timeout || 30000,
      });
 
      if (config.waitForSelector) {
        await page.waitForSelector(config.waitForSelector, {
          timeout: 10000,
        });
      }
 
      // Défilement automatique pour charger le contenu lazy
      await this.autoScroll(page);
 
      const content = await this.extractContent(page);
 
      let screenshot: Buffer | undefined;
      if (config.screenshot) {
        screenshot = await page.screenshot({
          fullPage: true,
          type: "png",
        });
      }
 
      return { ...content, screenshot };
    } finally {
      await context.close();
    }
  }
 
  private async autoScroll(page: Page): Promise<void> {
    await page.evaluate(async () => {
      await new Promise<void>((resolve) => {
        let totalHeight = 0;
        const distance = 300;
        const timer = setInterval(() => {
          const scrollHeight = document.body.scrollHeight;
          window.scrollBy(0, distance);
          totalHeight += distance;
 
          if (totalHeight >= scrollHeight) {
            clearInterval(timer);
            resolve();
          }
        }, 100);
 
        // Timeout de sécurité : arrêter le défilement après 10s
        setTimeout(() => {
          clearInterval(timer);
          resolve();
        }, 10000);
      });
    });
  }
 
  private async extractContent(
    page: Page
  ): Promise<Omit<PageContent, "screenshot">> {
    const title = await page.title();
    const url = page.url();
 
    const text = await page.evaluate(() => {
      const scripts = document.querySelectorAll(
        "script, style, noscript, iframe"
      );
      scripts.forEach((el) => el.remove());
      return document.body.innerText || "";
    });
 
    const html = await page.evaluate(() => {
      return document.body.innerHTML;
    });
 
    return { html, text, url, title };
  }
 
  async close(): Promise<void> {
    if (this.browser) {
      await this.browser.close();
      this.browser = null;
    }
  }
}

Décisions de conception clés :

  • Stratégie networkidle — garantit le chargement du contenu rendu par JavaScript
  • Défilement automatique — déclenche le chargement du contenu lazy (courant sur les sites modernes)
  • Extraction de texte propre — supprime les scripts et styles avant l'envoi à Claude
  • Isolation du contexte — chaque scrape obtient un contexte de navigateur frais

Étape 5 : Construire la couche d'extraction IA

C'est ici que la magie opère. Créez src/ai-extractor.ts :

import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import type { PageContent, ExtractionResult } from "./types";
import { ExtractionResultSchema } from "./types";
 
export class AIExtractor {
  private client: Anthropic;
  private model = "claude-sonnet-4-20250514";
 
  constructor(apiKey: string) {
    this.client = new Anthropic({ apiKey });
  }
 
  async extract(
    content: PageContent,
    userPrompt: string,
    schema?: z.ZodSchema
  ): Promise<ExtractionResult> {
    const systemPrompt = `You are a precise data extraction assistant.
Your job is to analyze web page content and extract structured data.
 
Rules:
1. Return ONLY valid JSON — no markdown, no explanations.
2. Extract ALL matching items from the page content.
3. If a field is not found, use null.
4. URLs should be absolute.
5. Clean up text: remove extra whitespace, fix encoding.
6. Be thorough — scan the entire content.`;
 
    const userMessage = `
Page URL: ${content.url}
Page Title: ${content.title}
 
--- PAGE CONTENT START ---
${this.truncateContent(content.text, 80000)}
--- PAGE CONTENT END ---
 
--- HTML STRUCTURE (first 20000 chars) ---
${this.truncateContent(content.html, 20000)}
--- HTML STRUCTURE END ---
 
EXTRACTION REQUEST: ${userPrompt}
 
Return JSON matching this structure:
{
  "items": [
    {
      "title": "string",
      "description": "string or null",
      "price": "string or null",
      "url": "absolute URL or null",
      "imageUrl": "absolute URL or null",
      "metadata": { "key": "value" }
    }
  ],
  "totalFound": number,
  "pageInfo": {
    "title": "page title",
    "url": "page url",
    "scrapedAt": "ISO date string"
  }
}`;
 
    const response = await this.client.messages.create({
      model: this.model,
      max_tokens: 4096,
      system: systemPrompt,
      messages: [{ role: "user", content: userMessage }],
    });
 
    const responseText = response.content
      .filter((block) => block.type === "text")
      .map((block) => block.text)
      .join("");
 
    return this.parseResponse(responseText, schema);
  }
 
  private parseResponse(
    text: string,
    schema?: z.ZodSchema
  ): ExtractionResult {
    let cleaned = text.trim();
 
    if (cleaned.startsWith("\`\`\`")) {
      cleaned = cleaned
        .replace(/^\`\`\`(?:json)?\n?/, "")
        .replace(/\n?\`\`\`$/, "");
    }
 
    let parsed: unknown;
    try {
      parsed = JSON.parse(cleaned);
    } catch (error) {
      throw new Error(
        `Échec du parsing de la réponse IA en JSON : ${(error as Error).message}`
      );
    }
 
    const validationSchema = schema || ExtractionResultSchema;
    const result = validationSchema.safeParse(parsed);
 
    if (!result.success) {
      throw new Error(
        `Échec de la validation de la réponse :\n${result.error.issues
          .map((i) => `  - ${i.path.join(".")}: ${i.message}`)
          .join("\n")}`
      );
    }
 
    return result.data as ExtractionResult;
  }
 
  private truncateContent(text: string, maxChars: number): string {
    if (text.length <= maxChars) return text;
    return (
      text.substring(0, maxChars) +
      `\n\n[... ${text.length - maxChars} caractères tronqués ...]`
    );
  }
}

🚀 Besoin d'aide pour implémenter une automatisation intelligente ? Noqta construit des solutions IA pour les équipes qui veulent des résultats, pas des expériences.

Points importants :

  • Troncature du contenu — Claude a une grande fenêtre de contexte, mais on reste intelligent sur l'utilisation des tokens. Le texte obtient 80K caractères, le HTML 20K pour les indices structurels.
  • Validation Zod — la réponse de l'IA est validée contre un schéma. Si Claude retourne une structure inattendue, vous obtenez une erreur claire.
  • Nettoyage de la réponse — parfois Claude enveloppe le JSON dans des blocs de code markdown malgré les instructions. On gère ça élégamment.

Étape 6 : Ajouter la logique de retry et la limitation de débit

Créez src/utils/retry.ts :

export interface RetryConfig {
  maxRetries: number;
  baseDelayMs: number;
  maxDelayMs: number;
}
 
const DEFAULT_CONFIG: RetryConfig = {
  maxRetries: 3,
  baseDelayMs: 1000,
  maxDelayMs: 10000,
};
 
export async function withRetry<T>(
  fn: () => Promise<T>,
  config: Partial<RetryConfig> = {}
): Promise<T> {
  const { maxRetries, baseDelayMs, maxDelayMs } = {
    ...DEFAULT_CONFIG,
    ...config,
  };
 
  let lastError: Error | undefined;
 
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;
      if (attempt === maxRetries) break;
 
      // Backoff exponentiel avec jitter
      const delay = Math.min(
        baseDelayMs * Math.pow(2, attempt) + Math.random() * 1000,
        maxDelayMs
      );
 
      console.warn(
        `Tentative ${attempt + 1} échouée : ${lastError.message}. ` +
        `Nouvelle tentative dans ${Math.round(delay)}ms...`
      );
 
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
 
  throw lastError;
}

Créez src/utils/rate-limiter.ts :

export class RateLimiter {
  private lastCall = 0;
 
  constructor(private minIntervalMs: number) {}
 
  async wait(): Promise<void> {
    const now = Date.now();
    const elapsed = now - this.lastCall;
 
    if (elapsed < this.minIntervalMs) {
      const waitTime = this.minIntervalMs - elapsed;
      await new Promise((resolve) => setTimeout(resolve, waitTime));
    }
 
    this.lastCall = Date.now();
  }
}

Étape 7 : Construire le scraper multi-pages

Créez src/multi-page-scraper.ts :

import { BrowserScraper } from "./scraper";
import { AIExtractor } from "./ai-extractor";
import { RateLimiter } from "./utils/rate-limiter";
import { withRetry } from "./utils/retry";
import type { ScraperConfig, ExtractionResult, ScrapedItem } from "./types";
 
export class MultiPageScraper {
  private browserScraper: BrowserScraper;
  private aiExtractor: AIExtractor;
  private rateLimiter: RateLimiter;
 
  constructor(apiKey: string, rateLimitMs = 1000) {
    this.browserScraper = new BrowserScraper();
    this.aiExtractor = new AIExtractor(apiKey);
    this.rateLimiter = new RateLimiter(rateLimitMs);
  }
 
  async scrapeMultiplePages(
    configs: ScraperConfig[]
  ): Promise<ExtractionResult> {
    await this.browserScraper.launch();
 
    const allItems: ScrapedItem[] = [];
    let totalFound = 0;
 
    try {
      for (const [index, config] of configs.entries()) {
        console.log(
          `\n📄 Extraction de la page ${index + 1}/${configs.length} : ${config.url}`
        );
 
        await this.rateLimiter.wait();
 
        const result = await withRetry(async () => {
          const content = await this.browserScraper.scrape(config);
          console.log(
            `  ✓ Page chargée (${content.text.length} caractères)`
          );
 
          const extraction = await this.aiExtractor.extract(
            content,
            config.prompt,
            config.schema
          );
          console.log(
            `  ✓ ${extraction.items.length} éléments extraits`
          );
 
          return extraction;
        });
 
        allItems.push(...result.items);
        totalFound += result.totalFound;
      }
    } finally {
      await this.browserScraper.close();
    }
 
    return {
      items: allItems,
      totalFound,
      pageInfo: {
        title: `Scraping multi-pages (${configs.length} pages)`,
        url: configs[0]?.url || "",
        scrapedAt: new Date().toISOString(),
      },
    };
  }
}

Étape 8 : Créer l'interface CLI

Créez src/index.ts :

import "dotenv/config";
import { Command } from "commander";
import { BrowserScraper } from "./scraper";
import { AIExtractor } from "./ai-extractor";
import { MultiPageScraper } from "./multi-page-scraper";
import { withRetry } from "./utils/retry";
import { writeFileSync } from "fs";
 
const program = new Command();
 
program
  .name("ai-scraper")
  .description("Web scraper intelligent avec Playwright et Claude")
  .version("1.0.0");
 
program
  .command("scrape")
  .description("Extraire les données d'une URL")
  .requiredOption("-u, --url <url>", "URL à scraper")
  .requiredOption("-p, --prompt <prompt>", "Quoi extraire")
  .option("-o, --output <file>", "Fichier JSON de sortie")
  .option("-s, --screenshot", "Prendre une capture d'écran pleine page")
  .option("--wait <selector>", "Sélecteur CSS à attendre")
  .option("--timeout <ms>", "Timeout de navigation en ms", "30000")
  .action(async (options) => {
    const apiKey = process.env.ANTHROPIC_API_KEY;
    if (!apiKey) {
      console.error("❌ ANTHROPIC_API_KEY non défini dans .env");
      process.exit(1);
    }
 
    const scraper = new BrowserScraper();
    const extractor = new AIExtractor(apiKey);
 
    try {
      console.log(`🌐 Navigation vers ${options.url}...`);
      await scraper.launch();
 
      const content = await withRetry(() =>
        scraper.scrape({
          url: options.url,
          prompt: options.prompt,
          waitForSelector: options.wait,
          screenshot: options.screenshot,
          timeout: parseInt(options.timeout),
        })
      );
 
      console.log(`📝 Page chargée : "${content.title}"`);
 
      if (content.screenshot) {
        writeFileSync("screenshot.png", content.screenshot);
        console.log(`📸 Capture d'écran sauvegardée`);
      }
 
      console.log(`\n🤖 Envoi à Claude pour extraction...`);
      const result = await withRetry(() =>
        extractor.extract(content, options.prompt)
      );
 
      console.log(`✅ ${result.items.length} éléments extraits\n`);
 
      const output = JSON.stringify(result, null, 2);
 
      if (options.output) {
        writeFileSync(options.output, output);
        console.log(`💾 Sauvegardé dans ${options.output}`);
      } else {
        console.log(output);
      }
    } catch (error) {
      console.error(`❌ Erreur : ${(error as Error).message}`);
      process.exit(1);
    } finally {
      await scraper.close();
    }
  });
 
program
  .command("multi")
  .description("Scraper plusieurs URLs")
  .requiredOption("-u, --urls <urls...>", "URLs à scraper (séparées par des espaces)")
  .requiredOption("-p, --prompt <prompt>", "Quoi extraire")
  .option("-o, --output <file>", "Fichier JSON de sortie")
  .action(async (options) => {
    const apiKey = process.env.ANTHROPIC_API_KEY;
    if (!apiKey) {
      console.error("❌ ANTHROPIC_API_KEY non défini dans .env");
      process.exit(1);
    }
 
    const multiScraper = new MultiPageScraper(
      apiKey,
      parseInt(process.env.RATE_LIMIT_MS || "1000")
    );
 
    try {
      const configs = options.urls.map((url: string) => ({
        url,
        prompt: options.prompt,
      }));
 
      const result = await multiScraper.scrapeMultiplePages(configs);
      const output = JSON.stringify(result, null, 2);
 
      if (options.output) {
        writeFileSync(options.output, output);
        console.log(
          `\n💾 ${result.items.length} éléments sauvegardés dans ${options.output}`
        );
      } else {
        console.log(output);
      }
    } catch (error) {
      console.error(`❌ Erreur : ${(error as Error).message}`);
      process.exit(1);
    }
  });
 
program.parse();

Étape 9 : Ajouter les scripts npm

Mettez à jour votre package.json :

{
  "type": "module",
  "scripts": {
    "scrape": "tsx src/index.ts scrape",
    "multi": "tsx src/index.ts multi",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

Étape 10 : Tester — Exemples concrets

Exemple 1 : Extraire des listes de produits

npx tsx src/index.ts scrape \
  -u "https://books.toscrape.com" \
  -p "Extract all book titles, prices, ratings, and availability" \
  -o books.json

Résultat attendu :

{
  "items": [
    {
      "title": "A Light in the Attic",
      "price": "£51.77",
      "metadata": {
        "rating": "Three",
        "availability": "In stock"
      }
    }
  ],
  "totalFound": 20,
  "pageInfo": {
    "title": "All products | Books to Scrape",
    "url": "https://books.toscrape.com",
    "scrapedAt": "2026-03-13T12:00:00.000Z"
  }
}

Exemple 2 : Extraire des offres d'emploi

npx tsx src/index.ts scrape \
  -u "https://news.ycombinator.com/jobs" \
  -p "Extract all job postings: company name, job title, posting date, and link" \
  -o hn-jobs.json

Exemple 3 : Scraping multi-pages

npx tsx src/index.ts multi \
  -u "https://books.toscrape.com/catalogue/page-1.html" \
     "https://books.toscrape.com/catalogue/page-2.html" \
     "https://books.toscrape.com/catalogue/page-3.html" \
  -p "Extract all book titles and prices" \
  -o all-books.json

Étape 11 : Schémas d'extraction personnalisés

La vraie puissance réside dans les schémas personnalisés. Créez src/extractors/product-extractor.ts :

import { z } from "zod";
 
export const ProductSchema = z.object({
  items: z.array(
    z.object({
      name: z.string(),
      price: z.object({
        amount: z.number(),
        currency: z.string(),
      }),
      rating: z.number().min(0).max(5).optional(),
      reviewCount: z.number().optional(),
      availability: z.enum(["in_stock", "out_of_stock", "preorder"]),
      sku: z.string().optional(),
      brand: z.string().optional(),
      category: z.string().optional(),
      url: z.string().url(),
      imageUrl: z.string().url().optional(),
    })
  ),
  totalFound: z.number(),
  pageInfo: z.object({
    title: z.string(),
    url: z.string(),
    scrapedAt: z.string(),
  }),
});
 
export type ProductResult = z.infer<typeof ProductSchema>;

Utilisez-le dans votre appel d'extraction :

import { ProductSchema } from "./extractors/product-extractor";
 
const result = await extractor.extract(content, userPrompt, ProductSchema);
// result est maintenant entièrement typé

💡 Prêt à passer de la lecture à la construction ? Discutez avec notre équipe de l'implémentation d'automatisations et d'intégrations API personnalisées pour votre entreprise.

Conseils et bonnes pratiques

✅ À faire

  • Utilisez networkidle pour les sites lourds en JavaScript
  • Validez avec Zod — ne faites jamais confiance à la sortie brute de l'IA en production
  • Limitez le débit — soyez un bon citoyen du web, ne surchargez pas les serveurs
  • Mettez en cache — sauvegardez le HTML brut avec les données extraites pour le débogage
  • Utilisez les captures d'écran pour diagnostiquer les problèmes d'extraction

⚠️ Attention

  • robots.txt — vérifiez toujours et respectez les règles de crawling
  • Conditions d'utilisation — assurez-vous que le scraping est autorisé
  • Limites de débit — pour les sites cibles ET votre API Anthropic
  • Coûts des tokens — les grandes pages signifient plus de tokens. Surveillez votre utilisation
  • Contenu dynamique — certains sites nécessitent des interactions spécifiques avant l'affichage du contenu

❌ À ne pas faire

  • Extraire des données personnelles sans consentement
  • Contourner les mécanismes d'authentification
  • Ignorer les limites de débit ou robots.txt
  • Utiliser cet outil pour le spam ou la génération de contenu sans attribution

Vue d'ensemble de l'architecture

Voici comment les composants s'articulent :

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Couche CLI  │────▶│  Playwright   │────▶│  Site cible  │
│  (Commander) │     │ (Navigateur)  │     │              │
└──────┬───────┘     └──────┬───────┘     └──────────────┘
       │                     │
       │            Contenu de page
       │              (html + texte)
       │                     │
       │              ┌──────▼───────┐     ┌──────────────┐
       │              │ Extracteur   │────▶│  Claude API  │
       │              │      IA      │     │              │
       │              └──────┬───────┘     └──────────────┘
       │                     │
       │               JSON structuré
       │             (validé par Zod)
       │                     │
       ▼                     ▼
  ┌──────────────────────────────┐
  │  Sortie (stdout ou .json)    │
  └──────────────────────────────┘

Aller plus loin

Voici quelques idées pour étendre ce projet :

  1. Couche de cache — stockez le HTML brut dans SQLite pour ré-extraire sans re-scraper
  2. Interface web — encapsulez le CLI dans une app Next.js avec un formulaire
  3. Planification — utilisez des cron jobs ou une file de tâches pour scraper périodiquement
  4. Support proxy — alternez les proxies pour le scraping à grande échelle
  5. Streaming — utilisez l'API de streaming de Claude pour un feedback en temps réel
  6. Extraction visuelle — envoyez des captures d'écran à la capacité de vision de Claude

Résumé

Vous avez construit un web scraper intelligent prêt pour la production qui :

  • Utilise Playwright pour une automatisation fiable du navigateur (gère le contenu JavaScript)
  • Exploite Claude pour une extraction intelligente sans sélecteurs CSS
  • Valide la sortie avec Zod pour la sécurité des types
  • Supporte le scraping multi-pages avec limitation de débit
  • Inclut une logique de retry avec backoff exponentiel

L'idée clé : au lieu d'écrire des sélecteurs CSS fragiles qui cassent à chaque mise à jour, vous décrivez ce que vous voulez en langage naturel et laissez l'IA trouver ça se trouve. Cette approche est plus résiliente, plus flexible, et étonnamment plus précise que le scraping traditionnel.

Le code source complet est disponible pour l'adapter à vos cas d'usage spécifiques. Bon scraping — et n'oubliez pas l'éthique !


Construire des outils d'automatisation, c'est une chose. Les construire correctement — résilients, type-safe, prêts pour la production — c'en est une autre. Chez Noqta, nous aidons les équipes à implémenter des solutions IA qui fonctionnent vraiment en production. Parlons de votre prochain projet.


Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur Vitest et React Testing Library avec Next.js 15 : Le Guide Complet des Tests Unitaires en 2026.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.

Articles connexes

Construire des Agents IA Stateful avec LangGraph.js et TypeScript

Apprenez à construire des agents IA prêts pour la production avec mémoire conversationnelle, appels d'outils et routage conditionnel en utilisant LangGraph.js et TypeScript. Des bases du graphe aux workflows d'agents multi-étapes.

22 min read·