Texto Web Limpio para RAG: Un Pipeline de Recuperación, Extracción y Fragmentación
Web Data Collection Specialist
Puntos Clave:
- La calidad de RAG es la calidad del corpus. Las respuestas de recuperación son tan buenas como el texto que indexaste, y la mayoría de las fallas en la tubería se remontan a páginas que nunca se representaron, elementos de navegación que se incrustaron o trozos que se dividieron a mitad de pensamiento.
- La recuperación es la etapa poco confiable. Las páginas modernas se renderizan con JavaScript y son verificadas por bots; un simple GET HTTP devuelve una carcasa vacía o una página de desafío, y esos datos basura fluyen silenciosamente a tu tienda vectorial.
- Un POST devuelve la página renderizada. El desbloqueador web de Scrapeless toma una URL y devuelve el HTML completamente renderizado como
{"code": 200, "data": "<html…>"}; el manejo de renderizado y anti-bot se ejecuta del lado del servidor. - La extracción es sustracción. Elimina scripts, estilos, navegación y pies de página antes de leer el texto; lo que queda es la prosa digna de ser incrustada.
- Trozos con superposición, mantén la procedencia. Ventanas de palabras fijas con superposición preservan el contexto a través de los límites, y cada trozo debe llevar su URL de origen; la recuperación sin procedencia no se puede auditar.
- Gratis para empezar. Las nuevas cuentas de Scrapeless incluyen créditos de prueba gratuitos; regístrate en app.scrapeless.com.
Resumen de la tubería
Un sistema RAG recupera trozos de texto y los alimenta a un modelo; todo lo que sigue hereda lo que se indexó. Esta guía construye el lado de ingesta de extremo a extremo:
- Recuperar — obtener HTML completamente renderizado para una lista de URL a través del desbloqueador web, de modo que las páginas construidas con JavaScript y los sitios verificados por bots devuelvan contenido real.
- Extraer — eliminar los elementos visuales y conservar la prosa.
- Trocear — dividir en ventanas de palabras superpuestas con procedencia, listas para cualquier modelo de incrustación y tienda vectorial.
La salida es corpus.jsonl: un trozo por línea con su URL de origen y posición — el formato neutral que acepta cualquier flujo de trabajo de incrustación. Las etapas 2 y 3 son transformaciones puras; solo la etapa 1 toca la red.
Por qué la etapa de recuperación falla primero
Tres modos de falla dominan la ingesta de texto web, y los tres son invisibles hasta que la calidad de recuperación disminuye:
- Renderizado del lado del cliente. El HTML que un GET simple devuelve es una carcasa de carga; el artículo llega más tarde a través de JavaScript. Tu extractor lee un
<div id="root">vacío e indexa nada. - Intersticiales anti-bot. Las páginas de desafío devuelven HTTP 200 con prosa de "verificando tu navegador", lo que se incrusta maravillosamente y se recupera con confianza.
- Soft-404s. URL muertas que muestran una página estilizada de "no encontrado", de nuevo con un estado 200.
La solución es recuperar a través de infraestructura que renderice y limpie esas capas del lado del servidor. El desbloqueador web Universal Scraping API hace exactamente eso: un POST por URL, HTML renderizado de vuelta.
Requisitos previos
- Una cuenta de Scrapeless y clave API — regístrate en app.scrapeless.com.
- Python 3.10+ con
requestsybeautifulsoup4. - Una lista de URL que tienes derecho a ingerir (ver la nota sobre la obtención a continuación).
bash
export SCRAPELESS_API_KEY=tu_token_api_aqui
Etapa 1 — Recuperar páginas renderizadas
Un POST por URL a la endpoint del desbloqueador. La respuesta es JSON con el documento renderizado en data:
python
# fetch.py — Lista de URL -> páginas/*.html (completamente renderizadas)
import os
import pathlib
import requests
ENDPOINT = "https://api.scrapeless.com/api/v1/unlocker/request"
HEADERS = {
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
}
URLS = [
"https://www.scrapeless.com/es/blog/best-llm-scrapers-2026",
"https://www.scrapeless.com/es/blog/google-ai-overview-scraper-api-2026",
]
pathlib.Path("pages").mkdir(exist_ok=True)
for url in URLS:
resp = requests.post(
ENDPOINT,
headers=HEADERS,
json={
"actor": "unlocker.webunlocker",
"input": {"url": url, "type": "html", "redirect": True, "method": "GET"},
},
timeout=120,
)
resp.raise_for_status()
html = resp.json()["data"]
name = url.rstrip("/").rsplit("/", 1)[-1] + ".html"
pathlib.Path("pages", name).write_text(html, encoding="utf-8")
print(f"{url} -> pages/{name} ({len(html):,} bytes)")
Una recuperación saludable genera cientos de kilobytes de documento renderizado por página de artículo. Unos pocos kilobytes generalmente significan una carcasa o un intersticial, lo que vale la pena verificar antes de que llegue al índice.
Obtén tu clave API en el plan gratuito: app.scrapeless.com
Etapas 2 y 3 — Extraer la prosa, trocear con procedencia
La extracción es sustracción: elimina los elementos que nunca son prosa, luego lee el texto de lo que queda. El agrupamiento es una ventana de palabras fija con superposición, y cada fragmento conserva su URL de origen y posición:
python
# build_corpus.py — pages/*.html -> corpus.jsonl (fragmentos con procedencia)
import json
import pathlib
from bs4 import BeautifulSoup
CHUNK_WORDS = 220 # tamaño de la ventana
OVERLAP_WORDS = 40 # llevados al siguiente fragmento
STRIP_TAGS = ["script", "style", "noscript", "nav", "header", "footer", "aside", "form", "svg"]
def extract_text(html: str) -> str:
soup = BeautifulSoup(html, "html.parser")
for tag in soup(STRIP_TAGS):
tag.decompose()
root = soup.find("article") or soup.find("main") or soup.body or soup
text = root.get_text(" ", strip=True)
return " ".join(text.split())
def chunk(words: list[str]):
step = CHUNK_WORDS - OVERLAP_WORDS
for start in range(0, max(len(words) - OVERLAP_WORDS, 1), step):
yield start, " ".join(words[start:start + CHUNK_WORDS])
total = 0
with open("corpus.jsonl", "w", encoding="utf-8") as out:
for page in sorted(pathlib.Path("pages").glob("*.html")):
text = extract_text(page.read_text(encoding="utf-8"))
words = text.split()
for start, body in chunk(words):
out.write(json.dumps({
"source": page.stem,
"word_offset": start,
"n_words": len(body.split()),
"text": body,
}) + "\n")
total += 1
print(f"{page.name}: {len(words):,} palabras")
print(f"{total} fragmentos -> corpus.jsonl")
Lo que sale, una línea por fragmento:
json
// muestra ilustrativa — esquema de una ejecución en vivo de build_corpus.py; texto abreviado
{
"source": "best-llm-scrapers-2026",
"word_offset": 180,
"n_words": 220,
"text": "…el actor devuelve la respuesta más sus citas como campos estructurados…"
}
Desde aquí, cualquier flujo de trabajo de incrustación toma el relevo: lee corpus.jsonl, incrusta text, almacena el vector con source y word_offset como metadatos. Los campos de procedencia son lo que te permite rastrear una mala recuperación de vuelta a la página y a la posición de donde vino.
Abastecimiento responsable
Un corpus de entrenamiento o recuperación hereda peso legal de sus fuentes. Ingiere solo páginas de acceso público que tengas derecho a usar; verifica los términos de servicio y las directrices de robots de cada sitio antes de agregarlo a la lista de URL; mantén el volumen de solicitudes modesto por host; y trata el texto con derechos de autor como algo que recuperas y atribuyes en lugar de republicar. Cuando un corpus incluye contenido de terceros, mantener intacto el campo source es la diferencia entre una cita y una copia.
FAQ
P: ¿Por qué no usar simplemente requests.get en las URL?
Para páginas estáticas funciona. Para sitios renderizados por JavaScript o verificados por bots, devuelve contenedores y pantallas intermedias que envenenan el índice silenciosamente; el desbloqueador existe justamente para esos casos.
P: ¿Qué tamaño deberían tener los fragmentos?
La ventana 220/40 aquí es un valor predeterminado razonable para modelos de incrustación de clase transformadora de oraciones. Ajusta según el contexto de tu modelo y tu granularidad de recuperación; mantén algo de superposición para que las ideas que cruzan un límite sobrevivan.
P: ¿Cómo puedo detectar una mala recuperación antes de que llegue al índice?
Verificaciones de tamaño y contenido: las páginas de artículos renderizadas son grandes (las páginas del ejemplo trabajado llegan a cientos de kilobytes), y el texto extraído de unas pocas cientos de palabras de una página que debería ser un artículo es una bandera roja que vale la pena registrar.
P: ¿Puede esto recuperar páginas detrás de potentes proveedores anti-bots?
La tarea del desbloqueador es eliminar las capas anti-bots del lado del servidor. Donde una página específica aún no pueda ser desbloqueada, trátala como no recuperable y déjala por fuera; una página faltante es recuperable, un índice envenenado no lo es.
P: ¿Necesito un proxy?
No. La salida y renderización se manejan dentro del actor; el POST que envías es toda la integración.
P: ¿Dónde entran las incrustaciones y el almacenamiento de vectores?
Después de corpus.jsonl, con cualquier pila que ya uses. Este pipeline se detiene deliberadamente en fragmentos limpios y etiquetados con procedencia; el formato que toda herramienta de incrustación acepta.
Conclusión: limpio dentro, limpio fuera
El pipeline de ingestión se reduce a tres archivos cortos: recuperar HTML renderizado a través del desbloqueador, restar el chrome, agrupar con superposición y procedencia. Nada de esto es glamoroso, y todo esto decide si las respuestas de recuperación provienen de texto real de artículos o de un contenedor de carga que se coló en el índice. Dirige la lista de URL a las fuentes que tu asistente debería conocer, programa la ejecución y conserva las páginas crudas; son el rastro de auditoría.
¿Listo para construir tu pipeline de ingestión RAG?
Únete a nuestra comunidad para reclamar un plan gratuito y conectarte con desarrolladores que están construyendo pipelines de datos: Discord · Telegram.
Regístrate en app.scrapeless.com para obtener créditos de prueba gratuitos y apunta la etapa de recuperación a las páginas que necesita tu corpus de recuperación. Consulta precios para conocer los niveles actuales.
En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.



