Tecnologías y protecciones 10 min de lectura

Scraping de sitios con autenticación: guía completa con ejemplos de código

Scraping de sitios con autenticación: cookies y sesiones, tokens, CSRF y ejemplos de inicio de sesión desde código en Python, Node.js, PHP y Go.

EW
Equipo Web-Scraping.es
Recopilación de datos para las necesidades del negocio
Publicado: 19 abril 2025

La mayoría de las tareas de web scraping se resuelven con una simple petición GET a una página pública. Pero en cuanto los datos necesarios quedan «tras el login» — en el área de cliente, en una sección privada o bajo una suscripción de pago —, la petición normal devuelve el formulario de acceso o un error 401/403. Para llegar al contenido, el scraper debe autenticarse primero, exactamente igual que lo hace el navegador del usuario.

Dónde se aplica con más frecuencia

El escenario más habitual del scraping con autenticación es la monitorización de precios de tiendas online. La situación típica es esta: usted trabaja con un proveedor cuyos precios y existencias actualizados solo están disponibles en el área de cliente de su sitio web. No hay un acceso completo a los datos vía API, tampoco exportaciones en el formato necesario, y los precios cambian tan a menudo que trasladarlos a mano no resulta racional.

Conviene subrayar un punto: ese acceso está acordado con el proveedor y no vulnera las condiciones de uso del sitio. Es decir, usted automatiza la obtención de los datos que ya tiene permiso para ver con su propia cuenta — simplemente lo hace por programa y no a golpe de clic. Solo en esos términos el scraping con autenticación es una herramienta de trabajo legítima, y no una forma de saltarse restricciones.

Antes de escribir código, asegúrese siempre de que:

  • el propietario del recurso permite el acceso a los datos (contrato, consentimiento por escrito, condiciones del programa de partners);
  • la recogida automatizada no está prohibida por los términos de servicio (ToS) ni por el archivo robots.txt;
  • la carga sobre el sitio se mantiene razonable y no interfiere en su funcionamiento;
  • no se recogen ni se tratan datos personales de terceros sin una base que lo justifique.

Cómo funciona la autenticación: cuatro mecanismos principales

Para elegir el enfoque de código hay que entender de qué manera el sitio autentica al usuario. En la práctica se encuentran cuatro variantes principales.

1. Formulario de acceso y cookies de sesión. El caso más frecuente. Se envían el usuario y la contraseña con una petición POST al endpoint de autenticación; el servidor responde estableciendo una cookie de sesión (por ejemplo, sessionid o PHPSESSID) y, a partir de ahí, esa cookie acompaña a todas las peticiones. La sesión vive mientras la cookie siga siendo válida.

2. Token CSRF. Muchos formularios están protegidos por un token que se esconde en el HTML de la página de acceso (en un campo oculto o en una etiqueta meta) o que se entrega en una cookie aparte. Antes de enviar el formulario hay que cargar la página de login, extraer el token y mandarlo junto con las credenciales. Sin él, el servidor rechazará la petición.

3. Tokens (Bearer / JWT). Los sitios modernos y las SPA suelen autenticar al usuario a través de una API que devuelve un token en JSON. Después, el token se envía en la cabecera Authorization: Bearer <token>. Aquí las cookies pueden no usarse en absoluto.

4. HTTP Basic Auth. La variante más simple: el usuario y la contraseña se codifican en base64 y viajan en la cabecera Authorization. Se encuentra en sistemas internos y en algunas APIs.

Una dificultad aparte son los sitios que generan el contenido con JavaScript. Ahí un cliente HTTP normal no basta: hace falta un navegador «headless» (Playwright, Puppeteer, Selenium) que ejecute los scripts y entregue el DOM ya construido.

Python: requests con sesión

requests.Session() conserva automáticamente las cookies entre peticiones — la base ideal para el scraping con autenticación. Ejemplo con obtención previa del token CSRF:

python
import requests
from bs4 import BeautifulSoup

LOGIN_URL = "https://supplier.example.com/login"
PRICES_URL = "https://supplier.example.com/account/prices"

session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (compatible; PriceMonitor/1.0)"
})

# 1. Cargamos la página de login y extraemos el token CSRF
login_page = session.get(LOGIN_URL, timeout=30)
soup = BeautifulSoup(login_page.text, "html.parser")
csrf_token = soup.select_one('input[name="csrf_token"]')["value"]

# 2. Enviamos el formulario de acceso
payload = {
    "username": "your_login",
    "password": "your_password",
    "csrf_token": csrf_token,
}
resp = session.post(LOGIN_URL, data=payload, timeout=30)
resp.raise_for_status()

if "Mi cuenta" not in resp.text:
    raise RuntimeError("La autenticación falló: revise las credenciales")

# 3. Sesión establecida: solicitamos la página privada de precios
prices_page = session.get(PRICES_URL, timeout=30)
soup = BeautifulSoup(prices_page.text, "html.parser")

for row in soup.select("table.prices tr"):
    cells = row.select("td")
    if len(cells) >= 2:
        name = cells[0].get_text(strip=True)
        price = cells[1].get_text(strip=True)
        print(f"{name}: {price}")

Una buena práctica es guardar el usuario y la contraseña fuera del código — en variables de entorno (os.environ) o en un archivo .env —, para no subirlos por accidente al repositorio.

Python: autenticación por token (API)

Si el sitio autentica a través de una API JSON y devuelve un token, el código es más sencillo:

python
import requests

auth = requests.post(
    "https://supplier.example.com/api/auth/login",
    json={"login": "your_login", "password": "your_password"},
    timeout=30,
)
auth.raise_for_status()
token = auth.json()["access_token"]

headers = {"Authorization": f"Bearer {token}"}
data = requests.get(
    "https://supplier.example.com/api/prices",
    headers=headers,
    timeout=30,
).json()

for item in data["items"]:
    print(item["sku"], item["price"])

Python: Playwright para sitios hechos con JavaScript

Cuando el área de cliente es una SPA y los precios se cargan mediante scripts, la solución es un navegador headless. Playwright sabe iniciar sesión como un usuario real e incluso guardar el estado de la sesión en un archivo para no volver a entrar en cada ejecución.

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    context = browser.new_context()
    page = context.new_page()

    # Inicio de sesión
    page.goto("https://supplier.example.com/login")
    page.fill("input[name='username']", "your_login")
    page.fill("input[name='password']", "your_password")
    page.click("button[type='submit']")
    page.wait_for_url("**/account/**")

    # Guardamos la sesión para reutilizarla más tarde
    context.storage_state(path="auth_state.json")

    # Vamos a los precios y esperamos a que carguen los datos
    page.goto("https://supplier.example.com/account/prices")
    page.wait_for_selector("table.prices")

    rows = page.query_selector_all("table.prices tr")
    for row in rows:
        cells = row.query_selector_all("td")
        if len(cells) >= 2:
            print(cells[0].inner_text(), "—", cells[1].inner_text())

    browser.close()

El auth_state.json guardado se conecta después mediante browser.new_context(storage_state="auth_state.json") — y el paso de login puede saltarse mientras la sesión no haya caducado.

En Node, para conservar las cookies entre peticiones se usa la combinación axios + tough-cookie + axios-cookiejar-support.

javascript
const axios = require("axios");
const { wrapper } = require("axios-cookiejar-support");
const { CookieJar } = require("tough-cookie");
const cheerio = require("cheerio");

const jar = new CookieJar();
const client = wrapper(axios.create({ jar, withCredentials: true }));

async function run() {
  // 1. Obtenemos el token CSRF de la página de login
  const loginPage = await client.get("https://supplier.example.com/login");
  const $ = cheerio.load(loginPage.data);
  const csrf = $('input[name="csrf_token"]').val();

  // 2. Iniciamos sesión
  await client.post(
    "https://supplier.example.com/login",
    new URLSearchParams({
      username: "your_login",
      password: "your_password",
      csrf_token: csrf,
    }),
  );

  // 3. Solicitamos los precios
  const pricesPage = await client.get(
    "https://supplier.example.com/account/prices",
  );
  const $$ = cheerio.load(pricesPage.data);

  $$("table.prices tr").each((_, el) => {
    const cells = $$(el).find("td");
    if (cells.length >= 2) {
      const name = $$(cells[0]).text().trim();
      const price = $$(cells[1]).text().trim();
      console.log(`${name}: ${price}`);
    }
  });
}

run().catch(console.error);

Node.js: Puppeteer para páginas dinámicas

javascript
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  await page.goto("https://supplier.example.com/login");
  await page.type("input[name='username']", "your_login");
  await page.type("input[name='password']", "your_password");
  await Promise.all([
    page.click("button[type='submit']"),
    page.waitForNavigation(),
  ]);

  await page.goto("https://supplier.example.com/account/prices");
  await page.waitForSelector("table.prices");

  const prices = await page.evaluate(() =>
    Array.from(document.querySelectorAll("table.prices tr"))
      .map((row) => {
        const td = row.querySelectorAll("td");
        return td.length >= 2
          ? { name: td[0].innerText.trim(), price: td[1].innerText.trim() }
          : null;
      })
      .filter(Boolean),
  );

  console.log(prices);
  await browser.close();
})();

En PHP, las cookies se conservan entre peticiones en un archivo mediante las opciones COOKIEJAR y COOKIEFILE.

php
<?php
$cookieFile = __DIR__ . "/cookies.txt";

function curlInit(string $cookieFile) {
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_COOKIEJAR      => $cookieFile,
        CURLOPT_COOKIEFILE     => $cookieFile,
        CURLOPT_USERAGENT      => "Mozilla/5.0 (compatible; PriceMonitor/1.0)",
    ]);
    return $ch;
}

// 1. Cargamos la página de login y extraemos el token CSRF
$ch = curlInit($cookieFile);
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/login");
$html = curl_exec($ch);

preg_match('/name="csrf_token"\s+value="([^"]+)"/', $html, $m);
$csrf = $m[1] ?? "";

// 2. Enviamos el formulario de acceso
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/login");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    "username"   => "your_login",
    "password"   => "your_password",
    "csrf_token" => $csrf,
]));
curl_exec($ch);

// 3. Solicitamos los precios
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/account/prices");
curl_setopt($ch, CURLOPT_POST, false);
$pricesHtml = curl_exec($ch);
curl_close($ch);

// Parseo del HTML
$dom = new DOMDocument();
@$dom->loadHTML($pricesHtml);
$xpath = new DOMXPath($dom);
foreach ($xpath->query("//table[@class='prices']//tr") as $row) {
    $cells = $row->getElementsByTagName("td");
    if ($cells->length >= 2) {
        echo trim($cells->item(0)->textContent) . ": "
           . trim($cells->item(1)->textContent) . PHP_EOL;
    }
}

Go: net/http con cookiejar

La biblioteca estándar de Go incluye net/http/cookiejar, que gestiona las cookies por usted de forma automática.

go
package main

import (
    "fmt"
    "net/http"
    "net/http/cookiejar"
    "net/url"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {
    jar, _ := cookiejar.New(nil)
    client := &http.Client{Jar: jar}

    // 1. Obtenemos el token CSRF
    resp, _ := client.Get("https://supplier.example.com/login")
    doc, _ := goquery.NewDocumentFromReader(resp.Body)
    resp.Body.Close()
    csrf, _ := doc.Find(`input[name="csrf_token"]`).Attr("value")

    // 2. Iniciamos sesión
    form := url.Values{
        "username":   {"your_login"},
        "password":   {"your_password"},
        "csrf_token": {csrf},
    }
    client.Post(
        "https://supplier.example.com/login",
        "application/x-www-form-urlencoded",
        strings.NewReader(form.Encode()),
    )

    // 3. Extraemos los precios
    pricesResp, _ := client.Get("https://supplier.example.com/account/prices")
    pricesDoc, _ := goquery.NewDocumentFromReader(pricesResp.Body)
    pricesResp.Body.Close()

    pricesDoc.Find("table.prices tr").Each(func(_ int, s *goquery.Selection) {
        cells := s.Find("td")
        if cells.Length() >= 2 {
            name := strings.TrimSpace(cells.Eq(0).Text())
            price := strings.TrimSpace(cells.Eq(1).Text())
            fmt.Printf("%s: %s\n", name, price)
        }
    })
}

Recomendaciones prácticas

Reutilice la sesión. No inicie sesión en cada petición: es carga innecesaria y riesgo de bloqueo. Guarde la cookie o el token y renuévelos solo cuando la sesión haya caducado.

Gestione la caducidad de la sesión. Las cookies y los tokens tienen fecha de caducidad. Prevea una comprobación: si la petición devuelve una redirección al formulario de login o un código 401, vuelva a autenticarse y repita la petición.

Mantenga un ritmo razonable. Haga pausas entre peticiones (por ejemplo, de 1 a 3 segundos) y no lance decenas de hilos en paralelo. Es una cortesía hacia el servidor del proveedor y reduce la probabilidad de caer bajo la protección anti-bots.

Guarde los secretos de forma segura. El usuario, la contraseña y los tokens van en variables de entorno o en un almacén protegido, no en el código y mucho menos en un repositorio público.

Sea resistente a los cambios de maquetación. Los sitios cambian y los selectores se rompen. Registre los errores de parseo y configure avisos para detectar rápido cuándo cambió la estructura de la página.

Use un User-Agent honesto y, a ser posible, incluya datos de contacto. Si el proveedor aprobó el acceso, un bot identificable simplifica el diagnóstico de su lado cuando algo va mal.

Conclusión

Técnicamente, el scraping con autenticación consiste en reproducir los pasos que da el navegador al iniciar sesión: obtener y enviar el formulario (con el token CSRF, si existe), guardar la cookie de sesión o el token y adjuntarlo a las peticiones siguientes. Para páginas estáticas basta un cliente HTTP con soporte de sesiones (requests, axios, cURL, net/http); para las dinámicas hace falta un navegador headless (Playwright, Puppeteer).

Lo esencial, sin embargo, no está en el código, sino en la base que legitima la recogida de datos. El scraping del área de cliente de un proveedor para la monitorización de precios es una herramienta de trabajo legítima exactamente cuando el acceso está acordado con el propietario del recurso y no vulnera las condiciones de uso. La tecnología funciona igual de bien en ambas direcciones, así que la responsabilidad de aplicarla correctamente sigue siendo suya.