La extracción de datos de internet suele asociarse con Python o con servicios especializados. Pero si los datos deben acabar directamente en una tabla, calcularse con fórmulas y mostrarse a los compañeros, Excel junto con VBA sigue siendo una de las vías más rápidas para obtener el resultado. No hay que instalar un intérprete, configurar un entorno ni explicar en contabilidad qué es pip install. Abra el libro, pulse un botón — y los datos están en la hoja.
En este artículo desmontamos cómo funciona el scraping con VBA: qué objetos usar para las peticiones HTTP, cómo parsear HTML y JSON, cómo volcar el resultado en las celdas y cómo no toparse con un bloqueo. Los ejemplos son funcionales: puede copiarlos en el editor de VBA y ejecutarlos.
Cuándo Excel y VBA son una buena elección
Merece la pena recurrir a VBA cuando:
- el resultado va a vivir de todos modos en Excel (un informe, un panel, un registro de cotizaciones);
- el volumen de datos es pequeño o medio — decenas o miles de filas, no millones;
- se necesita una automatización «de un botón» para personas sin conocimientos de programación;
- la fuente entrega los datos mediante una petición HTTP simple o una API abierta.
Si lo que hace falta es una escala seria, sortear protecciones complejas de JavaScript o lanzar peticiones en paralelo, es mejor mirar hacia Python (requests, BeautifulSoup, Playwright). En ese tipo de tareas, VBA toca techo enseguida.
Las herramientas dentro de VBA
Para hacer scraping en VBA existen varios «motores» principales:
| Objeto | Función | Cuándo aplicarlo |
|---|---|---|
MSXML2.XMLHTTP / ServerXMLHTTP |
Peticiones HTTP | La vía principal para obtener la respuesta del servidor |
WinHttp.WinHttpRequest.5.1 |
Peticiones HTTP | Alternativa con timeouts configurables |
HTMLDocument (MSHTML) |
Parseo de HTML | Cuando hay que extraer elementos por etiquetas/clases |
RegExp (VBScript) |
Expresiones regulares | Extracción puntual dentro del texto |
Split / InStr / Mid |
Funciones de cadena | Parseo simple de JSON y de texto sin bibliotecas |
QueryTables / Power Query |
Tablas ya montadas | Cuando la página entrega una tabla HTML limpia |
La mayoría de estos objetos se instancian «al vuelo» con CreateObject, es decir, no exigen añadir referencias al proyecto a mano. Es una comodidad: el libro funciona en cualquier máquina con Excel.
La petición HTTP básica
El scraper más simple se limita a obtener el texto de una página. Esta función hace una petición GET y devuelve el HTML o el JSON como cadena:
Function GetResponse(ByVal url As String) As String
Dim http As Object
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "GET", url, False
' Nos hacemos pasar por un navegador normal: muchos sitios cortan las peticiones sin User-Agent
http.setRequestHeader "User-Agent", _
"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
http.send
If http.Status = 200 Then
GetResponse = http.responseText
Else
GetResponse = "ERROR: " & http.Status & " " & http.statusText
End If
Set http = Nothing
End FunctionEl tercer argumento de Open — False — indica una petición síncrona: el código espera la respuesta. Para la mayoría de las tareas es suficiente. La cabecera User-Agent es crítica: sin ella, una parte de los servidores devuelve un 403 o un captcha.
Parseo de JSON sin bibliotecas
VBA no sabe parsear JSON «de fábrica», pero para respuestas simples bastan las funciones de cadena. Supongamos que la API devolvió:
{"price": 152.34, "currency": "USD", "symbol": "AAPL"}El valor de un campo puede extraerse con una función pequeña:
Function ExtractJsonValue(ByVal json As String, ByVal key As String) As String
Dim pattern As String
Dim startPos As Long, endPos As Long
pattern = """" & key & """:"
startPos = InStr(json, pattern)
If startPos = 0 Then Exit Function
startPos = startPos + Len(pattern)
' Saltamos la comilla si el valor es una cadena
If Mid(json, startPos, 1) = """" Then startPos = startPos + 1
' El final del valor es una coma, una llave de cierre o una comilla
endPos = startPos
Do While endPos <= Len(json)
Dim ch As String
ch = Mid(json, endPos, 1)
If ch = "," Or ch = "}" Or ch = """" Then Exit Do
endPos = endPos + 1
Loop
ExtractJsonValue = Trim(Mid(json, startPos, endPos - startPos))
End FunctionEste enfoque funciona con objetos planos. Si la estructura es anidada y compleja, es mejor incorporar un parser de JSON ya hecho para VBA (por ejemplo, el módulo abierto VBA-JSON de Tim Hall) — convierte la respuesta en Dictionary y Collection, con los que se trabaja con mucha más comodidad.
La cadena «petición HTTP → parseo del JSON → escritura en la celda» es el patrón básico sobre el que se construye el scraping de tipos de cambio. La mayoría de los servicios de los bancos centrales — incluido el feed de tipos de referencia del BCE — y de las APIs de divisas devuelven precisamente JSON o XML, y la función de extracción de valores descrita arriba cubre el 80% de los casos. El análisis detallado de una solución completa con actualización automática por temporizador está en el artículo «Scraping de tipos de cambio».
Parseo de HTML con MSHTML
Cuando los datos no están en una API sino directamente en el marcado de la página, resulta cómodo el objeto HTMLDocument. Permite buscar elementos igual que en el navegador — por id, etiquetas y clases.
Function ParseHtmlElement(ByVal url As String, ByVal elementId As String) As String
Dim http As Object, htmlDoc As Object
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "GET", url, False
http.setRequestHeader "User-Agent", "Mozilla/5.0"
http.send
Set htmlDoc = CreateObject("htmlfile")
htmlDoc.body.innerHTML = http.responseText
Dim el As Object
Set el = htmlDoc.getElementById(elementId)
If Not el Is Nothing Then
ParseHtmlElement = Trim(el.innerText)
End If
Set http = Nothing
Set htmlDoc = Nothing
End FunctionSi hay que extraer varios elementos por clase o etiqueta, conviene recorrer la colección:
Sub ParseAllRows(ByVal url As String)
Dim http As Object, htmlDoc As Object
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "GET", url, False
http.setRequestHeader "User-Agent", "Mozilla/5.0"
http.send
Set htmlDoc = CreateObject("htmlfile")
htmlDoc.body.innerHTML = http.responseText
Dim rows As Object, i As Long
Set rows = htmlDoc.getElementsByTagName("tr")
For i = 0 To rows.Length - 1
' Escribimos el texto de cada fila de la tabla en la hoja, a partir de la fila 2
Cells(i + 2, 1).Value = Trim(rows.Item(i).innerText)
Next i
Set http = Nothing
Set htmlDoc = Nothing
End SubExpresiones regulares
A veces el valor buscado está incrustado en el texto sin un envoltorio cómodo. En ese caso, RegExp saca del apuro:
Function ExtractByRegex(ByVal text As String, ByVal pattern As String) As String
Dim re As Object
Set re = CreateObject("VBScript.RegExp")
re.Global = False
re.IgnoreCase = True
re.pattern = pattern
Dim matches As Object
Set matches = re.Execute(text)
If matches.Count > 0 Then
' Devolvemos el primer grupo de captura
ExtractByRegex = matches(0).SubMatches(0)
End If
Set re = Nothing
End Function
' Ejemplo: extraer el número de una cadena tipo "Precio: 152.34 EUR"
' value = ExtractByRegex(s, "Precio:\s*([\d\.]+)")Escritura del resultado en la hoja
Escribir los datos celda a celda es lento. Si las filas son muchas, acumúlelas en un array y vuélquelas con una sola asignación:
Sub WriteArrayFast(data() As Variant)
Dim n As Long
n = UBound(data) - LBound(data) + 1
' Volcamos la columna entera en una sola operación
Range("A1").Resize(n, 1).Value = Application.Transpose(data)
End SubEste volcado es decenas de veces más rápido que un bucle que escribe en cada celda, sobre todo con la actualización de pantalla desactivada:
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' ... scraping y escritura ...
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = TrueEjemplo práctico: una tabla de cotizaciones
Montemos un pequeño scraper que recorre una lista de tickers, solicita el precio a una API ficticia y vuelca el resultado en la hoja.
Sub ParseQuotes()
Dim tickers As Variant
tickers = Array("AAPL", "MSFT", "GOOGL", "TSLA")
Dim i As Long, url As String, response As String, price As String
' Encabezados de la tabla
Cells(1, 1).Value = "Ticker"
Cells(1, 2).Value = "Precio"
Cells(1, 3).Value = "Hora"
Application.ScreenUpdating = False
For i = LBound(tickers) To UBound(tickers)
url = "https://example-api.com/quote?symbol=" & tickers(i)
response = GetResponse(url) ' la función de la sección anterior
price = ExtractJsonValue(response, "price")
Cells(i + 2, 1).Value = tickers(i)
Cells(i + 2, 2).Value = Val(price)
Cells(i + 2, 3).Value = Now
' Pausa entre peticiones para no cargar el servidor y no ganarse un bloqueo
Application.Wait Now + TimeValue("0:00:01")
Next i
Application.ScreenUpdating = True
MsgBox "Listo: se cargaron " & (UBound(tickers) + 1) & " cotizaciones", vbInformation
End SubEs un esqueleto simplificado. En la práctica, para la extracción de cotizaciones bursátiles se añaden el parseo del volumen negociado, de la variación porcentual y de los históricos, además del tratamiento de los fines de semana y de las horas de cierre de la bolsa. La implementación completa, con actualización automática y formato condicional, está en el artículo «Extracción de cotizaciones bursátiles».
Manejo de errores y robustez
Las peticiones de red fallan: el servidor no responde, salta el timeout, llega un formato inesperado. El scraper tiene que sobrevivir a todo eso, no morir en el primer error.
Function SafeGet(ByVal url As String, Optional retries As Long = 3) As String
Dim attempt As Long
For attempt = 1 To retries
On Error Resume Next
Dim http As Object
Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
http.SetTimeouts 5000, 5000, 10000, 10000 ' resolve, connect, send, receive
http.Open "GET", url, False
http.setRequestHeader "User-Agent", "Mozilla/5.0"
http.send
If Err.Number = 0 And http.Status = 200 Then
SafeGet = http.responseText
On Error GoTo 0
Exit Function
End If
On Error GoTo 0
' La pausa antes del siguiente intento crece cada vez
Application.Wait Now + TimeValue("0:00:0" & attempt)
Next attempt
SafeGet = "" ' se agotaron todos los intentos
End FunctionEl objeto WinHttpRequest resulta aquí más cómodo que XMLHTTP precisamente por su método SetTimeouts: permite fijar de forma explícita los límites de espera y no quedarse colgado para siempre.
Ética y limitaciones
Unas cuantas reglas que ahorran nervios y reputación:
- Lea
robots.txty las condiciones de uso. No todos los sitios permiten la recogida automatizada de datos. - Haga pausas entre peticiones. Decenas de peticiones por segundo parecen un ataque y llevan al baneo de la IP.
- Prefiera las APIs oficiales. Si la fuente tiene API, úsela: es más estable y legal.
- No extraiga datos personales sin base legal ni consentimiento.
- Cachee el resultado. Si el tipo de cambio se actualiza una vez al día, no hace falta castigar al servidor cada minuto.
La alternativa sin código: Power Query
Conviene recordar que para muchas tareas VBA ni siquiera es necesario. El Power Query integrado en Excel (Datos → Obtener datos → Desde la web) sabe cargar tablas HTML y respuestas JSON desde la interfaz, con actualización automática programada. Si la fuente entrega una tabla limpia o una API REST sin autenticación enrevesada, Power Query resolverá la tarea más rápido y sin una sola línea de código. VBA queda para los casos en que hacen falta lógica, ramificaciones, bucles sobre una lista y un parseo fuera de lo estándar.
Conclusión
El scraping con VBA se construye con unos pocos ladrillos: la petición HTTP (XMLHTTP o WinHttp), el parseo de la respuesta (funciones de cadena, RegExp o HTMLDocument), la escritura en las celdas y el manejo de errores. Una vez dominado ese conjunto, se puede automatizar la recogida de casi cualquier dato tabular sin salir del libro de Excel de siempre.
Dos escenarios clásicos con los que resulta cómodo practicar:
- Scraping de tipos de cambio — una fuente JSON/XML sencilla, ideal para el primer scraper.
- Extracción de cotizaciones bursátiles — algo más compleja: lista de tickers, actualizaciones frecuentes, formato.
Ambos están desarrollados en artículos aparte — empiece por el que más se parezca a su tarea; las funciones descritas aquí (GetResponse, ExtractJsonValue, SafeGet) serán el fundamento común de los dos.