Manejo de Codificación de Caracteres en Node.js al Extraer Páginas Web

  1. Descripción del Problema

Durante el aprendizaje de técnicas de raspado web con Node.js, me encontré con un problema frecuente al utilizar axios para realizar solicitudes HTTP: los caracteres en inglés se mostraban correctamente, pero los caracteres chinos aparecían completamente corruptos. Este es un problema clásico relacionado con la codificación de caracteres.

  1. Primeros Intentos de Solución

2.1 Modificación de la Codificación en axios

Revisé la documentación oficial de axios y noté que utiliza utf8 como codificación predeterminada. Decidí cambiar esto a gbk para ver si resolvía el problema:

async function obtenerDatos(id) {
    return await new Promise(async (resolve) => {
        let respuesta = await axios.request({
            url: 'https://www.ejemplo.com/pagina/' + id + '.html',
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
            },
            responseType: 'text',
            responseEncoding: 'gbk'
        });
        resolve(respuesta.data);
    });
}

Sin embargo, los resultados siguieron siendo insatisfactorios.

2.2 Análisis de Hipótesis

Hipótesis 1: Node.js nativamente solo understande utf8, por lo que incluso devolviendo datos en gbk, el entorno de Node.js los procesa incorrectamente.

Hipótesis 2: Es posible que axios simplemente ignore el parámetro de codificación especificado y devuelva los datos en su formato predeterminado.

2.3 Prueba con el Módulo Request

Decidí utilizar el módulo request para verificar si el comportamiento era diferente:

async function obtenerPagina() {
    return await new Promise((resolve) => {
        Request.get({ url: urlPagina, headers: headers }, (error, estado, datos) => {
            resolve(datos);
        });
    });
}

Request.encoding = 'gbk';
let resultado = obtenerPagina();
resultado.then((html) => {
    console.log(html);
});

  1. Comparación de Resultados

Realicé pruebas sistemáticas comparando diferentes configuraciones:

Biblioteca Codificación Resultado
axios gbk Caracteres corruptos
axios utf-8 Caracteres corruptos
request gbk Caracteres corruptos
request utf-8 Caracteres corruptos

Los resultados fueron consistentes independiente de la biblioteca o codificación seleccionada. Esto sugirió que el problema no estaba en las bibliotecas de solicitud HTTP, sino en la plataforma Node.js misma.

  1. Verificación con Python

Para confirmar mi sospecha, decidí probar con Python utilizando la biblioteca requests:

import requests

url = "https://www.ejemplo.com/pagina/1234.html"
headers = {"User-Agent": "Mozilla/5.0"}

# Intento con UTF-8
respuesta_utf8 = requests.get(url, headers=headers)
print(respuesta_utf8.encoding)  # Muestra: utf-8
print(respuesta_utf8.text)  # Caracteres corruptos

# Intento con GBK
respuesta_gbk = requests.get(url, headers=headers, encoding='gbk')
print(respuesta_gbk.text)  # ¡Perfecto! Caracteres correctos

La prueba con Python generó resultados diferentes: mientras que UTF-8 producía los mismos caracteres corruptos, especificar explícitamente GBK resolvía completamente el problema.

  1. Diagnóstico Final

Conclusión: Las bibliotecas axios y request para Node.js no pueden realizar decodificación GBK debido a las limitaciones nativas del runtime de Node.js. Estas bibliotecas simplemente delegan el manejo de codificación al motor de JavaScript subyacente, que solo soporta un conjunto limitado de codificaciones.

Cuando el servidor responde con datos en GBK y Node.js intenta procesarlos, el motor de V8 no tiene conocimiento de esta codificación y produce caracteres corruptos durante el proceso de decodificación implícita.

  1. Soluciones Propuestas

6.1 Utilizando iconv-lite con request

El módulo iconv-lite es una implementación pura en JavaScript de iconv que soporta múltiples codificaciones, incluyendo GBK y GB2312.

Instalación:

npm install iconv-lite request

Implementación:

const request = require('request');
const iconv = require('iconv-lite');

async function rasparPagina(id) {
    return await new Promise((resolve, reject) => {
        request({
            url: `https://www.ejemplo.com/contenido/${id}.html`,
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
            },
            encoding: null  // Importante: indica que no se decodifique la respuesta
        }, (error, respuesta, bufferDatos) => {
            if (error) {
                reject(error);
                return;
            }
            
            // Decodificar el buffer como GBK
            const contenidoHTML = iconv.decode(bufferDatos, 'gbk');
            resolve(contenidoHTML);
        });
    });
}

rasparPagina('4484').then(html => {
    console.log(html);
});

6.2 Utilizando el Módulo Nativo https con iconv-lite

Para casos donde no queremos depender de bibliotecas externas de solicitud HTTP, podemos utilizar los módulos nativos http o https de Node.js:

const https = require('https');
const iconv = require('iconv-lite');

const url = 'https://www.ejemplo.com/contenido/4484.html';

https.get(url, (respuesta) => {
    const fragmentos = [];

    respuesta.on('data', (fragmento) => {
        fragmentos.push(fragmento);
    });

    respuesta.on('end', () => {
        // Concatenar todos los fragmentos del buffer
        const bufferCompleto = Buffer.concat(fragmentos);
        
        // Decodificar el buffer utilizando GBK
        const htmlDecodificado = iconv.decode(bufferCompleto, 'gb2312');
        
        console.log(htmlDecodificado);
    });
}).on('error', (error) => {
    console.error('Error en la solicitud:', error);
});

6.3 Utilizando el Módulo encodign

Otra alternativa es el módulo encoding, que actúa como un envoltorio para node-iconv e iconv-lite:

npm install encoding

const encoding = require('encoding');

function convertirCodificacion(texto, desdeCharset, haciaCharset) {
    return encoding.convert(texto, haciaCharset, desdeCharset);
}

// Ejemplo de uso
const textoCorrupto = 'ẃ0�2014-2015g��!g���6������g�����?';
const convertido = convertirCodificacion(textoCorrupto, 'GBK', 'UTF-8');

// Nota: encoding.convert() devuelve un Buffer
console.log(convertido.toString());

  1. Acerca de Buffer en Node.js

El módulo Buffer en Node.js es una clase global que maneja datos binarios de manera directa. Fue creado específicamente para procesar flujos de datos como TCP streams o archivos, donde se requiere manipulación de datos binarios.

Cuando procesamos codificaciones no estándar en Node.js, trabajar con Buffers es esencial porque:

  • Permite manejar datos binarios crudos sin intentar interpretarlos
  • Es más eficiente para operaciones de red y archivo
  • Sirve como interfaz entre los datos recibidos y las bibliotecas de conversión de codificación
  1. Resumen

El problema de caracteres corurptos al extraer páginas web en Node.js se debe a que el motor de V8 solo soporta un conjunto limitado de codificaciones de manera nativa. La solución más efectiva es utilizar bibliotecas especializadas como iconv-lite que扩充en las capacidades de codificación del entorno.

El enfoque recomendado consiste en:

  1. Obtener los datos como un Bufferr nativo (estableciendo encoding: null o utilizando módulos HTTP/HTTPS nativos)
  2. Utilizar iconv.decode() para convertir el buffer a la codificación correcta
  3. Procesar el string resultante normalmente

Etiquetas: nodejs web-scraping axios encoding character-encoding

Publicado el 7-6 23:38