---
title: "Web scraping vs. API: ¿cuál deberías usar?"
description: "Una API entrega los datos que el proveedor decide compartir, en formato fijo; el web scraping lee la propia página. Comparamos ambos y probamos las dos vías."
url: https://proxynet.io/es/blog/web-scraping-vs-api
date: 2026-09-29
author: "Acar Diveroli"
category: "Comparativas, Web scraping"
lang: es
---

# Web scraping vs. API: ¿cuál deberías usar?

Necesitas la misma lista cada mañana: los issues abiertos de un repositorio de GitHub, los precios de la página de categoría de una tienda, las citas de un sitio web. GitHub documenta una API para sus issues, así que un script puede pedirlos y recibir JSON. La tienda quizá no ofrezca nada más que sus páginas, y entonces el script tiene que descargar el HTML y sacar de ahí los precios. Esa es toda la diferencia entre una API y el web scraping, y en la mayoría de los proyectos la elección depende de lo que ofrece el otro lado, no de tus gustos.

Este artículo explica qué es una API y qué es el scraping, compara ambos en diez puntos y después recoge los mismos 100 registros de las dos formas en Python: una vez desde páginas HTML y otra desde el endpoint JSON al que llama la propia página del sitio. Luego vienen los endpoints JSON ocultos, los servicios de API de scraping, los límites de solicitudes, las listas de IP autorizadas, lo que cuesta cada vía y cuándo combinarlas. Todos los ejemplos de código se ejecutaron el 29 de septiembre de 2026 con Python 3.13, Requests 2.34.2 y beautifulsoup4 4.15.0.

> **Nota: Respuesta breve**
>
> Usa una API oficial cuando exista y devuelva los campos que necesitas: los datos llegan estructurados en un formato versionado y los límites están por escrito. Haz scraping cuando no haya API, cuando la API omita campos que la página muestra o cuando su cuota o su precio no encajen con el trabajo. Entre ambas opciones está el endpoint JSON al que llaman las propias páginas de un sitio. Pesa menos que el HTML, pero nadie ha prometido mantenerlo, así que las condiciones de uso del sitio y un ritmo de solicitudes prudente siguen aplicándose. Muchos proyectos usan las dos cosas: la API para los registros principales y el scraping para lo que la API no incluye.

## ¿Qué es una API?

Una API (application programming interface, interfaz de programación de aplicaciones) es un conjunto de solicitudes fijas que un programa puede enviar a otro, junto con las reglas sobre qué enviar y qué se recibe. En la web suele significar una solicitud HTTP a una dirección como `https://api.github.com/repos/python/cpython/issues` y una respuesta en JSON. Una página web está escrita para que la lea una persona; una respuesta de API, para que la analice un programa.

Una API web tiene varias partes:

- **Endpoint:** la dirección de una operación, como «listar issues» u «obtener un producto».
- **Parámetros:** lo que pides, por ejemplo `state=open` o `page=2`.
- **Autenticación:** una clave de API, un token u OAuth que le dice al servicio quién llama. Muchas API también responden a llamadas anónimas, con un límite más bajo.
- **Formato de respuesta:** normalmente JSON, con nombres de campo que no cambian de una llamada a otra.
- **Límites y condiciones:** cuántas llamadas puedes hacer por hora y qué puedes hacer con los datos.
- **Documentación:** muchas API publican una descripción legible por máquinas en formato OpenAPI. La [OpenAPI Specification](https://spec.openapis.org/oas/latest.html), en la versión 3.2.1 desde el 10 de septiembre de 2026, se define como una descripción de interfaz estándar e independiente del lenguaje para API HTTP, para que personas y herramientas puedan saber qué ofrece un servicio sin leer su código fuente.

No todas las API están abiertas a todo el mundo. Bancos, exchanges y muchos servicios empresariales solo dan claves a los titulares de una cuenta, y algunos aceptan llamadas únicamente desde direcciones IP registradas de antemano; volveremos a eso más abajo.

## ¿Qué es el web scraping?

El web scraping es un programa que hace lo mismo que tu navegador y luego se queda solo con los datos: descarga la página, lee el HTML y extrae valores con selectores CSS o XPath. El sitio no ha aceptado nada. La estructura de la página es el único «contrato», y el sitio puede cambiarla cualquier día por sus propios motivos. [¿Qué es el web scraping y cómo funciona?](/es/blog/what-is-web-scraping) recorre todo el proceso, y la diferencia entre seguir enlaces y extraer campos se explica en [Web scraping y web crawling](/es/blog/web-scraping-vs-web-crawling).

La ventaja del scraping es el alcance: puedes llegar a todo lo que un visitante ve sin iniciar sesión. El precio es que cada valor hay que encontrarlo de nuevo en un marcado pensado para el diseño, no para los datos.

## ¿Cómo obtiene los datos cada vía?

Vistos de lejos, los pasos se parecen. La diferencia está en quién decide la forma de la respuesta.

Con una API:

1. **Lees la documentación** y encuentras el endpoint, los parámetros y los límites.
2. **Consigues una clave** si la API la exige, y la guardas en una variable de entorno en lugar de en el código.
3. **Envías una solicitud** con parámetros, por ejemplo `?page=2`.
4. **El servicio devuelve JSON** con campos con nombre y, normalmente, un campo o una cabecera que apunta a la página siguiente.
5. **Lees los campos por su nombre.** Un rediseño del sitio web no los toca.

Con scraping:

1. **Estudias la página** y el HTML que hay detrás para saber dónde está cada valor.
2. **Revisas `robots.txt` y las condiciones del sitio** ([cómo leer robots.txt](/es/blog/robots-txt)).
3. **Descargas la página** como lo haría un navegador, o la renderizas en un navegador headless si el contenido lo construye JavaScript.
4. **Analizas el HTML** y seleccionas cada valor con un selector como `span.text` ([qué es el parsing de datos](/es/blog/what-is-data-parsing)).
5. **Limpias y guardas los valores,** y repites el trabajo cuando cambia la estructura.

## Web scraping vs. API: tabla comparativa

| | API oficial | Endpoint JSON propio del sitio | Web scraping (HTML) |
|---|---|---|---|
| Cobertura de datos | Solo los campos que expone el proveedor | Lo que la página necesita para mostrarse | Todo lo que un visitante puede ver |
| Formato | JSON o XML documentado | JSON sin documentar | HTML que tienes que analizar |
| Estabilidad | Versionada; los cambios se anuncian | Puede cambiar con cualquier versión del front-end | Se rompe cuando cambia la estructura |
| Límites de solicitudes | Publicados, a menudo en las cabeceras de respuesta | Sin publicar; tú marcas el ritmo | Sin publicar; tú marcas el ritmo |
| Autenticación | Clave, token u OAuth; a veces una lista de IP autorizadas | A veces las cookies o los tokens de una sesión de la página | Normalmente ninguna en páginas públicas |
| Condiciones de uso | Las condiciones de la API dicen qué está permitido | Se aplican las condiciones del sitio; el sitio no promete nada | Se aplican las condiciones del sitio y robots.txt |
| Costo | Cuota gratuita o plan de pago | Sin tarifa; tu tiempo y tu tráfico | Sin tarifa; desarrollo, mantenimiento, proxies, renderizado |
| Mantenimiento | Bajo; actualizar cuando se retira una versión | Medio; vigilar los campos renombrados | Alto; los selectores fallan tras los rediseños |
| Tamaño de una respuesta | Pequeño, solo los datos | Pequeño, solo los datos | Páginas enteras con diseño y marcado |
| Contenido generado con JavaScript | No es un problema | No es un problema | Necesita un navegador headless o la vía JSON |

La API REST de GitHub muestra qué significa «versionada» en la práctica. Una solicitud puede indicar su versión en la cabecera `X-GitHub-Api-Version`, y cuando sale una versión nueva, la anterior sigue teniendo soporte durante al menos 24 meses más ([versiones de la API REST de GitHub](https://docs.github.com/en/rest/about-the-rest-api/api-versions)). Allí, eliminar o renombrar un campo de la respuesta cuenta como un cambio incompatible y tiene que esperar a una versión nueva. Las solicitudes sin esa cabecera siguen recibiendo la versión 2022-11-28, con soporte hasta el 10 de marzo de 2028. Ningún sitio web promete algo así sobre sus clases CSS.

## Los mismos datos por las dos vías: un ejemplo probado en Python

El sitio de práctica [quotes.toscrape.com](https://quotes.toscrape.com/) es un entorno de pruebas creado para ejercicios de scraping; su pie de página menciona a Zyte. Muestra 100 citas en diez páginas HTML, de `/page/1/` a `/page/10/`. Su versión con scroll infinito, en `/scroll`, carga las mismas citas desde un endpoint JSON, `/api/quotes?page=N`, y cada respuesta incluye un campo `has_next`. El sitio devuelve 404 para `/robots.txt`, lo que según el estándar de robots.txt significa que no hay reglas de rastreo; aun así, el script espera un segundo entre páginas.

El script recoge las 100 citas por las dos vías con una sola sesión compartida. Cada solicitud lleva un tiempo de espera (timeout), la sesión se identifica con su nombre en el User-Agent y reintenta ante respuestas `429` y `5xx`:

```python
"""Las mismas citas dos veces: analizadas desde las páginas HTML y leídas del endpoint JSON."""
import os
import time

import requests
from bs4 import BeautifulSoup
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

BASE = "https://quotes.toscrape.com"
DELAY = 1.0  # pausa entre páginas

def make_session():
    retry = Retry(
        total=4,
        backoff_factor=1,  # espera 0, 2, 4, 8 s entre intentos
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods=["GET"],
        respect_retry_after_header=True,  # un encabezado Retry-After sustituye al backoff
    )
    session = requests.Session()
    session.mount("https://", HTTPAdapter(max_retries=retry))
    session.mount("http://", HTTPAdapter(max_retries=retry))
    session.headers["User-Agent"] = "quotes-compare/1.0 (contact: you@example.com)"
    proxy = os.environ.get("PROXY_URL")  # p. ej., http://user:pass@pr.proxynet.io:8000
    if proxy:
        session.proxies = {"http": proxy, "https": proxy}
    return session

def scrape_html(session):
    """Vía 1: descargar cada página HTML y extraer los campos con selectores CSS."""
    quotes, page, size = [], 1, 0
    while True:
        r = session.get(f"{BASE}/page/{page}/", timeout=(5, 20))
        r.raise_for_status()
        size += len(r.content)
        soup = BeautifulSoup(r.content, "lxml")
        for q in soup.select("div.quote"):
            quotes.append({
                "text": q.select_one("span.text").get_text(strip=True),
                "author": q.select_one("small.author").get_text(strip=True),
                "tags": [a.get_text(strip=True) for a in q.select("a.tag")],
            })
        if soup.select_one("li.next > a") is None:  # sin enlace «Next»: última página
            return quotes, page, size
        page += 1
        time.sleep(DELAY)

def fetch_api(session):
    """Vía 2: llamar al endpoint JSON que usa la propia página de scroll del sitio."""
    quotes, page, size = [], 1, 0
    while True:
        r = session.get(f"{BASE}/api/quotes", params={"page": page}, timeout=(5, 20))
        r.raise_for_status()
        size += len(r.content)
        data = r.json()
        for q in data["quotes"]:
            quotes.append({
                "text": q["text"],
                "author": q["author"]["name"],
                "tags": q["tags"],
            })
        if not data["has_next"]:  # la API indica cuándo termina la lista
            return quotes, page, size
        page += 1
        time.sleep(DELAY)

session = make_session()
results = {}
for name, collect in (("HTML", scrape_html), ("API", fetch_api)):
    quotes, pages, size = collect(session)
    results[name] = quotes
    print(f"{name}: {len(quotes)} quotes from {pages} pages, {size / 1024:.1f} KiB")

print("same data:", results["HTML"] == results["API"])
print(results["API"][0])
```

La salida fue idéntica tanto si lo ejecutamos directamente como a través de un proxy de prueba local configurado en `PROXY_URL`:

```text
HTML: 100 quotes from 10 pages, 106.1 KiB
API: 100 quotes from 10 pages, 30.2 KiB
same data: True
{'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', 'author': 'Albert Einstein', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}
```

Los 100 registros de las dos vías coincidieron campo por campo. La vía HTML descargó 106,1 KiB para obtenerlos y la vía JSON 30,2 KiB, contados tras la descompresión; las páginas HTML también llevan el diseño, la navegación, la barra lateral de etiquetas y el marcado que rodea cada valor. A través del proxy, la única `Session` envió las 20 solicitudes por un solo túnel del proxy.

Los dos bucles detectan el final en sitios distintos. La vía HTML se detiene cuando la página no tiene enlace «Next», mientras que la API lo dice de forma explícita con `has_next: false`. Contar páginas hasta que aparezca un error no funcionaría en este sitio: `/page/11/` responde `200` sin citas, y `/api/quotes?page=11` responde `200` con una lista vacía. Otras condiciones de parada se explican en [¿Qué es la paginación y cómo rastrear todas las páginas?](/es/blog/pagination-web-scraping)

La configuración de reintentos sirve para las dos vías. Apuntamos la sesión a un servidor de prueba local que respondió `429` dos veces con `Retry-After: 2`: la sesión esperó dos segundos cada vez, devolvió la tercera respuesta a los 4,0 segundos y el código que la llamaba nunca vio un 429. Frente a un servidor que respondía `503` una y otra vez sin esa cabecera, esperó 0, 2, 4 y 8 segundos y, a los 14 segundos, lanzó `requests.exceptions.RetryError` con «too many 503 error responses». `allowed_methods=["GET"]` es intencionado: [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-idempotent-methods) dice que un cliente no debería reintentar automáticamente una solicitud con un método no idempotente como POST.

### Campos que tiene una vía y le faltan a la otra

Las dos vías no traen exactamente los mismos campos. Cada registro de la API incluye el enlace del autor en Goodreads y un slug, que la página de lista no muestra:

```json
{
  "author": {
    "goodreads_link": "/author/show/9810.Albert_Einstein",
    "name": "Albert Einstein",
    "slug": "Albert-Einstein"
  },
  "tags": [
    "change",
    "deep-thoughts",
    "thinking",
    "world"
  ],
  "text": "“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”"
}
```

La página de lista HTML, por su parte, enlaza a cada autor con una página «about» que tiene la fecha y el lugar de nacimiento («March 14, 1879» e «in Ulm, Germany» en el caso de Einstein). Esa página no tiene equivalente en JSON: `/api/author/Albert-Einstein` devuelve 404. En los proyectos reales pasa algo parecido. La API contiene identificadores internos y cifras exactas de stock, mientras que la página contiene los textos, las insignias y los precios que ven de verdad los visitantes.

Una API también puede fallar de formas que desde el código resultan extrañas. `/api/quotes?page=abc` devuelve un estado `500` con una página de error en HTML, y llamar a `.json()` sobre ese cuerpo lanza `JSONDecodeError: Expecting value: line 1 column 1 (char 0)`. En el script de arriba, el adaptador de reintentos captura primero el 500 y termina en un `RetryError`; sin él, `raise_for_status()` detiene la ejecución antes que `.json()`. Las demás causas de ese error están en [Cómo solucionar JSONDecodeError: Expecting Value](/es/blog/jsondecodeerror-expecting-value).

## Endpoints JSON ocultos: la vía intermedia

Muchas páginas que parecen HTML normal cargan sus datos como JSON en segundo plano, igual que la página `/scroll` de arriba llama a `/api/quotes`. Estas solicitudes se encuentran en las herramientas para desarrolladores del navegador: abre el panel **Red**, filtra por **Fetch/XHR**, recarga la página y busca las respuestas que contienen tus datos. El procedimiento completo está en [Páginas estáticas y dinámicas en web scraping](/es/blog/static-vs-dynamic-pages), y cómo convertir una solicitud copiada en código Python se explica en [POST con JSON en Python Requests](/es/blog/python-requests-post-json).

Un endpoint así suele ser un buen término medio: datos estructurados con una fracción del tamaño de la página. Aun así, no es una API pública, de modo que se aplican algunas reglas:

- **Solo datos públicos.** Si la solicitud solo funciona con la cookie de tu sesión iniciada, los datos no son públicos, y un script programado que funciona con tu cookie pone en riesgo tu propia cuenta.
- **Sin promesa de estabilidad.** Los nombres de campo y los parámetros pueden cambiar con cualquier versión del front-end del sitio, sin aviso. Comprueba la forma de la respuesta en cada ejecución y haz que el script falle de forma visible cuando falte una clave.
- **Mismas condiciones, mismo ritmo.** Las condiciones de uso del sitio y `robots.txt` se aplican al endpoint igual que a sus páginas. Las solicitudes JSON son pequeñas, y eso facilita enviarlas mucho más rápido de lo que lo haría una persona; mantén la pausa.
- **Detente ante los parámetros firmados.** Si la solicitud lleva una firma o un token de corta duración que genera el script de la página, no se diseñó para reutilizarla. Busca una API oficial o contacta con el sitio.
- **Prefiere la vía documentada.** Si el sitio ofrece una API oficial para los mismos datos, usa esa.

El aspecto legal de recopilar datos públicos, que cambia según el país, se trata en [¿El web scraping es legal?](/es/blog/is-data-web-scraping-legal)

## ¿Qué es una API de scraping?

«API de scraping» también es el nombre de un tipo de servicio comercial, algo distinto de la API propia de un sitio web. Envías al servicio una URL de destino; el servicio descarga la página por ti, a menudo en un navegador headless y a través de su propio pool de proxies, reintenta las solicitudes fallidas y devuelve el HTML, o los campos que ha extraído, en JSON. Lo llamas como a una API, pero los datos siguen saliendo del scraping de la página de destino.

Estos servicios encajan con equipos que necesitan páginas de muchos sitios sin mantener ellos mismos navegadores ni rotación de proxies. Cobran por las solicitudes que envías a través de ellos, así que la comparación es entre esa factura y el costo de mantener tu propio scraper. No cambian de quién son las reglas: las condiciones del sitio de destino y su `robots.txt` te siguen obligando, y una API de scraping no es motivo para saltarse una API oficial que ya existe.

## Límites de solicitudes, 429 y claves de API

Una API oficial te dice sus límites, a menudo en cada respuesta. La API REST de GitHub permite 60 solicitudes por hora sin autenticación, contadas por dirección IP de origen, y 5000 por hora con un token de acceso personal ([límites de solicitudes de la API REST de GitHub](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api)). El endpoint `/rate_limit` muestra cómo vas, y llamarlo no cuenta para el límite principal:

```python
import requests

r = requests.get(
    "https://api.github.com/rate_limit",
    headers={"Accept": "application/vnd.github+json"},
    timeout=(5, 20),
)
core = r.json()["resources"]["core"]
print(r.status_code, "limit:", core["limit"], "remaining:", core["remaining"], "reset:", core["reset"])
print({k: v for k, v in r.headers.items() if k.lower().startswith("x-ratelimit")})
```

```text
200 limit: 60 remaining: 58 reset: 1790650244
{'X-RateLimit-Limit': '60', 'X-RateLimit-Remaining': '58', 'X-RateLimit-Used': '2', 'X-RateLimit-Resource': 'core', 'X-RateLimit-Reset': '1790650244'}
```

El valor de reinicio es una marca de tiempo Unix en UTC, las 02:50:44 del 29 de septiembre de 2026 en esta ejecución, y desde nuestra dirección ya se habían hecho dos solicitudes esa hora. Cuando se agota el límite, GitHub responde `403` o `429` con `x-ratelimit-remaining` a 0, y debes esperar hasta la hora que indica `x-ratelimit-reset`. Para sus límites secundarios envía `retry-after` cuando puede y, si no, pide esperar al menos un minuto.

Aquí es donde un reintento genérico se queda corto. La sesión de nuestro script reintenta ante `429` pero no ante `403`, y 14 segundos de backoff no sirven de nada contra una ventana que se reinicia una vez por hora. Con una API, lee las cabeceras y espera hasta el reinicio.

El código de estado viene de [RFC 6585](https://www.rfc-editor.org/rfc/rfc6585.html#section-4): `429 Too Many Requests` significa que el cliente envió demasiadas solicitudes en un periodo de tiempo determinado; la respuesta debería explicar la situación y puede incluir `Retry-After`. El RFC deja abierto a propósito cómo identifica el servidor al cliente y cuenta las solicitudes, así que un límite puede ser por IP, por clave o por cuenta. RFC 9110 define `Retry-After` como una fecha o como un número de segundos, y urllib3 lee las dos formas. Un sitio web del que haces scraping rara vez publica nada de esto, así que el ritmo lo marcas tú y bajas la velocidad con el primer 429 ([429 Too Many Requests explicado](/es/blog/http-429-too-many-requests)).

## ¿Por qué algunas API piden una dirección IP fija?

Algunas API comprueban de dónde viene una llamada, además de qué clave lleva. Exchanges, marketplaces, bancos y muchos servicios de datos para empresas te permiten registrar una o varias direcciones IP para una clave, y una llamada desde cualquier otra dirección se rechaza aunque la clave sea correcta. Eso falla en cuanto el script se ejecuta en un lugar con una dirección cambiante: una conexión doméstica, un equipo portátil que viaja contigo, una función serverless sin IP de salida fija. Cómo encontrar tu dirección de salida, y cuatro formas de resolverlo, se explica en [IP estática para API](/es/blog/static-ip-for-api-access); el caso de los exchanges está en [Lista blanca de IP en la API de un exchange](/es/blog/crypto-exchange-api-ip-whitelist).

Para integraciones que manejan pagos, datos de tarjetas o historiales médicos, la dirección registrada debería ser la de tu propio servidor o tu propia línea. Para entornos de prueba y clientes que no manejan datos sensibles, un proxy con dirección fija también sirve: con [Proxies ISP](https://proxynet.io/es/static-isp-residential-proxy) o [Proxies de centro de datos](https://proxynet.io/es/datacenter-proxy), tu script sale por una única IP que registras una vez en la API. Estos productos por IP vienen de forma predeterminada con una restricción de sitio de destino, así que indicas el host de la API al hacer el pedido; el acceso a todos los sitios web es un complemento de pago.

El scraping tiene la necesidad contraria: muchas páginas a lo largo del tiempo, a veces tal como las ven los visitantes de otro país. Para eso están los [Proxies rotativos](https://proxynet.io/es/rotating-proxy). Ningún tipo de proxy cambia las reglas anteriores: cambia la dirección, no las condiciones del sitio ni el ritmo de solicitudes que debes respetar.

## ¿Cuánto cuesta cada enfoque?

**Una API oficial.** El precio está en la página de precios del proveedor. Algunas API son gratuitas hasta una cuota, otras cobran desde la primera llamada y otras solo están disponibles con un contrato empresarial. El costo de ingeniería es bajo: un cliente para una API JSON documentada suele ocupar unas pocas decenas de líneas, como el de arriba. Los costos ocultos son dos. Uno es la cuota, porque un trabajo que necesita más llamadas de las que permite el plan tiene que esperar o pagar. El otro es el control del proveedor: las condiciones, los precios y el acceso pueden cambiar, y una API puede cerrarse.

**Scraping.** El sitio no cobra nada, pero todo lo demás corre por tu cuenta: escribir el parser, arreglarlo después de los rediseños, un navegador headless cuando JavaScript construye la página (mucho más pesado que una solicitud simple; consulta la sección de costos de [Páginas estáticas y dinámicas en web scraping](/es/blog/static-vs-dynamic-pages)), proxies cuando el volumen o el país lo exigen, y un seguimiento que detecte cuando un selector deja de devolver datos sin avisar. El tamaño también suma. En nuestra prueba, la vía HTML descargó unas 3,5 veces más bytes para los mismos registros, y si tu plan de proxy se factura por tráfico, esa proporción aparece en la factura.

**Un servicio de API de scraping.** Pagas por solicitud y no mantienes navegadores ni proxies. Si el servicio devuelve HTML sin procesar, el parsing sigue siendo cosa tuya.

## ¿Cuándo combinar el scraping y una API?

Usar las dos cosas es habitual, y suele seguir uno de estos cuatro patrones:

- **La API para la lista, las páginas para los detalles.** En nuestro ejemplo tomarías las 100 citas del endpoint JSON y visitarías una vez cada una de las 50 páginas de autor para obtener las fechas de nacimiento.
- **La API para tus propios datos, las páginas para la vista pública.** Una API de vendedor devuelve tus anuncios con sus ID y su stock; la página pública del producto muestra las insignias y el número de reseñas que ven los clientes. Une ambas fuentes por el ID del producto.
- **La API para el registro, la página para un país.** Una API puede devolver un único precio de lista, mientras que un visitante de otro país ve en la página una moneda local, impuestos y una promoción. Esa comparación necesita la página cargada desde ese país.
- **La página como control de la API.** Un pequeño scraper que revisa unas cuantas páginas al día confirma que lo que devuelve la API sigue coincidiendo con lo que ven los visitantes.

## Casos de uso

- **Seguimiento de precios y stock:** tus propios anuncios a través de la API de la plataforma, las páginas públicas de la competencia con un scraper ([seguimiento de precios de la competencia](/es/blog/competitor-price-tracking)).
- **Datos de repositorios, issues y versiones:** la API de GitHub con paginación mediante la cabecera `Link` ([paginación en API](/es/blog/pagination-web-scraping)).
- **Datos de exchanges y bots de trading:** una clave de API vinculada a una dirección registrada ([lista de IP autorizadas en la API de un exchange](/es/blog/crypto-exchange-api-ip-whitelist)).
- **Una tabla a una hoja de cálculo, una sola vez:** una función de importación o unas pocas líneas de Python ([extraer datos de una web](/es/blog/extract-data-from-website)).
- **Guardar los resultados:** los mismos registros escritos en CSV, JSON o SQLite ([guardar datos de scraping](/es/blog/save-scraped-data-csv-json-sqlite)).
- **Encontrar todas las páginas antes de la extracción:** un crawler que descubre URL en todo el sitio ([rastreador web](/es/web-crawler)).
- **Recopilación grande y programada:** colas, control del ritmo y salidas en varios países ([extracción de datos](/es/data-scraping)).

## Errores comunes

- **Hacer scraping de un sitio que ofrece una API para los mismos datos.** Asumes el mantenimiento sin necesidad, y puede que las condiciones de la API sean las únicas que permiten el acceso automatizado.
- **Tratar un endpoint oculto como una API pública.** No tiene versión ni promesa; comprueba la forma de la respuesta en cada ejecución.
- **Una sola regla de reintento para todos los errores.** Un backoff corto sirve para un `503` breve; una cuota por hora necesita `x-ratelimit-reset`, y la configuración de arriba ni siquiera reintenta un `403`.
- **Reintentar solicitudes POST automáticamente.** Un pedido o un mensaje puede enviarse dos veces; limita los reintentos automáticos a GET.
- **Llamar a `.json()` sobre cualquier cosa que llegue.** Comprueba primero el código de estado y el `Content-Type`; una página de error es HTML.
- **Recorrer números de página hasta que algo falle.** En el sitio de práctica, la página 11 respondió `200` sin nada dentro; sigue `has_next` o el enlace «Next».
- **Guardar una clave de API en el código.** Léela de una variable de entorno y mantenla fuera del repositorio.
- **Culpar al sitio de un error del proxy.** El adaptador de reintentos también reintenta un inicio de sesión fallido en el proxy: con una contraseña de proxy incorrecta, nuestro script lo intentó cinco veces durante 14 segundos antes de lanzar `ProxyError` con `407 Proxy Authentication Required`. Revisa primero las credenciales.

## Guía de decisión

| Necesidad | Recomendación |
|---|---|
| El sitio tiene una API oficial con los campos que necesitas | Usa la API; lee antes sus límites y condiciones |
| Hay API, pero le faltan algunos campos | API para los registros principales, scraping para el resto, unidos por un ID |
| No hay API y los datos están en el HTML | Requests y BeautifulSoup, con una pausa entre páginas |
| No hay API y JavaScript carga los datos | Busca primero la solicitud JSON; un navegador headless solo si no se puede reutilizar |
| La API solo acepta llamadas desde IP registradas | Una dirección de salida fija: tu propio servidor o, para clientes sin datos sensibles, un proxy ISP estático o de centro de datos |
| La cuota de la API se agota cada hora | Lee las cabeceras de límite, reparte las llamadas, pide un nivel superior |
| Páginas de muchos sitios sin infraestructura propia | Un servicio de API de scraping, si las condiciones de los sitios de destino permiten la recopilación |
| Miles de páginas al día de sitios que ya evaluaste | Tu propio scraper con [Proxies rotativos](https://proxynet.io/es/rotating-proxy) y un presupuesto de solicitudes por sitio |

## Preguntas frecuentes

### ¿Cuál es la diferencia entre el web scraping y una API?

Una API es una vía que el proveedor construyó para programas: envías una solicitud documentada y recibes datos estructurados, con límites y condiciones publicados. El web scraping lee las páginas hechas para personas y extrae los valores del HTML. Con la API, el proveedor decide qué campos recibes; el scraping llega a todo lo visible, pero se rompe cuando cambia la página.

### ¿Es mejor el web scraping que usar una API?

En general, no. Cuando una API oficial devuelve los campos que necesitas, un script basado en ella se escribe más rápido y sigue funcionando aunque el sitio se rediseñe. El scraping es mejor opción cuando no hay API, cuando la API omite datos que muestra la página o cuando su cuota o su precio no encajan con el trabajo.

### ¿Todos los sitios web tienen una API?

No. Muchos sitios no tienen ninguna API pública, y muchos tienen una que solo cubre parte de sus datos o exige una cuenta de empresa. Algunos sitios cargan sus páginas desde endpoints JSON internos; se pueden usar con cuidado para datos públicos, pero no son una API publicada.

### ¿Es legal usar la API oculta de un sitio web?

Depende de los datos, de las condiciones de uso del sitio y de las leyes que se apliquen a ti y al sitio. Leer datos públicos desde un endpoint al que llama la propia página es, técnicamente, lo mismo que leer la página, y se aplican las mismas condiciones. Iniciar sesión con la cuenta de otra persona, eludir controles de acceso o recopilar datos personales plantea otras cuestiones. El panorama general está en [¿El web scraping es legal?](/es/blog/is-data-web-scraping-legal)

### ¿Una API de scraping es lo mismo que la API de un sitio web?

No. La API de un sitio web la publica el propio sitio y devuelve sus datos en un formato fijo. Una API de scraping es un servicio de terceros que descarga la página de destino por ti y devuelve el HTML o campos ya analizados. Los datos siguen saliendo de la página, y las condiciones del sitio de destino se siguen aplicando.

### ¿Necesito un proxy para llamar a una API?

Normalmente no. Lo necesitas cuando la API solo acepta llamadas desde direcciones IP registradas y tu propia dirección cambia, o cuando tienes que ver una API o una página tal como responde desde otro país. Para pagos y otras integraciones sensibles, registra en su lugar la dirección de tu propio servidor.

## En resumen

Una API es la vía que un proveedor construyó para programas, con un formato versionado y límites por escrito. El scraping lee lo que el proveedor construyó para personas; llega a todo lo visible y se rompe cuando cambia la página. Comprueba primero si hay una API oficial, usa con cuidado el endpoint JSON propio de un sitio cuando no la haya y haz scraping del HTML para lo que quede. Cuando una API exija una dirección fija o un trabajo de scraping necesite volumen en varios países, compara nuestros [planes de proxy](/es/proxy).
