Scraping de datos estructurados con PowerShell y Html Agility Pack

Última actualización: 10/10/2025
Autor: Isaac
  • Html Agility Pack permite parsear y consultar HTML con XPath/LINQ de forma robusta.
  • PowerShell y C# cubren scraping estático; Selenium resuelve páginas con JavaScript.
  • CsvHelper e IronPDF facilitan exportar datos a CSV o generar informes en PDF.
  • El uso de proxies reduce bloqueos y posibilita scraping con enfoque regional.

Scraping con PowerShell y HTML Agility Pack

Si buscas una forma práctica de extraer datos estructurados sin volverte loco con expresiones regulares, combinar PowerShell con HTML Agility Pack es una de esas soluciones que te ahorran tiempo y disgustos. Este stack permite navegar por el DOM, localizar nodos por XPath o LINQ y leer texto, atributos o HTML de manera fiable, incluso cuando el marcado no está perfecto.

En las próximas líneas unimos lo mejor de varios enfoques: PowerShell, C# y Selenium para abarcar contenido estático y dinámico, ejemplos reales (como extraer el cuerpo de anuncios en Craigslist), exportación a CSV, e incluso la posibilidad de convertir resultados a PDF con IronPDF. Todo ello con trucos útiles como el uso de proxies para evitar bloqueos y recomendaciones para que tus selectores sean robustos con el paso del tiempo.

Qué es HTML Agility Pack y por qué es tan útil

HTML Agility Pack (HAP) es una biblioteca .NET que parsea HTML en un árbol de nodos sobre el que puedes navegar, consultar y manipular. A diferencia de otros enfoques más frágiles, HAP tolera HTML mal formateado y te permite moverte por el DOM con XPath o LINQ con una API sencilla.

Entre sus puntos fuertes están: análisis indulgente (se traga HTML imperfecto), manipulación del DOM (añadir, borrar o modificar nodos/atributos), compatibilidad con XPath y LINQ, y buen rendimiento incluso en documentos grandes. Además, su diseño es extensible, así que puedes implementar filtros o manejadores a medida cuando haga falta.

Cargar y analizar HTML con HAP: archivo, cadena o web

Para empezar, puedes cargar contenido HTML desde un fichero local, desde una cadena en memoria o directamente desde una URL. La clase clave es HtmlDocument, y para web resulta muy cómodo usar HtmlWeb y su método Load().

// Desde archivo
var doc = new HtmlDocument();
doc.Load(filePath);

// Desde cadena
var doc2 = new HtmlDocument();
doc2.LoadHtml(htmlString);

// Desde la web
var web = new HtmlWeb();
var doc3 = web.Load("http://example.com/");

Una vez cargado el documento, accedes al nodo raíz con DocumentNode. A partir de ahí, puedes seleccionar nodos por XPath o con LINQ, y leer propiedades como OuterHtml, InnerText, Name o la colección de atributos con total comodidad.

Seleccionar y leer nodos: XPath, atributos y limpieza de texto

Con XPath localizarás elementos concretos sin pelearte con el HTML. La librería ofrece SelectSingleNode() para un único resultado y SelectNodes() cuando esperas varios.

// Un solo nodo (por ejemplo, el <title> de la página)
var titleNode = doc.DocumentNode.SelectSingleNode("//head/title");

// Varios nodos (por ejemplo, todos los <article>)
var articles = doc.DocumentNode.SelectNodes("//article");

// Lectura de información útil
var name = titleNode.Name;            // etiqueta del nodo
var html = titleNode.OuterHtml;       // HTML completo del nodo
var text = titleNode.InnerText;       // texto plano del nodo

Cuando el texto contiene entidades HTML, puedes “limpiar” el contenido usando utilidades como HtmlEntity.DeEntitize() o, si prefieres BCL, System.Net.WebUtility.HtmlDecode(). Esto te devuelve un texto más natural, listo para tratar como dato.

// Limpieza de entidades HTML en texto extraído
var limpio = HtmlEntity.DeEntitize(titleNode.InnerText);
// o
var limpio2 = System.Net.WebUtility.HtmlDecode(titleNode.InnerText);

PowerShell + HTML Agility Pack: inspección, métodos y extracción real

Muchos equipos prefieren PowerShell porque permite prototipar scraping muy rápido. Puedes cargar la DLL de HAP (por ejemplo, versión 1.11.59) y usar sus clases desde scripts. Si has trabajado con módulos como PSParseHTML, en realidad estabas usando HAP por debajo.

# Cargar la DLL (ajusta la ruta a tu entorno)
$hapPath = 'C:\ruta\a\HtmlAgilityPack.dll'
[Reflection.Assembly]::LoadFile($hapPath) | Out-Null

# Descargar una página y cargarla en HtmlDocument
$dest = '$env:TEMP\page.htm'
$wc = New-Object System.Net.WebClient
$wc.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$wc.DownloadFile('http://localhost/mihtml.html', $dest)

$doc = New-Object HtmlAgilityPack.HtmlDocument
$doc.Load($dest)
$root = $doc.DocumentNode

# Por ejemplo, recorrer filas de una tabla
$rows = $root.Descendants('tr')
foreach ($row in $rows) {
    $cells = $row.Descendants('td')
    if ($cells.Count -ge 2) {
        Write-Host ($cells[0].InnerText + ' - ' + $cells[1].InnerText)
    }
}

Una duda habitual al explorar objetos en PowerShell es de dónde sale GetAttributeValue() y por qué aparecen varias firmas. En HtmlAgilityPack, los nodos exponen ese método con sobrecargas que aceptan nombre del atributo y un valor por defecto para convertir a string, int, bool, etc. PowerShell puede mostrarlo como un método genérico (T) o varias sobrecargas según el motor de tipado, pero a efectos prácticos lo usarás así:

# Obtener un atributo (con valor por defecto si no existe)
$href = $node.GetAttributeValue('href', $null)
$tabIndex = $node.GetAttributeValue('tabindex', -1)
$esActivo = $node.GetAttributeValue('data-active', $false)

¿Regex para HTML? Mejor no. Si lo que quieres es extraer el cuerpo de un anuncio envuelto en un <section> con un id concreto, es más estable usar xpath que pelear con patrones frágiles. Por ejemplo, para un caso como <section id=’posting body’>…</section>:

# Seleccionar el <section> por id (incluso si hay espacios en el id)
$section = $root.SelectSingleNode("//section[@id='posting body']")
if ($section) {
    $texto = $section.InnerText
}

Este enfoque es limpio y mantenible: si cambia la estructura, ajustas el xpath y listo. Evitas errores típicos de los regex en HTML (anidaciones, espacios, atributos en distinto orden, etc.).

  Cómo eliminar o desvincular un dispositivo conectado a la cuenta de Netflix

Un vistazo rápido con VB.NET y otra muestra en PowerShell

La misma idea se aplica en VB.NET o C#: descargamos el HTML, lo cargamos en HtmlDocument, localizamos filas y celdas, y extraemos su texto con muy poco código.

' VB.NET: recorrer una tabla simple
Using client As New Net.WebClient()
    Dim tmp = IO.Path.GetTempFileName()
    client.Credentials = CredentialCache.DefaultNetworkCredentials
    client.DownloadFile(_URL, tmp)

    Dim doc = New HtmlAgilityPack.HtmlDocument()
    doc.Load(tmp)

    Dim root = doc.DocumentNode
    Dim filas = root.Descendants("tr").ToList()

    For Each fila In filas
        Dim tds = fila.Descendants("td").ToList()
        If tds.Count >= 2 Then
            Console.WriteLine(tds(0).InnerText & ": " & tds(1).InnerText)
        End If
    Next
End Using

Como ves, HAP ofrece un motor de parsing sólido y versátil. La diferencia entre lenguajes está en la sintaxis; el flujo de trabajo es idéntico: cargar, seleccionar nodos y leer contenido.

Scraping estático en C# paso a paso: de XPath a CSV

En sitios con contenido estático (el HTML ya trae los datos), basta con descargar la página y parsear sus nodos. Veamos un flujo completo: instalar HAP, cargar una página, seleccionar filas por XPath, mapear a objetos y exportar a CSV con CsvHelper.

1) Instala HtmlAgilityPack desde NuGet. 2) Carga la URL con HtmlWeb.Load(). 3) Obtén los nodos por XPath. 4) Extrae texto de cada celda. 5) Exporta los objetos a CSV con CsvHelper.

using HtmlAgilityPack;
using System.Collections.Generic;

// URL de ejemplo (Wikipedia)
var url = "https://en.wikipedia.org/wiki/List_of_SpongeBob_SquarePants_episodes";
var web = new HtmlWeb();
var document = web.Load(url);

// XPath que selecciona filas de las tablas de episodios
var nodes = document.DocumentNode.SelectNodes(
    "//*[@id='mw-content-text']/div[1]/table[position()>1 and position()<15]/tbody/tr[position()>1]");

// Clase para mapear resultados
public class Episode {
    public string OverallNumber { get; set; }
    public string Title { get; set; }
    public string Directors { get; set; }
    public string WrittenBy { get; set; }
    public string Released { get; set; }
}

var episodes = new List<Episode>();
foreach (var node in nodes) {
    episodes.Add(new Episode {
        OverallNumber = HtmlEntity.DeEntitize(node.SelectSingleNode("th[1]").InnerText),
        Title        = HtmlEntity.DeEntitize(node.SelectSingleNode("td[2]").InnerText),
        Directors    = HtmlEntity.DeEntitize(node.SelectSingleNode("td[3]").InnerText),
        WrittenBy    = HtmlEntity.DeEntitize(node.SelectSingleNode("td[4]").InnerText),
        Released     = HtmlEntity.DeEntitize(node.SelectSingleNode("td[5]").InnerText)
    });
}

Para escribir el CSV, CsvHelper simplifica mucho la salida. Sólo creas un StreamWriter y llamas a WriteRecords() con tu lista fuertemente tipada.

using CsvHelper;
using System.Globalization;
using System.IO;

using (var writer = new StreamWriter("output.csv"))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) {
    csv.WriteRecords(episodes);
}

De esta forma, cualquiera puede abrir el CSV en Excel y trabajar con los datos estructurados sin tocar código. Es un flujo sencillo, fiable y fácil de mantener si la estructura de la página cambia: actualizas tu XPath y listo.

  Automatizar la instalación de software y drivers en Windows 11 tras formatear

Cuando el HTML no trae los datos: dinámico, AJAX y Selenium

En sitios dinámicos, el HTML inicial puede venir vacío y es JavaScript quien pinta los datos tras peticiones XHR. Como HAP no ejecuta JavaScript, necesitas un navegador sin interfaz (headless) como Selenium para renderizar primero y extraer después.

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

var url = "https://en.wikipedia.org/wiki/List_of_SpongeBob_SquarePants_episodes";
var chromeOptions = new ChromeOptions();
chromeOptions.AddArguments("headless");
var driver = new ChromeDriver(chromeOptions);

driver.Navigate().GoToUrl(url);

var rows = driver.FindElements(By.XPath(
    "//*[@id='mw-content-text']/div[1]/table[position()>1 and position()<15]/tbody/tr[position()>1]"));

foreach (var row in rows) {
    var title = row.FindElement(By.XPath("td[2]")).Text;
    // ...
}

En escenarios con carga perezosa o peticiones lentas, añade una WebDriverWait para esperar a que aparezcan los nodos o a que el Ajax finalice. Es más pesado que HAP, pero para páginas dinámicas es la vía correcta.

Limitación clave de HAP y alternativa WebView

HAP analiza el DOM tal como llega del servidor, es decir, no ejecuta JS. Si tu sitio de destino requiere scripts para renderizar su contenido, además de Selenium puedes cargar la página en un control WebView/WebBrowser que sí ejecute JavaScript y, una vez lista, pasar su HTML resultante a HtmlAgilityPack. Así combinas renderizado real con parsing robusto.

Casos de uso: qué hacer con los datos

Una vez que tienes tus objetos en memoria, el límite es tu imaginación: guardarlos en una base de datos, transformarlos a JSON para invocar APIs, generar CSV para el equipo de negocio o volcarlos en informes periódicos. La clave es trasladar resultados a formatos que tu organización ya maneja.

Privacidad, bloqueos y scraping regional mediante proxies

Al raspar a escala, los sitios pueden detectar patrones y bloquear tu IP. Usar proxies (mejor si rotan dirección) ayuda a evitar vetos, repartir la carga y acceder a versiones regionales de la misma web. Un buen proveedor te permite elegir ubicación de salida, ideal para estudios de mercado o pricing internacional.

Los proxies rotativos asignan IP distintas en cada solicitud, complicando el rastreo por parte de sistemas anti-bot. Además, si necesitas ver catálogos o precios que cambian según país, elige la localización del proxy para obtener la vista exacta que vería un usuario real de esa región.

Integrar HtmlAgilityPack con IronPDF: de HTML a PDF

Hay escenarios donde necesitas empaquetar resultados en un documento. Ahí entra IronPDF: con HAP extraes y compones el HTML deseado y con IronPDF lo conviertes a PDF manteniendo estilos y maquetación. Es perfecto para reportes o entregables que se comparten fuera del equipo técnico.

  Cómo eliminar un usuario o cuenta en Windows 11 paso a paso

Instalar IronPDF es tan simple como añadir el paquete de NuGet. Si lo prefieres, también existe la opción de integrar la DLL manualmente. Una vez referenciado, creas un HtmlToPdf y renderizas una cadena HTML que tú mismo generas con lo raspado.

using HtmlAgilityPack;
using System.Text;
// using IronPdf;  // Asegúrate de referenciar IronPDF

var web = new HtmlWeb();
var doc = web.Load("https://ironpdf.com/");

var nodes = doc.DocumentNode.SelectNodes(
    "//h1[@class='product-homepage-header product-homepage-header--ironpdf']");

var htmlContent = new StringBuilder();
foreach (var n in nodes) {
    htmlContent.Append(n.OuterHtml);
}

var renderer = new IronPdf.HtmlToPdf();
var pdf = renderer.RenderHtmlAsPdf(htmlContent.ToString());
pdf.SaveAs("output.pdf");

Si necesitas añadir cabeceras, pies, numeración, o componer páginas con secciones extraídas de distintas URLs, puedes personalizar la salida antes de pasarla al motor de PDF para un resultado más pulido.

Buenas prácticas para selectores y mantenimiento

  • Prefiere atributos estables (id o clases con significado) frente a índices frágiles como div[3]/span[2].
  • Evita regex sobre HTML cuando haya alternativa DOM/XPath.
  • Utiliza HtmlEntity.DeEntitize/HtmlDecode para limpiar entidades.
  • Centraliza XPaths en constantes y documenta su intención.
  •  Implementa controles de errores: nodos nulos, timeouts, cambios de estructura.
  • Para dinámicos, añade esperas explícitas y condiciones de presencia de elementos.

También es recomendable registrar ejemplos de HTML real en tests para detectar roturas cuando la web cambie. Mantener una pequeña suite de pruebas sobre tus XPaths y mapeos es barato y previene incidencias en producción, especialmente cuando hay varias fuentes de datos.

Consejos de rendimiento y extensibilidad

HAP está optimizado para manejar documentos grandes con un consumo de memoria razonable. Aun así, si vas a procesar muchas páginas, considera paralelizar descargas con límites (p. ej. SemaphoreSlim) y normaliza el HTML justo antes de extraer. Si necesitas reglas especiales, puedes extender el pipeline con filtros propios antes de construir tus objetos.

En entornos mixtos, PowerShell es ideal para orquestar tareas (descargas, rotación de proxies, ejecución de parsers C# compilados) y consolidar resultados. Combinar scripts con utilidades .NET te da agilidad sin renunciar a rendimiento.

Si vienes de expresiones regulares, notarás que con DOM/XPath el mantenimiento es menor y la legibilidad, mucho mayor. Es habitual que un selector bien pensado sobreviva meses aunque haya pequeños retoques en el markup de la web objetivo.

Todo este ecosistema (HtmlAgilityPack para analizar, Selenium para renderizar si hace falta, CsvHelper para exportar y IronPDF para presentar) encaja de maravilla con flujos reales de extracción y reporte. Con PowerShell, C# o VB.NET puedes montar soluciones escalables y, con el apoyo de proxies, operar de forma más resiliente frente a bloqueos, cambios regionales o picos de carga.