Volver al blog

C# Web Scraping: Una Guía Práctica

Michael Lee
Michael Lee

Expert Network Defense Engineer

11-Aug-2026

TL;DR:

  • HttpClient más AngleSharp es la combinación moderna de C#. AngleSharp toma selectores CSS, por lo que doc.QuerySelectorAll("div.quote") se comporta de la misma manera que el mismo selector se comporta en la consola del navegador.
  • La API de Raspado Universal de Scrapeless responde con un sobre JSON. El marcado llega dentro de data, por lo que primero lees el JSON y luego analisas HTML.
  • El primer paso habitual de PuppeteerSharp descarga un navegador. Conectarse a uno remoto lo omite completamente. Cada inicio rápido comienza con BrowserFetcher().DownloadAsync(); Puppeteer.ConnectAsync nunca lo llama. Medido: el proyecto de verificación terminó en 7.6 MB sin Chromium en disco y sin caché de navegador de PuppeteerSharp.
  • La ruta CDP remota funciona sin ceremonia. ConnectAsync aceptó un endpoint wss:// que lleva una credencial de cadena de consulta y devolvió title=Quotes to Scrape, quotes on page=10.
  • Comienza gratis: el tablero de Scrapeless emite una clave que funciona con cada ejemplo a continuación.

Lo Que Necesitas

Todo lo siguiente se ejecutó en .NET SDK 8.0.129 contra quotes.toscrape.com, un sitio publicado para practicar raspado.

bash Copy
dotnet new console -o scraper
cd scraper
dotnet add package AngleSharp        # 1.7.1
dotnet add package PuppeteerSharp    # 25.5.0

HttpClient y System.Text.Json están en la biblioteca de clases base, por lo que las únicas dependencias son el analizador y, más tarde, el controlador del navegador.

Sobre la elección del analizador: HtmlAgilityPack es el nombre que la mayoría del material de raspado en C# usa, y funciona. AngleSharp es preferible para nuevo código porque implementa el DOM W3C y toma selectores CSS directamente, lo que significa que los selectores que copias de las devtools del navegador funcionan sin cambios en lugar de necesitar traducción a XPath.

Recuperación y Análisis

csharp Copy
using AngleSharp.Html.Parser;
using System.Text;

var http = new HttpClient();
http.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (compatible; csharp-guide/1.0)");

var res  = await http.GetAsync("https://quotes.toscrape.com/");
var html = await res.Content.ReadAsStringAsync();
Console.WriteLine($"http={(int)res.StatusCode} bytes={Encoding.UTF8.GetByteCount(html)}");

var parser = new HtmlParser();
var doc    = await parser.ParseDocumentAsync(html);
var quotes = doc.QuerySelectorAll("div.quote");

Console.WriteLine($"quotes={quotes.Length}");
Console.WriteLine($"first_author={quotes[0].QuerySelector("small.author")!.TextContent}");
Console.WriteLine($"first_text={quotes[0].QuerySelector("span.text")!.TextContent[..40]}");
text Copy
http=200 bytes=11064
quotes=10
first_author=Albert Einstein
first_text=“The world as we have created it is a pr

Tres detalles valen la pena destacar.

Establece un agente de usuario. HttpClient no envía ninguno por defecto, y una solicitud sin agente de usuario es una de las cosas más baratas que un sitio puede tratar de manera diferente.

Encoding.UTF8.GetByteCount(html) no es html.Length. Las cadenas .NET son UTF-16, así que Length cuenta unidades de código UTF-16, no bytes en la red. En esta página los dos difieren porque las comillas utilizan comillas de estilo curly. Informa lo que quieras decir, pero no los confundas al comparar una recuperación directa contra una respuesta de API.

Limita las consultas hijas a la fila. quotes[0].QuerySelector(...) busca dentro de ese elemento; llamar doc.QuerySelector(...) dentro de un bucle devuelve el valor de la primera cita en cada iteración, produciendo un conjunto de datos que parece completo y es uniformemente incorrecto.

Donde C# Simple Se Detiene

El script funciona porque el objetivo se renderiza del lado del servidor y no filtra llamadores. Dos cosas ponen fin a eso: contenido que solo existe después de que se ejecuta JavaScript y sitios que se niegan a servir un cliente HTTP básico. Ninguna de estas es una limitación de .NET: ningún cliente HTTP en ningún lenguaje maneja ninguna de las dos.

A partir de aquí, las escalaciones son herramientas diferentes en lugar de mejores, y el camino simple sigue siendo el más barato donde funciona. Para la forma general de lo que un navegador agrega, el explicador de automatización de navegador cubre la categoría.

Escalación Uno: La API de Raspado Universal

Esto mantiene tu código como un ordinario HttpClient llamador y mueve la dificultad al lado del servidor.

csharp Copy
using System.Text;
using System.Text.Json;
using AngleSharp.Html.Parser;

var key = Environment.GetEnvironmentVariable("SCRAPELESS_API_KEY")!;

var payload = JsonSerializer.Serialize(new
{
    actor = "unlocker.webunlocker",
    input = new { url = "https://quotes.toscrape.com/", js_render = false }
});

using var req = new HttpRequestMessage(
    HttpMethod.Post, "https://api.scrapeless.com/api/v2/unlocker/request")
{
    Content = new StringContent(payload, Encoding.UTF8, "application/json")
};
req.Headers.Add("x-api-token", key);

var apiRes = await http.SendAsync(req);
using var json = JsonDocument.Parse(await apiRes.Content.ReadAsStringAsync());

var keys = string.Join(",", json.RootElement.EnumerateObject().Select(p => p.Name));
Console.WriteLine($"http={(int)apiRes.StatusCode} envelope_keys={keys}");

var apiHtml = json.RootElement.GetProperty("data").GetString()!;
var apiDoc  = await new HtmlParser().ParseDocumentAsync(apiHtml);
Console.WriteLine($"quotes={apiDoc.QuerySelectorAll("div.quote").Length}");
text Copy
http=200 envelope_keys=code,data
quotes=10

El sobre es la parte que debes internalizar. El cuerpo de la respuesta es JSON y el marcado vive en data. Entregar el cuerpo sin procesar a ParseDocumentAsync produce un documento sin elementos coincidentes y no lanza nada: los selectores devuelven silenciosamente cero resultados. Lee el JSON, toma data, luego analiza, y mantiene ese desempaquetado en un método.

Nota req.Headers.Add("x-api-token", key) en lugar de un encabezado Authorization. Es un encabezado personalizado, así que va en HttpRequestMessage.Headers directamente; intentar expresarlo como un encabezado de autenticación tipado es un desvío que no se aplica aquí.

Aquí es donde C# hace notablemente mejor de lo que sugieren las instrucciones habituales. Cada PuppeteerSharp inicio rápido comienza así:

csharp Copy
// The standard first step — downloads a Chromium build before anything runs.
await new BrowserFetcher().DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });

Puppeteer.ConnectAsync no necesita nada de ello. Te estás conectando a un navegador que ya existe en otro lugar, así que BrowserFetcher nunca está involucrado y nada se guarda en el disco:

csharp Copy
using PuppeteerSharp;

var key = Environment.GetEnvironmentVariable("SCRAPELESS_API_KEY")!;

await using var browser = await Puppeteer.ConnectAsync(new ConnectOptions
{
    BrowserWSEndpoint = $"wss://browser.scrapeless.com/api/v2/browser?token={key}"
});

var page = await browser.NewPageAsync();
await page.GoToAsync("https://quotes.toscrape.com/");

Console.WriteLine($"title={await page.GetTitleAsync()}");
var n = await page.EvaluateExpressionAsync<int>("document.querySelectorAll('.quote').length");
Console.WriteLine($"quotes on page={n}");
text Copy
title=Quotes to Scrape
quotes on page=10

Dos cosas que esto te compra más allá de la conveniencia.

El proyecto se mantiene pequeño. Después de ejecutar cada ejemplo en este artículo, el proyecto de verificación midió 7.6 MB en disco, no contenía Chromium, y no dejó caché de navegador de PuppeteerSharp. Un flujo de trabajo LaunchAsync agrega una construcción de navegador a cada máquina y a cada imagen de contenedor que ejecuta el código.
EvaluateExpressionAsync<T> deserializa por ti. Pedir <int> devuelve un int, así que no hay JsonElement que desempaquetar y ningún análisis manual entre el navegador y tu variable.

Vale la pena saber: ConnectAsync aceptó un wss:// endpoint llevando su credencial en la cadena de consulta sin ninguna configuración adicional. Eso no es universal entre los clientes de CDP de los lenguajes: algunos eliminan la cadena de consulta u omiten la negociación del nombre del servidor TLS, así que si estás portando desde otra pila, esta parte probablemente sea más fácil que la que dejaste.

Elegir entre los tres

enfoque usar cuando costo
HttpClient + AngleSharp HTML renderizado en el servidor, objetivo permisivo más bajo; un paquete
API de scraping universal bloqueado o desafiado, sin JS necesario una llamada HTTP, sobre con el que desempaquetar
PuppeteerSharp + un navegador remoto el contenido requiere ejecución de JavaScript una sesión por trabajo, pero sin navegador en disco

Trabaja de abajo hacia arriba en la lista. Una página que parece necesitar un navegador a menudo incrusta sus datos en una etiqueta <script>, y un QuerySelector más JsonDocument.Parse supera a una sesión del navegador en todos los ejes.

Conclusión

C# es un lenguaje de scraping cómodo y la biblioteca estándar cubre más de lo que los tutoriales del ecosistema implican. HttpClient y AngleSharp manejan páginas renderizadas en el servidor con selectores CSS que puedes copiar de las herramientas de desarrollo, y las dos trampas que vale la pena recordar son contar unidades UTF-16 cuando te referías a bytes, y consultar el documento cuando te referías a la fila.

La historia del navegador es mejor de lo que sugiere la documentación. El paso de descarga en cada inicio rápido es una propiedad de lanzar un navegador local, no de PuppeteerSharp, y ConnectAsync lo elimina junto con la huella de disco — un proyecto de 7.6 MB que aún utiliza un Chrome real.

¿Listo para raspar con C#?

Crea una clave en el tablero de Scrapeless y ejecuta el ejemplo HttpClient contra una página que ya coleccionas. Si devuelve lo que esperas, has terminado: un paquete, sin navegador. La API de Scraping Universal cubre las páginas donde no lo hace, y las tarifas actuales están en la página de precios.

FAQ

P: ¿AngleSharp o HtmlAgilityPack?

Ambos analizan bien el HTML del mundo real. AngleSharp implementa el DOM de W3C y acepta selectores CSS, por lo que los selectores de herramientas de desarrollo se portan sin cambios; HtmlAgilityPack es primero XPath y tiene una historia más larga. Para nuevo código, AngleSharp generalmente significa menos trabajo de traducción.

P: ¿Tengo que descargar Chromium para usar PuppeteerSharp?

Solo si lanzas un navegador local. BrowserFetcher().DownloadAsync() pertenece a la ruta LaunchAsync; ConnectAsync se adjunta a un navegador que ya existe y nunca lo toca. El proyecto de verificación aquí se mantuvo en 7.6 MB sin navegador en disco.

P: ¿Por qué mi análisis no devuelve nada cuando la llamada a la API tuvo éxito?

Estás analizando el sobre. La respuesta es JSON con el marcado dentro data, así que AngleSharp recibe una cadena JSON, no coincide con nada y no lanza nada. Lee data del JSON primero.

P: ¿Por qué la cuenta de bytes difiere de la longitud de la cadena?

Las cadenas de .NET son UTF-16, así que string.Length cuenta unidades de código mientras Encoding.UTF8.GetByteCount cuenta bytes según se transfieren. Cualquier página con caracteres no ASCII — incluyendo comillas tipográficas — mostrará una diferencia.

P: ¿Debería reutilizar HttpClient?

Sí. Crea uno y compártelo, o usa IHttpClientFactory en una aplicación alojada. Construir un nuevo HttpClient por solicitud agota sockets bajo carga — las propias directrices de HttpClient de Microsoft explican por qué — y eso se aplica a los scrapers exactamente como a cualquier otra carga de trabajo HTTP de .NET.

En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.

Artículos más populares

Catalogar