Tony Wang9 min de lecturaExtraer un sitio web a Google Sheets — de IMPORTXML a API
Cómo extraer un sitio web a Google Sheets: IMPORTXML con XPath real, auto-actualización con Apps Script y un pipeline Python + API.
Hay tres niveles para extraer datos de un sitio web hacia Google Sheets, y cada uno existe porque el nivel anterior choca con una pared. El nivel uno son las fórmulas de importación integradas — IMPORTHTML, IMPORTXML, IMPORTDATA — gratuitas, instantáneas y limitadas a páginas estáticas. El nivel dos es Apps Script con UrlFetchApp y un disparador basado en tiempo, que te da programación real y manejo de JSON. El nivel tres es un script corto de Python que llama a una API de scraping y escribe filas con gspread, al que recurres cuando el sitio se resiste. Esta guía profundiza en los tres, con fórmulas funcionales, scripts completos y un relato honesto de dónde falla cada uno. (¿Necesitas los datos en Excel en su lugar? La guía hermana, extraer un sitio web a Excel, cubre Power Query y el lado del libro de trabajo del mismo pipeline.)
Nivel 1: Las fórmulas de importación integradas, bien usadas
La mayoría de los tutoriales muestran un ejemplo de IMPORTXML y siguen adelante. Las fórmulas integradas merecen algo mejor — usadas dentro de sus límites, son la herramienta de scraping más rápida que existe, porque no hay nada que instalar ni nada que autenticar.
IMPORTHTML — tablas y listas por posición
IMPORTHTML(url, query, index) toma el elemento table o list número N de una página. Para cualquier página cuyos datos ya vivan en una tabla HTML — Wikipedia, estadísticas gubernamentales, tablas de posiciones deportivas — esto es todo el trabajo:
=IMPORTHTML("https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)", "table", 1)
Si el primer índice devuelve la tabla equivocada, no adivines — enumera. Coloca este ayudante en una celda de prueba y etiquetará cada tabla de la página para que encuentres el índice correcto de una vez:
=INDEX(IMPORTHTML("https://example.com/stats", "table", 2), 1, 1)
Sube el 2 final hasta que la vista previa muestre el encabezado que buscas, y luego apunta tu fórmula real a ese índice. Usa "list" como segundo argumento para elementos ul y ol de la misma manera.
IMPORTXML — XPath para todo lo demás
IMPORTXML(url, xpath_query) es el instrumento de precisión: obtiene la página y ejecuta una consulta XPath contra el marcado, para que puedas extraer exactamente un campo en lugar de una tabla entera. Consultas reales que sí vas a usar:
=IMPORTXML("https://example.com/product", "//h1")
=IMPORTXML("https://example.com/product", "//span[@class='price']")
=IMPORTXML("https://example.com/blog", "//a/@href")
=IMPORTXML("https://example.com/blog", "//article//h2[contains(@class, 'title')]")
=IMPORTXML("https://example.com", "//meta[@name='description']/@content")
La primera extrae el título de la página, la segunda un span de precio por clase, la tercera cada URL de enlace en la página, la cuarta encabezados cuya clase simplemente contiene un valor (útil cuando los sitios usan nombres de clase con hash pero prefijos estables), y la quinta obtiene metadatos que nunca se renderizan visiblemente. Los atributos de XPath vuelven como texto plano, y una consulta que coincide con varios nodos se derrama automáticamente por la columna. Un tercer argumento opcional fija la configuración regional de análisis si los formatos numéricos de la página fuente difieren de los de tu hoja.
Si una consulta no devuelve nada, pruébala primero en tu navegador: abre las DevTools en la página objetivo y ejecuta $x("//span[@class='price']") en la consola. Si DevTools encuentra el nodo pero Sheets no, el elemento está siendo creado por JavaScript después de cargar la página — y has encontrado el límite duro de las fórmulas integradas (más abajo). Escribir XPath contra marcado del mundo real es la habilidad central aquí; nuestra entrada del glosario sobre parsing de HTML explica por qué la estructura de la página siempre es un objetivo en movimiento.
IMPORTDATA — la subestimada
IMPORTDATA(url) ingiere cualquier URL que devuelva CSV o TSV — sin XPath, sin índices, solo datos estructurados directo a las celdas:
=IMPORTDATA("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip?format=csv")
=IMPORTDATA("https://data.cityofnewyork.us/api/views/erm2-nwe9/rows.csv?$limit=500")
Los portales de datos abiertos gubernamentales, los feeds de referencia financiera y muchos endpoints de exportación de SaaS publican URLs de CSV directas, e IMPORTDATA las trata como una fuente de datos nativa. Antes de escribir cualquier XPath, revisa si el sitio ofrece silenciosamente un endpoint CSV — es la diferencia entre analizar una página y simplemente leer la respuesta. (Esa distinción — analizar páginas renderizadas frente a consumir endpoints estructurados — es el tema central de web scraping vs API.)
Dónde fallan las tres
- Sin renderizado de JavaScript. Las fórmulas leen el HTML crudo que devuelve el servidor en la primera solicitud. Los sitios que hidratan su contenido del lado del cliente — Amazon, resultados de Google, la mayoría de los paneles y sitios de comercio electrónico — le entregan a Sheets una cáscara vacía, así que la fórmula devuelve
#N/A, «Imported content is empty» o nada. - La actualización es superficial. Según la documentación de Google,
IMPORTDATA,IMPORTHTMLeIMPORTXMLbuscan actualizaciones aproximadamente cada hora, y solo mientras el documento está abierto. Una hoja cerrada nunca se actualiza, y simplemente reabrirla no fuerza una nueva obtención de datos. - Límites de tráfico y la celda atascada en «Loading...». Google impone límites de uso en las funciones de importación, calculados sobre todos los documentos abiertos de un mismo creador. El techo práctico que se cita habitualmente ronda las 50 fórmulas de importación por hoja de cálculo antes de que las cosas se degraden, y un uso intenso produce la célebre celda que dice «Loading...» para siempre — la propia guía de Google para el error «Loading data may take a while because of the large number of requests» consiste simplemente en usar menos funciones de importación. No hay una cola que puedas inspeccionar ni un botón de reintento; el modo de falla es el silencio. Las fórmulas de importación también se niegan a referenciar funciones volátiles como
NOW()oRANDBETWEEN()(conTODAY()como única excepción), lo que mata el truco clásico de forzar la actualización con caché. - El buscador de Google termina bloqueado. Cada fórmula de importación obtiene datos desde la infraestructura compartida de Google — los mismos rangos de IP para todos los usuarios de Sheets del planeta. Los sitios con cualquier capa de limitación de tasa o antibots han visto esas IPs mil millones de veces y las bloquean en bloque, por lo que una página que carga bien en tu navegador aún puede devolver
#N/Aen Sheets incluso cuando es completamente estática.
Veredicto: insuperables para tablas estáticas, feeds CSV y metadatos en sitios cooperativos. En el momento en que necesites un horario, una página renderizada con JavaScript o un sitio protegido, sube de nivel.
Nivel 2: Apps Script — UrlFetchApp más un disparador basado en tiempo
Apps Script es el escalón que la mayoría de los usuarios de hojas de cálculo nunca toma, y arregla gratis dos de las cuatro paredes: programación real y manejo real de errores. UrlFetchApp hace solicitudes HTTP con encabezados personalizados, y un disparador basado en tiempo vuelve a ejecutar tu función según un reloj — incluso con la hoja cerrada, algo que ninguna fórmula de importación puede hacer.
Abre tu hoja, ve a Extensiones → Apps Script, y pega este script completo. Llama a una API JSON (aquí, el endpoint de búsqueda de Amazon de Crawlora — cualquier endpoint JSON funciona igual), verifica el código de estado y reescribe una pestaña llamada results:
function refreshSheet() {
const API_KEY = PropertiesService.getScriptProperties()
.getProperty("CRAWLORA_API_KEY");
const url =
"https://api.crawlora.net/api/v1/amazon/search?k=" +
encodeURIComponent("mechanical keyboard");
const resp = UrlFetchApp.fetch(url, {
headers: { "x-api-key": API_KEY },
muteHttpExceptions: true, // devuelve la respuesta en vez de lanzar una excepción
});
if (resp.getResponseCode() !== 200) {
console.error("Fetch failed: HTTP " + resp.getResponseCode());
return;
}
const rows = JSON.parse(resp.getContentText()).data;
const header = ["asin", "title", "price", "rating", "review_count", "link"];
const values = rows.map((r) =>
header.map((h) => (r[h] == null ? "" : r[h]))
);
const sheet =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("results");
sheet.clearContents();
sheet.getRange(1, 1, 1, header.length).setValues([header]);
if (values.length) {
sheet.getRange(2, 1, values.length, header.length).setValues(values);
}
}
function installTrigger() {
ScriptApp.newTrigger("refreshSheet").timeBased().everyHours(6).create();
}
Tres notas de configuración. Guarda la clave de API en Project Settings → Script properties como CRAWLORA_API_KEY — nunca pegues secretos en celdas o código. Ejecuta installTrigger() una vez desde el editor (o configura lo mismo en el panel de Triggers), y Google llamará a refreshSheet() aproximadamente cada seis horas; los disparadores basados en tiempo se comportan como cron, aunque Google aleatoriza el minuto exacto dentro de la ventana para repartir la carga. Y mantén muteHttpExceptions: true — convierte una obtención fallida en un código de estado que puedes registrar y manejar, en lugar de una excepción roja a las 3 a.m.
Los límites, con honestidad: las solicitudes de Apps Script expiran a los seis minutos, las URLs están limitadas a 2082 caracteres, las cuentas de consumidor tienen cuotas diarias acotadas de UrlFetch y de disparadores, y — la grande — las solicitudes siguen originándose desde los rangos de IP de Google. Contra un sitio protegido, Apps Script se bloquea exactamente igual que le pasaba a IMPORTXML, porque para el sitio objetivo es el mismo rastreador tocando la puerta. Apps Script resuelve la programación y el JSON, no el acceso. Precisamente por eso el script anterior llama a una API en lugar de a una página cruda.
Nivel 3: Python + API de scraping + gspread
Cuando el sitio objetivo se renderiza con JavaScript, rota su marcado o bloquea activamente a los rastreadores, deja de obtener páginas y empieza a obtener respuestas. Una API de scraping hace el renderizado, la rotación de proxies y los reintentos de su lado, y devuelve el mismo JSON normalizado incluso cuando el sitio se rediseña — y gspread convierte ese JSON en filas de la hoja en tres líneas. Esta es la configuración que sobrevive al contacto con producción. (Construir el scraper tú mismo en su lugar es un camino legítimo — el pilar web scraping con Python cubre el kit de herramientas completo para hacerlo tú mismo — pero para un pipeline de hoja de cálculo, la ruta de la API elimina el mantenimiento.)
Configuración única de Google, cinco minutos: en Google Cloud Console crea un proyecto, habilita la Google Sheets API y la Google Drive API, crea una cuenta de servicio, descarga su clave JSON, y comparte tu hoja de cálculo con el correo de la cuenta de servicio (termina en iam.gserviceaccount.com) exactamente como compartirías con un colega. Luego:
pip install requests gspread
import requests
import gspread
from datetime import datetime, timezone
API_KEY = "YOUR_API_KEY" # nivel gratuito: 2000 créditos al mes, sin tarjeta
resp = requests.get(
"https://api.crawlora.net/api/v1/amazon/search",
params={"k": "mechanical keyboard"},
headers={"x-api-key": API_KEY},
timeout=60,
)
resp.raise_for_status()
items = resp.json()["data"] # tarjetas de resultado normalizadas
gc = gspread.service_account(filename="service-account.json")
ws = gc.open("Keyboard price tracker").worksheet("log")
stamp = datetime.now(timezone.utc).isoformat(timespec="seconds")
rows = [
[stamp, it["asin"], it["title"], it["price"], it["rating"], it["link"]]
for it in items
]
ws.append_rows(rows, value_input_option="USER_ENTERED")
print(f"Appended {len(rows)} rows at {stamp}")
Nota la diferencia de diseño frente a la versión de Apps Script: esta agrega filas con marca de tiempo en lugar de sobrescribir, así que la hoja se convierte en un historial de precios que puedes graficar y pivotar, no solo una instantánea. Cambia la palabra clave k, o cambia el endpoint por completo — el catálogo de plataformas lista cada sitio compatible, los docs especifican los campos de cada endpoint, y el playground te deja disparar una solicitud de prueba en el navegador antes de escribir una línea de código. Como el precio se paga solo por éxito — se te cobra únicamente por respuestas 2xx exitosas —, una obtención bloqueada o fallida cuesta un reintento, no créditos. Prográmalo con cron o un workflow de GitHub Actions; la guía hermana de Excel tiene un YAML completo de Actions listo para copiar y pegar que funciona igual aquí. Y sea cual sea el horario, mantenlo educado — una extracción diaria es crawling hecho de forma responsable; un bucle a martillazos es cómo las IPs terminan en listas de bloqueo.
¿Qué nivel deberías usar?
| Fórmulas de importación | Apps Script + disparador | Python + API de scraping | |
|---|---|---|---|
| Tiempo de configuración | Menos de un minuto | 15–30 minutos | Alrededor de 15 minutos |
| Sitios con mucho JavaScript | No | Solo vía una API | Sí |
| Sitios protegidos contra bots | No (IPs compartidas de Google) | No (mismas IPs de Google) | Sí (lo maneja la API) |
| Actualización con la hoja cerrada | Nunca | Sí — disparador basado en tiempo | Sí — cron / Actions |
| Visibilidad de errores | «Loading...» atascado / #N/A | Códigos de estado, logs | Completa: excepciones, reintentos, alertas |
| Sobrevive a rediseños del sitio | No (XPath se rompe) | Sí con JSON de la API | Sí (JSON normalizado) |
| Costo | Gratis | Gratis (limitado por cuota) | Nivel gratuito de 2000 créditos al mes; pago solo por éxito |
Por perfil:
- Analista con una tabla estática y una fecha límite:
IMPORTHTML, listo en sesenta segundos. Revisa primero si hay un endpoint CSV y usaIMPORTDATAsi existe. - Persona de operaciones que necesita una página actualizada a diario en una hoja compartida: Apps Script con un disparador basado en tiempo. Es la opción genuinamente programada más barata, y el script de arriba es todo lo que necesitas.
- Cualquiera que haga scraping de sitios dinámicos, protegidos o muchos sitios a la vez: Python + API + gspread. Es el único nivel donde el problema del sitio bloqueado es trabajo de alguien más.
- Equipo que ya vive en Excel: el mismo pipeline, con un último tramo distinto — la guía de Excel cubre Power Query y
to_excel().
- ¿Los datos están en el HTML inicial del servidor? Prueba primero IMPORTHTML o IMPORTXML — verifica tu XPath en DevTools con $x().
- ¿Existe un endpoint CSV? IMPORTDATA le gana a XPath cada vez que existe uno.
- ¿Necesitas actualización con la hoja cerrada? Las fórmulas de importación no pueden hacerlo — usa un disparador basado en tiempo de Apps Script.
- ¿El sitio se renderiza con JavaScript o bloquea el buscador de Google? Enrútalo por una API de scraping y escribe filas con gspread.
- Guarda las claves de API en Script properties o en secretos de entorno, nunca en celdas, y mantén una cadencia de obtención educada.
Convierte cualquier sitio compatible en una hoja de Google viva
Una sola solicitud GET devuelve JSON normalizado — Apps Script o gspread hacen el resto. 2000 créditos gratis al mes, sin tarjeta, y pago solo por éxito: se te cobra únicamente por respuestas 2xx exitosas.
Lectura relacionada: la guía hermana de Excel para Power Query y el lado del libro de trabajo de este pipeline, el pilar web scraping con Python para el kit de herramientas completo para hacerlo tú mismo, y web scraping vs API para saber cuándo gana cada enfoque.
Preguntas frecuentes
How do I scrape a website into Google Sheets?
Start with the built-in formulas: IMPORTHTML pulls the Nth table or list off a page, IMPORTXML extracts specific elements with an XPath query, and IMPORTDATA ingests any public CSV or TSV URL. If the page is JavaScript-rendered, needs a schedule, or blocks Google's fetcher, escalate to Apps Script with UrlFetchApp or a Python script that calls a scraping API and writes rows with gspread.
Why does IMPORTXML say Loading... or return #N/A?
A cell stuck on Loading... usually means you have hit Google's traffic limits for import functions — the fix is fewer import formulas across your open spreadsheets. #N/A or an empty result usually means the element is created by JavaScript after page load (Sheets only reads the initial server HTML) or the site is blocking Google's shared fetcher IPs.
Can Google Sheets scrape JavaScript-rendered pages?
No. IMPORTHTML, IMPORTXML, and IMPORTDATA read only the raw HTML the server returns on first request, with no browser rendering step. Sites that hydrate content client-side — Amazon, Google results, most modern web apps — return empty results. The workaround is to fetch a scraping API's normalized JSON via Apps Script UrlFetchApp or a Python script instead.
How many IMPORT functions can one Google Sheet have?
Google documents strict traffic-based limits calculated across all of a creator's open documents rather than a fixed number, but the commonly cited practical ceiling is around 50 import formulas per spreadsheet before cells start sticking on Loading... . Past that point, consolidate formulas or move the fetching into Apps Script or an external script.
How do I auto-refresh scraped data in Google Sheets?
Import formulas re-check roughly hourly but only while the sheet is open — a closed sheet never refreshes. For a real schedule, use an Apps Script time-driven trigger (for example ScriptApp.newTrigger().timeBased().everyHours(6)) to re-run a UrlFetchApp fetch, or run a Python + gspread script on cron or GitHub Actions; both work with the sheet closed.
What is the difference between IMPORTHTML, IMPORTXML, and IMPORTDATA?
IMPORTHTML grabs an entire table or list by its position on the page. IMPORTXML runs an XPath query, so it can extract one specific element, attribute, or set of nodes. IMPORTDATA fetches a URL that returns CSV or TSV and loads it straight into cells. Check for a CSV endpoint first — IMPORTDATA is the most robust of the three when one exists.
Do I need a paid tool to scrape into Google Sheets?
Not for static pages — the import formulas and Apps Script are free. For JavaScript-heavy or bot-protected sites you need a scraping backend, but that can start free too: Crawlora's free tier includes 2,000 credits/month with no card, and pricing is pay-on-success, so you are charged only for successful 2xx responses.