Web Scraping Con Goutte En PHP: 2025 Tutorial

Domine el raspado web de Goutte con esta guía paso a paso. Aprenda a configurar, alternativas y cómo evitar las limitaciones del scraping para una mejor extracción de datos.
1 min de lectura
Web Scraping With Goutte blog image

En esta guía de raspado web de Goutte, aprenderá:

  • Qué es la biblioteca PHP Goutte
  • Cómo utilizarlo para el web scraping en un tutorial paso a paso
  • Alternativas a Goutte para el web scraping
  • Limitaciones de este enfoque y posibles soluciones

Sumerjámonos.

¿Qué es la gota?

Goutte es una librería PHP para screen scraping y web crawling, que ofrece una API intuitiva para navegar por sitios web y extraer datos de respuestas HTML/XML. Incluye un cliente HTTP integrado y capacidades de análisis de HTML, lo que le permite recuperar páginas web a través de peticiones HTTP y procesarlas para el raspado de datos.

Nota: Desde el 1 de abril de 2023, Goutte ya no se mantiene y se considera obsoleto. Sin embargo, en el momento de escribir este artículo, todavía funciona de forma fiable.

Cómo realizar Web Scraping con Goutte: Guía paso a paso

Siga este tutorial paso a paso y vea cómo utilizar Goutte para extraer datos del sitio “Equipos de Hockey“:

La página objetivo "Equipos de hockey

El objetivo es extraer los datos de la tabla anterior y exportarlos a un archivo CSV.

¡Es hora de aprender a hacer web scraping con Goutte!

Paso nº 1: Configuración del proyecto

Antes de empezar, asegúrese de que su sistema cumple los requisitos de Goutte: PHP7.1 o superior. Para comprobar la versión actual de PHP, ejecute el siguiente comando:

php -v

El resultado debería ser algo parecido a esto:

PHP 8.4.3 (cli) (built: Jan 19 2025 14:20:58) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.3, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.3, Copyright (c), by Zend Technologies

Si su versión de PHP es inferior a la 7.1, deberá actualizar PHP antes de continuar.

A continuación, ten en cuenta que Goutte se instalará a través de Composer, un gestor de dependencias para PHP. Si Composer no está instalado en tu sistema, descárgalo desde el sitio oficial y sigue las instrucciones de instalación.

Ahora, crea un nuevo directorio para tu proyecto Goutte y navega hasta él en el terminal:

mkdir goutte-parser
cd goutte-parser

A continuación, utilice el comando composer init para inicializar un proyecto Composer dentro de la carpeta:

composer init

Composer le pedirá que introduzca los detalles del proyecto, como el nombre del paquete y la descripción. Las respuestas predeterminadas funcionarán, pero no dudes en personalizarlas según tus objetivos.

Ahora, abre la carpeta del proyecto en tu IDE de PHP favorito. Visual Studio Code con la extensión PHP o IntelliJ WebStorm son buenas opciones.

Crear un archivo index.php vacío en la carpeta del proyecto, que debe contener:

php-html-parser/
  ├── vendor/
  ├── composer.json
  └── index.php

Abre index.php y añade la siguiente línea de código para importar las librerías de Composer:

<?php

require_once __DIR__ . "/vendor/autoload.php";

// scraping logic...

Este archivo contendrá en breve la lógica de raspado de Goutte.

Ahora puede ejecutar su script utilizando este comando:

php index.php

Muy bien. Ya está todo listo para empezar a raspar datos con Goutte en PHP.

Paso 2: Instalar y configurar Goutte

Instala Goutte con el comando Compose que aparece a continuación:

composer require fabpot/goutte

Esto añadirá la dependencia fabpot/goutte a tu archivo composer.json, que ahora incluirá:

"require": {
    "fabpot/goutte": "^4.0"
}

En index.php, importe Goutte añadiendo la siguiente línea de código:

use Goutte\Client;

Esto expone el cliente HTTP de Goutte que puedes utilizar para conectarte a una página de destino, analizar su HTML y extraer datos de ella. Verás cómo hacerlo en el siguiente paso.

Paso 3: Obtener el HTML de la página de destino

En primer lugar, cree un nuevo cliente HTTP Goutte:

$client = new Client();

Entre bastidores, la clase Client de Goutte es simplemente una envoltura alrededor del componente BrowserKit\HttpBrowser de Symfony. Míralo en acción en nuestra guía sobre web scraping con Laravel.

A continuación, almacena la URL de la página web de destino en una variable y utiliza el método request() para obtener su contenido:

$url = "https://www.scrapethissite.com/pages/forms/";
$crawler = $client->request("GET", $url);

Esto envía una petición GET a la página web, recupera su documento HTML, y lo analiza por ti. En concreto, el objeto $crawler proporciona acceso a todos los métodos del componente DomCrawler de Symfony. $crawler es el objeto que utilizarás para navegar y extraer datos de la página.

¡Increíble! Ya tienes todo lo que necesitas para el web scraping de Goutte.

Paso 4: Preparar el raspado de los datos de interés

Antes de extraer los datos, debe familiarizarse con la estructura HTML de la página de destino.

En primer lugar, recuerde que los datos de interés se presentan en filas dentro de una tabla. Dado que esa tabla contiene múltiples filas, un array es una gran estructura de datos donde almacenar los datos raspados:

$teams = [];

Ahora, céntrese en la estructura HTML de la tabla. Visite la página de destino en su navegador, haga clic con el botón derecho en la tabla que contiene los datos de interés y seleccione la opción “Inspeccionar”:

La estructura HTML del elemento tabla

En las DevTools, verás que la tabla tiene una clase table y está contenida dentro de un elemento

con el id=``"``hockey``". Esto significa que puede apuntar a la tabla utilizando el siguiente selector CSS:

#hockey .table

Aplica el selector CSS para seleccionar el nodo de la tabla usando el método $crawler->filter():

$table = $crawler->filter("#hockey .table");

A continuación, observe que cada fila está representada por un elemento

con la clase equipo. Selecciona todas las filas e itera sobre ellas, preparándote para extraer datos de ellas:

    $table->filter("tr.team")->each(function ($tr) use (&$teams) {
      // data extraction logic...
    });

Estupendo. Ahora tiene un esqueleto listo para el raspado de datos de Goutte.

Paso 5: Implementar la lógica de extracción de datos

Al igual que antes, esta vez inspeccione las filas dentro de la tabla:

La estructura HTML de los elementos de fila

Lo que puede observar es que cada fila contiene la siguiente información en columnas específicas:

  • Nombre del equipo → dentro del elemento .name
  • Año de temporada → dentro del elemento .year
  • Número de victorias → dentro del elemento .wins
  • Número de pérdidas → dentro del elemento .losses
  • Pérdidas en tiempo extra → dentro del elemento .ot-losses
  • Porcentaje de victorias → dentro del elemento .pct
  • Goles marcados (Goals For – GF) → dentro del elemento .gf
  • Goles encajados (Goles en contra – GA) → dentro del elemento .ga
  • Diferencia de goles → dentro del elemento .diff

Para recuperar una única información, hay que aplicar estos dos pasos:

  1. Seleccionar el elemento HTML mediante filter()
  2. Extrae su contenido textual mediante el método text() y elimina los espacios sobrantes con trim()

Por ejemplo, puedes raspar el nombre del equipo con:

$teamElement = $tr->filter(".name");
$team = trim($teamElement->text());

Del mismo modo, extienda esta lógica a todas las demás columnas:

$yearElement = $tr->filter(".year");
$year = trim($yearElement->text());

$winsElement = $tr->filter(".wins");
$wins = trim($winsElement->text());

$lossesElement = $tr->filter(".losses");
$losses = trim($lossesElement->text());

$otLossesElement = $tr->filter(".ot-losses");
$otLosses = trim($otLossesElement->text());

$pctElement = $tr->filter(".pct");
$pct = trim($pctElement->text());

$gfElement = $tr->filter(".gf");
$gf = trim($gfElement->text());

$gaElement = $tr->filter(".ga");
$ga = trim($gaElement->text());

$diffElement = $tr->filter(".diff");
$diff = trim($diffElement->text());

Una vez que haya extraído los datos de interés de la fila, almacénelos en la matriz $teams:

$teams[] = [
  "team" => $team,
  "year" => $year,
  "wins" => $wins,
  "losses" => $losses,
  "ot_losses" => $otLosses,
  "win_perc" => $pct,
  "goals_for" => $gf,
  "goals_against" => $ga,
  "goal_diff" => $diff
];

Después de recorrer todas las filas, la matriz $teams contendrá:

Array
(
    [0] => Array
        (
            [team] => Boston Bruins
            [year] => 1990
            [wins] => 44
            [losses] => 24
            [ot_losses] =>
            [win_perc] => 0.55
            [goals_for] => 299
            [goals_against] => 264
            [goal_diff] => 35
        )

    // ...

    [24] => Array
        (
            [team] => Chicago Blackhawks
            [year] => 1991
            [wins] => 36
            [losses] => 29
            [ot_losses] =>
            [win_perc] => 0.45
            [goals_for] => 257
            [goals_against] => 236
            [goal_diff] => 21
        )
)

¡Fantástico! Goutte data scraping realizado con éxito.

Paso 6: Implementar la lógica de rastreo

No olvide que el sitio de destino presenta los datos en varias páginas, mostrando sólo una parte cada vez. Debajo de la tabla, hay un elemento de paginación que proporciona enlaces a todas las páginas:

El elemento de paginación

Así, puede gestionar la paginación en su script de scraping con estos sencillos pasos:

  1. Seleccione los elementos del enlace de paginación
  2. Extraer las URL de las páginas paginadas
  3. Visite cada página y aplique la lógica de raspado ideada anteriormente

Empiece por inspeccionar los elementos del enlace de paginación:

La estructura HTML de los elementos del enlace de paginación

Tenga en cuenta que puede seleccionar todos los enlaces de paginación utilizando el siguiente selector CSS:

.pagination li a

Para implementar el paso 2 y recopilar todas las URL de paginación, utilice esta lógica:

$urls = [$url];

// select the pagination link elements
$crawler->filter(".pagination li a")->each(function ($a) use (&$urls) {
  // construct the absolute URL
  $url = "https://www.scrapethissite.com" . $a->attr("href");

  // add the pagination URL to the list only if it is not already present
  if (!in_array($url, $urls)) {
    $urls[] = $url;
  }
});

Inicializa una lista de URLs que almacenarán los enlaces de paginación, empezando por la URL de la primera página. A continuación, selecciona todos los elementos de paginación e itera sobre ellos, añadiendo nuevas URLs a la matriz $urls sólo si no están ya presentes. Como las URLs de la página son relativas, deben convertirse en URLs absolutas antes de añadirlas a la lista.

Dado que el manejo de la paginación sólo debe ejecutarse una vez y no está directamente ligado a la extracción de datos, es mejor envolverlo en una función:

function getPaginationUrls($client, $url)
{
  // connect to the first page of the site
  $crawler = $client->request("GET", $url);

  // initialize the list of URLs to scrape with the current URL
  $urls = [$url];

  // select the pagination link elements
  $crawler->filter(".pagination li a")->each(function ($a) use (&$urls) {
    // construct the absolute URL
    $url = "https://www.scrapethissite.com" . $a->attr("href");

    // add the pagination URL to the list only if it is not already present
    if (!in_array($url, $urls)) {
      $urls[] = $url;
    }
  });

  return $urls;
}

Puede llamar a la función getPaginationUrls() así:

$urls = getPaginationUrls($client, "https://www.scrapethissite.com/pages/forms/?page_num=1");

Tras la ejecución, $urls contendrá todas las URL paginadas:

Array
(
    [0] => https://www.scrapethissite.com/pages/forms/?page_num=1
    [1] => https://www.scrapethissite.com/pages/forms/?page_num=2
    [2] => https://www.scrapethissite.com/pages/forms/?page_num=3
    [3] => https://www.scrapethissite.com/pages/forms/?page_num=4
    [4] => https://www.scrapethissite.com/pages/forms/?page_num=5
    [5] => https://www.scrapethissite.com/pages/forms/?page_num=6
    [6] => https://www.scrapethissite.com/pages/forms/?page_num=7
    [7] => https://www.scrapethissite.com/pages/forms/?page_num=8
    [8] => https://www.scrapethissite.com/pages/forms/?page_num=9
    [9] => https://www.scrapethissite.com/pages/forms/?page_num=10
    [10] => https://www.scrapethissite.com/pages/forms/?page_num=11
    [11] => https://www.scrapethissite.com/pages/forms/?page_num=12
    [12] => https://www.scrapethissite.com/pages/forms/?page_num=13
    [13] => https://www.scrapethissite.com/pages/forms/?page_num=14
    [14] => https://www.scrapethissite.com/pages/forms/?page_num=15
    [15] => https://www.scrapethissite.com/pages/forms/?page_num=16
    [16] => https://www.scrapethissite.com/pages/forms/?page_num=17
    [17] => https://www.scrapethissite.com/pages/forms/?page_num=18
    [18] => https://www.scrapethissite.com/pages/forms/?page_num=19
    [19] => https://www.scrapethissite.com/pages/forms/?page_num=20
    [20] => https://www.scrapethissite.com/pages/forms/?page_num=21
    [21] => https://www.scrapethissite.com/pages/forms/?page_num=22
    [22] => https://www.scrapethissite.com/pages/forms/?page_num=23
    [23] => https://www.scrapethissite.com/pages/forms/?page_num=24
)

Perfecto. Acabas de implementar el rastreo web en Goutte.

Paso 7: Extraer datos de todas las páginas

Ahora que tienes todas las URLs de las páginas almacenadas en un array, puedes rasparlas una a una mediante:

  1. Iterar sobre la lista
  2. Recuperación y análisis del contenido HTML de cada URL
  3. Extracción de los datos necesarios
  4. Almacenamiento de la información obtenida en la matriz $teams.

Implementa la lógica anterior de la siguiente manera:

$teams = [];

// iterate over all pages and scrape them all
foreach ($urls as $_ => $url) {
  // logging which page the scraper is currently working on
  echo "Scraping webpage \"$url\"...\n";

  // retrieve the HTML of the current page and parse it
  $crawler = $client->request("GET", $url);

  // $table = $crawler-> ...
  // data extraction logic
}

Observe la instrucción echo para registrar la página actual en la que está operando el scraper. Esa información es útil para entender lo que el script está haciendo durante la ejecución.

¡Precioso! Sólo queda exportar los datos raspados a un formato legible por humanos como CSV.

Paso 8: Exportar los datos a CSV

En este momento, los datos raspados se almacenan en la matriz $teams. Para que otros equipos puedan acceder a ellos y sea más fácil analizarlos, expórtalos a un archivo CSV.

PHP proporciona soporte incorporado para la exportación CSV a través de la función fputcsv(). Utilícela para escribir los datos raspados en un archivo llamado teams.csv como se indica a continuación:

// open the output file for writing
$file = fopen("teams.csv", "w");

// write the header row
fputcsv($file, ["Team Name", "Year", "Wins", "Losses", "OT Losses", "Win %","Goals For (GF)",        "Goals Against (GA)", "+ / -"]);

// append each team as a new row
foreach ($teams as $team) {
  fputcsv($file, [
    $team["team"],
    $team["year"],
    $team["wins"],
    $team["losses"],
    $team["ot_losses"],
    $team["win_perc"],
    $team["goals_for"],
    $team["goals_against"],
    $team["goal_diff"]
  ]);
}

// close the file
fclose($file);

Misión cumplida. El rascador Goutte es completamente funcional.

Paso 9: Póngalo todo junto

Tu script de Goutte web scraping debería contener ahora:

<?php

require_once __DIR__ . "/vendor/autoload.php";

use Goutte\Client;

function getPaginationUrls($client, $url)
{
  // connect to the first page of the site
  $crawler = $client->request("GET", $url);

  // initialize the list of URLs to scrape with the current URL
  $urls = [$url];

  // select the pagination link elements
  $crawler->filter(".pagination li a")->each(function ($a) use (&$urls) {
    // construct the absolute URL
    $url = "https://www.scrapethissite.com" . $a->attr("href");

    // add the pagination URL to the list only if it is not already present
    if (!in_array($url, $urls)) {
      $urls[] = $url;
    }
  });

  return $urls;
}

// initialize a new Goutte HTTP client
$client = new Client();

// get the URLs of the pages to scrape
$urls = getPaginationUrls($client, "https://www.scrapethissite.com/pages/forms/?page_num=1");

// where to store the scraped data
$teams = [];

// iterate over all pages and scrape them all
foreach ($urls as $_ => $url) {
  // logging which page the scraper is currently working on
  echo "Scraping webpage \"$url\"...\n";

  // retrieve the HTML of the current page and parse it
  $crawler = $client->request("GET", $url);

  // select the table element with the data of interest
  $table = $crawler->filter("#hockey .table");

  // iterate over each row and extract data from them
  $table->filter("tr.team")->each(function ($tr) use (&$teams) {
    // data extraction logic

    $teamElement = $tr->filter(".name");
    $team = trim($teamElement->text());

    $yearElement = $tr->filter(".year");
    $year = trim($yearElement->text());

    $winsElement = $tr->filter(".wins");
    $wins = trim($winsElement->text());

    $lossesElement = $tr->filter(".losses");
    $losses = trim($lossesElement->text());

    $otLossesElement = $tr->filter(".ot-losses");
    $otLosses = trim($otLossesElement->text());

    $pctElement = $tr->filter(".pct");
    $pct = trim($pctElement->text());

    $gfElement = $tr->filter(".gf");
    $gf = trim($gfElement->text());

    $gaElement = $tr->filter(".ga");
    $ga = trim($gaElement->text());

    $diffElement = $tr->filter(".diff");
    $diff = trim($diffElement->text());

    // add the scraped data to the array
    $teams[] = [
      "team" => $team,
      "year" => $year,
      "wins" => $wins,
      "losses" => $losses,
      "ot_losses" => $otLosses,
      "win_perc" => $pct,
      "goals_for" => $gf,
      "goals_against" => $ga,
      "goal_diff" => $diff
    ];
  });
}

// open the output file for writing
$file = fopen("teams.csv", "w");

// write the header row
fputcsv($file, ["Team Name", "Year", "Wins", "Losses", "OT Losses", "Win %","Goals For (GF)",        "Goals Against (GA)", "+ / -"]);

// append each team as a new row
foreach ($teams as $team) {
  fputcsv($file, [
    $team["team"],
    $team["year"],
    $team["wins"],
    $team["losses"],
    $team["ot_losses"],
    $team["win_perc"],
    $team["goals_for"],
    $team["goals_against"],
    $team["goal_diff"]
  ]);
}

// close the file
fclose($file);

Ejecútalo con este comando:

php index.php

El scraper registraría la siguiente salida:

Scraping webpage "https://www.scrapethissite.com/pages/forms/?page_num=1"...
// omitted for brevity..
Scraping webpage "https://www.scrapethissite.com/pages/forms/?page_num=24"...

Al final de la ejecución, aparecerá en la carpeta del proyecto un archivo teams.csv con estos datos:

El archivo CSV de salida

¡Et voilà! Los datos exactos del sitio de destino están ahora disponibles en un formato estructurado.

Alternativas a la librería PHP Goutte para Web Scraping

Como se mencionó al principio de este artículo, Goutte está obsoleto y ya no se mantiene. Esto significa que deberías considerar soluciones alternativas.

el anuncio en GitHub de la desaparición de la biblioteca

Como se explica en GitHub, dado que Goutte v4 se ha convertido esencialmente en un proxy de la clase HttpBrowser de Symfony, deberías migrar a ella. Para ello, solo necesitas instalar estas librerías:

composer require symfony/browser-kit symfony/http-client

Entonces, reemplaza:

use Goutte\Client;

con

use Symfony\Component\BrowserKit\HttpBrowser;

Por último, elimina Goutte como dependencia en tu proyecto. La API subyacente sigue siendo la misma, por lo que no deberías tener que cambiar mucho en tu script.

En lugar de Goutte, también puede combinar un cliente HTTP con un analizador HTML. Algunas alternativas recomendadas:

  • Guzzle o cURL para realizar peticiones HTTP.
  • Dom\HTMLDocument, Simple HTML DOM Parser, o DomCrawler para analizar HTML en PHP.

Todas estas alternativas le dan más flexibilidad y garantizan que su script de web scraping siga siendo mantenible a largo plazo.

Limitaciones de este enfoque del Web Scraping

Goutte es una herramienta potente, pero su uso para el web scraping tiene varias limitaciones:

  • La biblioteca está obsoleta
  • Su API ya no se mantiene
  • Está sujeta a limitadores de tarifa y bloques antirrobo
  • No puede gestionar páginas dinámicas basadas en JavaScript
  • Tiene un soporte de proxy integrado limitado, que es esencial para evitar bloqueos de IP.

Algunas de estas limitaciones se pueden mitigar mediante el uso de bibliotecas alternativas o enfoques diferentes, como se cubre en nuestra guía sobre web scraping con PHP. Aún así, siempre te enfrentarás a medidas anti-scraping que sólo pueden ser eludidas utilizando una API de Web Unlocker.

Una API de Web Unlocker es un punto final de raspado especializado diseñado para eludir las protecciones anti-bot y recuperar el HTML sin procesar de cualquier página web. Su uso es tan sencillo como realizar una llamada a la API y analizar el contenido devuelto. Este enfoque se integra perfectamente con Goutte (o los componentes actualizados de Symfony), tal y como se demuestra en este artículo.

Conclusión

En esta guía, usted exploró lo que es Goutte y lo que ofrece para el web scraping a través de un tutorial paso a paso. Dado que esta librería está obsoleta, también has tenido la oportunidad de explorar algunas de sus alternativas.

Independientemente de la biblioteca de scraping PHP que elija, el mayor reto es que la mayoría de los sitios web protegen sus datos mediante tecnologías anti-bot y anti-scraping. Estos mecanismos pueden detectar y bloquear solicitudes automatizadas, haciendo que los métodos tradicionales de scraping sean ineficaces.

Afortunadamente, Bright Data ofrece un conjunto de soluciones para evitar cualquier problema:

  • Web Unlocker: Una API que elude las protecciones anti-scraping y entrega HTML limpio de cualquier página web con el mínimo esfuerzo.
  • Navegador de raspado: Un navegador controlable basado en la nube con renderizado JavaScript. Maneja automáticamente CAPTCHAs, huellas digitales del navegador, reintentos, y más para usted. Se integra perfectamente con Panther o Selenium PHP.
  • API de raspado web: Puntos finales para el acceso programático a datos web estructurados de docenas de dominios populares.

¿No quiere ocuparse del web scraping pero sigue interesado en ‘datos web en línea’? Explore nuestros conjuntos de datos listos para usar.

Regístrese ahora en Bright Data y comience su prueba gratuita para probar nuestras soluciones de scraping.

No se requiere tarjeta de crédito