En esta página
LangGraph es el framework de LangChain para construir agentes de IA con estado basados en grafos. Al integrar las herramientas de CrawlForge como nodos del grafo, puedes construir agentes que toman decisiones inteligentes sobre qué hacer scraping, cuándo profundizar y cómo sintetizar datos web a lo largo de múltiples pasos.
Esta guía te muestra cómo construir un agente de scraping completo con LangGraph y CrawlForge en TypeScript.
Tabla de contenidos
- ¿Qué es LangGraph?
- Requisitos previos
- Paso 1: configuración del proyecto
- Paso 2: definir herramientas de CrawlForge para LangGraph
- Paso 3: diseñar el estado del agente
- Paso 4: construir los nodos del grafo
- Paso 5: conectar el grafo
- Paso 6: ejecutar el agente
- Referencia de costes en credits
- LangGraph frente a LangChain directo para scraping
- Próximos pasos
¿Qué es LangGraph?
LangGraph es un framework de orquestación de bajo nivel para construir agentes de IA fiables. A diferencia de las arquitecturas simples basadas en cadenas, LangGraph modela la lógica del agente como un grafo dirigido donde:
- Los nodos representan acciones (llamadas a herramientas, invocaciones del LLM, procesamiento de datos)
- Las aristas definen las transiciones entre nodos, incluido el enrutamiento condicional
- El estado persiste durante toda la ejecución del grafo
Esta arquitectura es ideal para los agentes de scraping porque el web scraping implica decisiones de forma inherente: ¿Debería profundizar más? ¿Está bloqueada esta página? ¿Necesito cambiar al modo sigiloso? LangGraph te permite modelar estas decisiones como aristas condicionales en un grafo.
Requisitos previos
- Node.js 18+ y TypeScript 5+
- Una cuenta de CrawlForge con una API key (1.000 credits gratuitos)
- Familiaridad con los conceptos básicos de LangChain
Paso 1: configuración del proyecto
mkdir langgraph-scraper && cd langgraph-scraper
npm init -y
npm install @langchain/langgraph @langchain/anthropic @langchain/core zod dotenv
npm install -D typescript @types/node tsxCrea tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"outDir": "dist"
},
"include": ["src/**/*"]
}Añade tus API keys a .env:
CRAWLFORGE_API_KEY=cf_live_your_key_here
ANTHROPIC_API_KEY=sk-ant-your_key_herePaso 2: definir herramientas de CrawlForge para LangGraph
Crea wrappers de herramientas tipados que LangGraph pueda invocar:
// src/tools.ts
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
const CRAWLFORGE_API = 'https://crawlforge.dev/api/v1/tools';
async function callCrawlForge(
endpoint: string,
params: Record<string, unknown>
): Promise<string> {
const response = await fetch(`${CRAWLFORGE_API}/${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.CRAWLFORGE_API_KEY}`,
},
body: JSON.stringify(params),
});
if (!response.ok) {
return JSON.stringify({ error: `HTTP ${response.status}`, endpoint });
}
const data = await response.json();
return JSON.stringify(data);
}
export const searchWebTool = tool(
async ({ query, limit }) => {
return callCrawlForge('search_web', { query, limit });
},
{
name: 'search_web',
description: 'Search the web for information. Costs 5 credits. Use when you need to find URLs for a topic.',
schema: z.object({
query: z.string().describe('Search query'),
limit: z.number().default(5).describe('Max results'),
}),
}
);
export const extractContentTool = tool(
async ({ url }) => {
return callCrawlForge('extract_content', { url });
},
{
name: 'extract_content',
description: 'Extract clean readable content from a URL. Costs 2 credits.',
schema: z.object({
url: z.string().describe('URL to extract content from'),
}),
}
);
export const scrapeStructuredTool = tool(
async ({ url, selectors }) => {
return callCrawlForge('scrape_structured', { url, selectors });
},
{
name: 'scrape_structured',
description: 'Extract structured data using CSS selectors. Costs 2 credits.',
schema: z.object({
url: z.string().describe('URL to scrape'),
selectors: z.record(z.string()).describe('CSS selectors map'),
}),
}
);
export const fetchUrlTool = tool(
async ({ url }) => {
return callCrawlForge('fetch_url', { url });
},
{
name: 'fetch_url',
description: 'Fetch raw HTML from a URL. Cheapest option at 1 credit.',
schema: z.object({
url: z.string().describe('URL to fetch'),
}),
}
);
export const allTools = [
searchWebTool,
extractContentTool,
scrapeStructuredTool,
fetchUrlTool,
];Paso 3: diseñar el estado del agente
Los agentes de LangGraph mantienen el estado durante la ejecución del grafo. Define una forma de estado que rastree el progreso del scraping:
// src/state.ts
import { BaseMessage } from '@langchain/core/messages';
import { Annotation } from '@langchain/langgraph';
// Define the graph state
export const AgentState = Annotation.Root({
// Conversation messages (LLM context)
messages: Annotation<BaseMessage[]>({
reducer: (prev, next) => [...prev, ...next],
default: () => [],
}),
// URLs discovered during research
discoveredUrls: Annotation<string[]>({
reducer: (prev, next) => [...new Set([...prev, ...next])],
default: () => [],
}),
// Content extracted from URLs
extractedContent: Annotation<Record<string, string>>({
reducer: (prev, next) => ({ ...prev, ...next }),
default: () => ({}),
}),
// Total credits consumed
creditsUsed: Annotation<number>({
reducer: (prev, next) => prev + next,
default: () => 0,
}),
// Current phase of the scraping pipeline
phase: Annotation<'search' | 'extract' | 'analyze' | 'complete'>({
reducer: (_prev, next) => next,
default: () => 'search' as const,
}),
});Paso 4: construir los nodos del grafo
Cada nodo del grafo realiza una acción específica y actualiza el estado:
// src/nodes.ts
import { ChatAnthropic } from '@langchain/anthropic';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
import { ToolNode } from '@langchain/langgraph/prebuilt';
import { AgentState } from './state';
import { allTools } from './tools';
const model = new ChatAnthropic({
model: 'claude-sonnet-4-20250514',
temperature: 0,
}).bindTools(allTools);
// Node: LLM decides which tool to call next
export async function agentNode(
state: typeof AgentState.State
) {
const systemPrompt = new SystemMessage(
`You are a web research agent. Your goal is to find and extract information efficiently.
Always prefer cheaper tools: fetch_url (1cr) > extract_content (2cr) > search_web (5cr).
Track credits used. Stop when you have enough information or reach 20 credits.`
);
const response = await model.invoke([systemPrompt, ...state.messages]);
return { messages: [response] };
}
// Node: Execute tool calls
export const toolNode = new ToolNode(allTools);
// Node: Analyze extracted content and decide next step
export async function analyzeNode(
state: typeof AgentState.State
) {
const extractedCount = Object.keys(state.extractedContent).length;
if (extractedCount >= 3 || state.creditsUsed >= 20) {
return { phase: 'complete' as const };
}
return { phase: 'extract' as const };
}Paso 5: conectar el grafo
Conecta los nodos con aristas y enrutamiento condicional:
// src/graph.ts
import { StateGraph, END } from '@langchain/langgraph';
import { AgentState } from './state';
import { agentNode, toolNode, analyzeNode } from './nodes';
import { AIMessage } from '@langchain/core/messages';
// Determine if the agent wants to use a tool or is finished
function shouldContinue(state: typeof AgentState.State) {
const lastMessage = state.messages[state.messages.length - 1];
// If the LLM returned tool calls, route to tool execution
if (
lastMessage instanceof AIMessage &&
lastMessage.tool_calls &&
lastMessage.tool_calls.length > 0
) {
return 'tools';
}
// Otherwise, analyze what we have
return 'analyze';
}
// Determine if we should continue scraping or wrap up
function shouldFinish(state: typeof AgentState.State) {
if (state.phase === 'complete') {
return 'end';
}
return 'agent';
}
// Build the graph
const workflow = new StateGraph(AgentState)
// Add nodes
.addNode('agent', agentNode)
.addNode('tools', toolNode)
.addNode('analyze', analyzeNode)
// Set entry point
.addEdge('__start__', 'agent')
// Agent -> tools (if tool call) or analyze (if no tool call)
.addConditionalEdges('agent', shouldContinue, {
tools: 'tools',
analyze: 'analyze',
})
// Tools -> agent (return results to LLM)
.addEdge('tools', 'agent')
// Analyze -> agent (continue) or end (done)
.addConditionalEdges('analyze', shouldFinish, {
agent: 'agent',
end: END,
});
export const app = workflow.compile();Paso 6: ejecutar el agente
// src/index.ts
import 'dotenv/config';
import { HumanMessage } from '@langchain/core/messages';
import { app } from './graph';
async function main() {
const result = await app.invoke({
messages: [
new HumanMessage(
'Research the top 3 MCP server implementations for web scraping. ' +
'Find their websites, extract their key features, and compare pricing.'
),
],
});
// Print final state
console.log('--- Research Complete ---');
console.log('Credits used:', result.creditsUsed);
console.log('URLs discovered:', result.discoveredUrls.length);
console.log('Pages extracted:', Object.keys(result.extractedContent).length);
console.log('\nFinal response:');
console.log(result.messages[result.messages.length - 1].content);
}
main().catch(console.error);Ejecútalo:
npx tsx src/index.tsEl agente buscará en la web, descubrirá páginas relevantes, extraerá contenido de los resultados más prometedores y sintetizará una comparación, todo ello mientras rastrea el uso de credits en el estado del grafo.
Referencia de costes en credits
| Credits | Herramientas | Rol del nodo en LangGraph |
|---|---|---|
| 1 | fetch_url, extract_text, extract_links, extract_metadata | Nodos ligeros de recopilación de datos |
| 2 | scrape_structured, extract_content, map_site, process_document, localization | Nodos de extracción, descubrimiento y procesamiento de documentos |
| 3 | track_changes, analyze_content | Nodos de seguimiento de cambios y análisis |
| 4 | summarize_content, crawl_deep | Nodos de resumen y crawling multipágina |
| 5 | search_web, batch_scrape, scrape_with_actions, stealth_mode | Nodos de investigación y operaciones masivas |
| 10 | deep_research | Análisis exhaustivo (úsalo como un subgrafo de un solo nodo) |
Ejecución típica de un agente de LangGraph: 5 (búsqueda) + 6 (3 extracciones) + 0 (análisis del LLM) = 11 credits.
LangGraph frente a LangChain directo para scraping
| Aspecto | LangGraph | LangChain directo |
|---|---|---|
| Gestión de estado | Integrada, tipada, persistente | Manual, requiere código personalizado |
| Lógica condicional | Aristas condicionales de primera clase | If/else en funciones de cadena |
| Seguimiento de credits | Rastreado automáticamente en el estado del grafo | Contador manual |
| Recuperación de errores | Enruta los errores a nodos de respaldo | Try/catch en la cadena |
| Complejidad | Mayor configuración inicial | Más simple para flujos lineales |
| Mejor para | Investigación multipaso con lógica de ramificación | Pipelines simples de obtener y procesar |
Usa LangGraph cuando tu agente de scraping necesite tomar decisiones basadas en resultados intermedios. Usa LangChain directo (consulta nuestra guía de integración con LangChain) cuando el flujo de trabajo sea lineal.
Próximos pasos
- Documentación de LangGraph -- guías oficiales de LangGraph
- 5 formas de usar CrawlForge con LangChain -- patrones más simples de LangChain
- Construye un asistente de investigación -- arquitectura de agente relacionada
- Referencia de la API de CrawlForge -- documentación completa de los endpoints de las herramientas
Construye hoy mismo agentes de scraping inteligentes. Regístrate en CrawlForge con 1.000 credits gratuitos, conecta las herramientas a tu grafo de LangGraph y deja que tu agente decida qué hacer scraping a continuación.
Pruébalo tú mismo — sin necesidad de registrarte
Ejecuta cualquiera de las 27 herramientas de scraping y extracción de CrawlForge en el playground y luego empieza gratis con 1,000 credits.
1,000 credits gratis • Se recargan cada mes • No se requiere tarjeta de crédito
Etiquetas
Sobre el autor
Mantente al día con los últimos artículos
Recibe tutoriales, novedades del producto y consejos de web scraping en tu bandeja de entrada.
Sin spam. Cancela tu suscripción cuando quieras.