🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

VBA HTTP Request en Excel — GET y POST con XMLHTTP, y por qué la respuesta llega desactualizada o con caracteres extraños

|

VBA HTTP Request en Excel — GET y POST con XMLHTTP, y por qué la respuesta llega desactualizada o con caracteres extraños

TL;DR — Una petición HTTP desde VBA te devuelve exactamente dos cosas: un código de estado y un bloque de bytes. No te dice si los datos están al día, qué codificación de texto usan esos bytes ni si la petición llegó siquiera a tiempo al servidor. Eso es cosa tuya. Elige el objeto según la red que tiene que atravesar (MSXML2.XMLHTTP usa la configuración y la caché del navegador del usuario; ServerXMLHTTP y WinHttpRequest te dan tiempos de espera y no tienen caché), comprueba tú mismo el estado, porque un 404 o un 500 nunca provoca un error, y decodifica los bytes a conciencia cuando el servidor envía algo que no sea UTF-8.

Dim http As Object
Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
http.setTimeouts 5000, 5000, 10000, 30000
http.Open "GET", "https://api.example.com/rates?base=EUR", False
http.send
If http.Status = 200 Then Debug.Print http.responseText

Este es el primer artículo de una serie sobre cómo traer datos de la web a Excel: aquí, la petición en sí; después, VBA JSON para convertir la respuesta en una estructura, y VBA web scraping para páginas que nunca se pensaron para que las leyera un programa. La idea de la serie es que una petición web te entrega texto, no datos. Entre el servidor y tus celdas hay tres traducciones que son responsabilidad tuya: de bytes a texto, de texto a estructura y de estructura a cuadrícula. Casi todos los fallos se producen en una de esas fronteras, no en la petición.

Lo que aprenderás

  • El modelo mental: un estado y unos bytes
  • Tres objetos, tres pilas de red
  • Dos tipos de fallo: errores que saltan y estados que no
  • Por qué un GET devuelve los datos de ayer
  • Tiempos de espera, y por qué una petición puede congelar Excel
  • Acentos ilegibles: decodificar tú mismo los bytes
  • Enviar un POST con un cuerpo JSON
  • Cadenas de consulta y claves de API

El modelo mental: un estado y unos bytes

Cuando llamas a http.send, VBA entrega la petición a una biblioteca de red de Windows y espera. Lo que vuelve es poco: un número (Status, como 200 o 404), algunos encabezados y el cuerpo en forma de bytes. responseText no es el cuerpo; es el cuerpo ya decodificado como texto a partir de una suposición. responseBody son los bytes en bruto.

Quédate con esa imagen y los fallos raros dejan de ser raros. Datos desactualizados significan que respondió en su lugar una capa situada entre tú y el servidor. Texto ilegible significa que la suposición al decodificar fue errónea. Una macro que «ayer funcionaba» y ahora se queda colgada significa que la red se volvió más lenta y nada le dijo a la petición que se rindiera.

Tres objetos, tres pilas de red

Los tres objetos que la gente copia de los foros parecen intercambiables. No lo son: cada uno se apoya en una pila de red de Windows distinta, y esa pila decide cómo se comporta la petición en el equipo de tu usuario.

Objeto Pila de red Caché Proxy e inicio de sesión Tiempos de espera
MSXML2.XMLHTTP.6.0 WinINet (la pila que hay detrás de la antigua configuración del navegador) sí, las respuestas GET pueden quedar en caché usa automáticamente la configuración de Internet de Windows del usuario no tiene método setTimeouts
MSXML2.ServerXMLHTTP.6.0 WinHTTP no la configuración de proxy propia de WinHTTP, que a menudo está vacía; setProxy para definir uno setTimeouts
WinHttp.WinHttpRequest.5.1 WinHTTP no igual que el anterior; SetProxy SetTimeouts, además de opciones como las versiones de TLS

Esa tabla explica la incidencia de soporte más habitual: «en mi portátil funciona y en la oficina del cliente falla». La oficina sale a la web a través de un proxy. XMLHTTP toma ese proxy de la configuración del usuario; ServerXMLHTTP y WinHttpRequest no, así que no consiguen conectar mientras el navegador del mismo equipo funciona sin problemas. Indícales el proxy de forma explícita:

http.setProxy 2, "proxy.company.local:8080"     ' 2 = usar este proxy

Los tres se crean con CreateObject y no necesitan ninguna referencia, así que el mismo código funciona en Office de 32 y de 64 bits. Consulta VBA CreateObject para el enlace tardío (late binding) en general.

Dos tipos de fallo: errores que saltan y estados que no

Una petición puede fallar en dos sitios, y VBA te lo comunica por dos canales distintos.

Los fallos de transporte provocan un error en tiempo de ejecución. Sin red, un nombre de servidor que no se resuelve, un tiempo de espera agotado, un protocolo de enlace TLS que falla: send se detiene con un error en tiempo de ejecución, normalmente un número negativo grande como -2147012889 (no se pudo resolver el nombre del servidor) o -2147012894 (se agotó el tiempo de espera de la operación). Captúralos con On Error.

Los fallos HTTP no provocan nada. Un 404, un 401 porque la clave ha caducado, un 500 porque el servidor se ha estropeado: send termina con normalidad, Status contiene el código y responseText contiene una página de error o un JSON de error. El código que se salta la comprobación del estado escribe tan tranquilo «Unauthorized» o una página de HTML en tu hoja.

Así que cada petición necesita las dos comprobaciones. Ponlas en una sola función y no llames nunca a send en ningún otro sitio:

Function HttpGet(ByVal url As String) As String
    Dim http As Object
    Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    http.setTimeouts 5000, 5000, 10000, 30000
    http.Open "GET", url, False
    http.setRequestHeader "Accept", "application/json"
    http.send                                   ' aqui saltan los errores de transporte
    If http.Status < 200 Or http.Status >= 300 Then
        Err.Raise vbObjectError + 513, "HttpGet", _
            "HTTP " & http.Status & " " & http.statusText & " for " & url
    End If
    HttpGet = http.responseText
End Function

Ahora los dos tipos de fallo llegan como un error en tiempo de ejecución con un mensaje legible, y quien llama a la función los gestiona en un solo sitio. Consulta control de errores en VBA para el patrón que la rodea.

Por qué un GET devuelve los datos de ayer

MSXML2.XMLHTTP pasa por WinINet, y WinINet mantiene una caché. Si la respuesta del servidor permite guardarla en caché, o no dice nada al respecto, un GET repetido a la misma URL puede responderse desde la caché sin llegar siquiera al servidor. La macro se ejecuta, el estado es 200 y los tipos de cambio son de esta mañana.

Tres salidas, de mejor a peor:

  1. Usa ServerXMLHTTP o WinHttpRequest, que no tienen caché.
  2. Si tienes que quedarte con XMLHTTP (por cómo maneja el proxy), obliga a la caché a volver a preguntar al servidor enviando una fecha muy antigua: http.setRequestHeader "If-Modified-Since", "Sat, 01 Jan 2000 00:00:00 GMT".
  3. Haz que cada URL sea única con un parámetro desechable, como "&_=" & Format(Now, "yyyymmddhhnnss"). Funciona, pero algunas API rechazan los parámetros que no conocen.

La señal inequívoca de la caché es una petición que vuelve al instante con exactamente los mismos datos que la última vez, mientras que la misma URL en un navegador muestra algo más reciente.

Tiempos de espera, y por qué una petición puede congelar Excel

El False de http.Open "GET", url, False hace que la petición sea síncrona: VBA se queda esperando en esa línea hasta que llega la respuesta. Mientras espera, Excel no puede redibujarse ni responder, y Windows marca la ventana como No responde. Una API lenta se convierte en un Excel congelado; una caída, sin tiempo de espera, puede dejarlo congelado mucho tiempo.

setTimeouts recibe cuatro valores en milisegundos: resolución de nombres, conexión, envío y recepción. Ajústalos a cifras que encajen con la API. 5000, 5000, 10000, 30000 significa: ríndete si el servidor no se encuentra o no se alcanza en cinco segundos, o si no ha respondido en treinta. XMLHTTP no tiene ese método, lo que es un motivo más para preferir ServerXMLHTTP en todo aquello que un usuario tenga que esperar.

En un bucle con muchas peticiones, actualiza la barra de estado entre llamadas para que el usuario vea el progreso. Existen las peticiones asíncronas (True como tercer argumento), pero sondearlas desde VBA requiere un bucle con DoEvents y añade más formas de fallar de las que elimina; mantén las peticiones síncronas y cortas.

Acentos ilegibles: decodificar tú mismo los bytes

responseText decodifica los bytes con el juego de caracteres que el servidor declara en su encabezado Content-Type, y supone UTF-8 cuando el servidor no declara ninguno. La mayoría de las API modernas envían UTF-8 y lo indican, y todo funciona. Un servidor antiguo que envía Windows-1252, ISO-8859-1 o Shift_JIS sin decirlo llega como München o como signos de interrogación y recuadros.

Cuando ocurra, olvídate de responseText, toma los bytes en bruto y decodifícalos con el juego de caracteres correcto:

Function BytesToText(ByVal bytes As Variant, ByVal charset As String) As String
    With CreateObject("ADODB.Stream")
        .Type = 1                  ' binario
        .Open
        .Write bytes
        .Position = 0
        .Type = 2                  ' texto
        .Charset = charset         ' "windows-1252", "iso-8859-1", "shift_jis"
        BytesToText = .ReadText
        .Close
    End With
End Function

body = BytesToText(http.responseBody, "windows-1252")

Una trampa mientras depuras esto: no juzgues el texto en la ventana Inmediato. El editor de VBA solo puede mostrar caracteres de la página de códigos del sistema de Windows, así que un texto japonés correcto aparece como ??? en un Windows en español, y un texto en español correcto puede verse mal en uno japonés. Escribe la cadena en una celda; la celda muestra la verdad.

Enviar un POST con un cuerpo JSON

Un POST es la misma llamada con un método, un tipo de contenido y un cuerpo:

http.Open "POST", "https://api.example.com/orders", False
http.setRequestHeader "Content-Type", "application/json"
http.send "{""sku"":""A-100"",""qty"":3}"

La trampa es construir ese cuerpo pegando números dentro de una cadena. VBA convierte un número en texto según la configuración regional de Windows, así que en tu Windows en español (con la configuración de España; en México, por ejemplo, se usa el punto), igual que en uno alemán o francés, "{""price"":" & 3.5 & "}" produce {"price":3,5}, que no es JSON válido. La misma macro funciona en Nueva York y falla en Madrid. O conviertes los números con Trim$(Str$(x)), que siempre usa el punto (consulta VBA Str), o, mejor aún, construyes el cuerpo con una biblioteca JSON, como se muestra en VBA JSON.

Cadenas de consulta y claves de API

Los valores que van en una URL deben codificarse con porcentajes: un espacio, un ampersand o una letra con diéresis en un parámetro de consulta rompe la petición o la cambia sin avisar. Excel 2013 y posteriores tienen una función para ello, y codifica las letras acentuadas en UTF-8, así que Zürich se convierte en Z%C3%BCrich:

url = "https://api.example.com/search?city=" & _
      Application.WorksheetFunction.EncodeURL(Range("B2").Value)
' B2 = Zurich & Geneva  ->  city=Zurich%20%26%20Geneva

Las claves de API van en un encabezado, normalmente http.setRequestHeader "Authorization", "Bearer " & apiKey. No van en el código. Cualquiera que tenga el libro puede abrir el editor de VBA, y la contraseña de un proyecto VBA no es una protección real. Lee la clave en tiempo de ejecución desde un archivo del perfil del usuario o desde una variable de entorno con Environ, para poder compartir el libro sin compartir la clave.

La decisión de criterio: una función de petición, tres comprobaciones

La mayoría de las macros HTTP rotas no se equivocan con HTTP. Se saltan una de las tres comprobaciones que la biblioteca no hará por ti: ¿llegó a tiempo?, ¿dijo que sí el servidor? y ¿está bien decodificado el texto? Así que escribe una sola función de petición que fije los tiempos de espera, compruebe el estado y decodifique a conciencia, y haz que sea el único sitio del proyecto que llama a send.

Como objeto, usa por defecto MSXML2.ServerXMLHTTP.6.0: sin caché que devuelva datos viejos y con tiempos de espera reales. Cambia a MSXML2.XMLHTTP.6.0 solo cuando los usuarios estén detrás de un proxy que no puedas configurar, y en ese caso añade el encabezado If-Modified-Since. Y si lo único que necesitas es traer la misma tabla en cada actualización, sin ninguna lógica alrededor, plantéate Datos > De la web de Power Query antes de escribir una sola línea de VBA.

Cómo ayuda ExcelMaster

Las macros de API fallan en los equipos de otras personas: un proxy que nunca has visto, un Windows configurado en otro idioma, un servidor que responde despacio los lunes por la mañana.

ExcelMaster escribe el código de la petición para el libro que tiene delante, comprueba el estado y el resultado decodificado antes de que nada llegue a tus celdas, y te muestra lo que la API devolvió de verdad cuando no coincide con lo que esperabas.

Preguntas frecuentes

¿Cómo hago una petición HTTP GET en Excel VBA?

Crea MSXML2.ServerXMLHTTP.6.0 con CreateObject, llama a .Open "GET", url, False y después a .send, comprueba .Status y lee .responseText. No hace falta ninguna referencia.

¿Qué diferencia hay entre XMLHTTP y ServerXMLHTTP?

XMLHTTP usa WinINet: sigue la configuración de proxy del usuario, puede guardar en caché las respuestas GET y no tiene ajuste de tiempo de espera. ServerXMLHTTP usa WinHTTP: sin caché y con tiempos de espera reales, pero puede que tengas que definir un proxy con setProxy.

¿Por qué mi petición VBA devuelve datos antiguos?

MSXML2.XMLHTTP puede responder a un GET repetido desde la caché de WinINet. Cambia a ServerXMLHTTP, o envía una fecha antigua en If-Modified-Since con setRequestHeader antes de send.

¿Por qué VBA no da error con un 404 o un 500?

Porque la petición en sí ha salido bien: el servidor respondió. Solo los fallos de transporte, como no tener red o agotar el tiempo de espera, provocan un error en tiempo de ejecución. Comprueba .Status después de cada send.

¿Por qué el texto de la respuesta sale ilegible?

El servidor envió texto en una codificación que no declaró, y responseText supuso UTF-8. Decodifica .responseBody con ADODB.Stream y el Charset correcto.

Probado en

Probado en: Excel 365 (Windows 11), VBA 7.1 — última verificación el 05/10/2026.

Guías relacionadas: VBA JSON · VBA web scraping · VBA CreateObject · Control de errores en VBA · VBA On Error · VBA Str · VBA Environ · VBA StatusBar