В этом руководстве по веб-скреппингу от Goutte вы узнаете:
- Что представляет собой PHP-библиотека Goutte
- Как использовать его для веб-скреппинга в пошаговом руководстве
- Альтернативы Goutte для веб-скреппинга
- Ограничения этого подхода и возможные решения
Давайте погрузимся!
Что такое гутте?
Goutte – это PHP-библиотека для скрейпинга и веб-скрейпинга, предлагающая интуитивно понятный API для навигации по веб-сайтам и извлечения данных из HTML/XML-ответов. Она включает в себя интегрированный HTTP-клиент и возможности разбора HTML, позволяющие получать веб-страницы через HTTP-запросы и обрабатывать их для сбора данных.
Примечание: С 1 апреля 2023 года Goutte больше не поддерживается и считается устаревшим. Однако на момент написания этой заметки он все еще надежно функционирует.
Как выполнять веб-скраппинг с помощью Goutte: пошаговое руководство
Следуйте этому пошаговому руководству и узнайте, как использовать Goutte для извлечения данных с сайта “Хоккейные команды“:
Задача состоит в том, чтобы извлечь данные из приведенной выше таблицы и экспортировать их в CSV-файл.
Пора научиться выполнять веб-скреппинг с помощью Goutte!
Шаг №1: Настройка проекта
Прежде чем приступить к работе, убедитесь, что ваша система соответствует требованиям Goutte – PHP7.1 или выше. Чтобы проверить текущую версию PHP, выполните следующую команду:
php -v
Результат должен выглядеть примерно так:
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
Если ваша версия PHP ниже 7.1, вам необходимо обновить PHP, прежде чем приступать к работе.
Далее имейте в виду, что Goutte будет устанавливаться через Composer – менеджер зависимостей для PHP. Если Composer не установлен в вашей системе, скачайте его с официального сайта и следуйте инструкциям по установке.
Теперь создайте новый каталог для вашего проекта Goutte и перейдите в него в терминале:
mkdir goutte-parser
cd goutte-parser
Затем с помощью команды composer init ини
циализируйте проект Composer в этой папке:
composer init
Composer предложит вам ввести такие детали проекта, как название и описание пакета. Ответы по умолчанию подойдут, но не стесняйтесь настраивать их в соответствии с вашими целями.
Теперь откройте папку с проектом в вашей любимой IDE PHP. Хорошо подойдут Visual Studio Code с расширением PHP или IntelliJ WebStorm.
Создайте пустой файл index.php
в папке проекта, который должен содержать:
php-html-parser/
├── vendor/
├── composer.json
└── index.php
Откройте файл index.php
и добавьте следующую строку кода для импорта библиотек Composer:
<?php
require_once __DIR__ . "/vendor/autoload.php";
// scraping logic...
Этот файл вскоре будет содержать логику скраппинга Goutte.
Теперь вы можете выполнить свой сценарий с помощью этой команды:
php index.php
Отлично! Вы готовы приступить к сбору данных с помощью Goutte на PHP.
Шаг №2: Установка и настройка Goutte
Установите Goutte с помощью приведенной ниже команды Compose:
composer require fabpot/goutte
Это добавит зависимость fabpot/goutte
в ваш файл composer.json
, который теперь будет включать:
"require": {
"fabpot/goutte": "^4.0"
}
В файле index.php
импортируйте Goutte, добавив следующую строку кода:
use Goutte\Client;
Это открывает HTTP-клиент Goutte, который можно использовать для подключения к целевой странице, разбора ее HTML и извлечения из нее данных. Как это сделать, смотрите в следующем шаге!
Шаг № 3: Получите HTML целевой страницы
Сначала создайте новый HTTP-клиент Goutte:
$client = new Client();
За кулисами класс Goutte’s Client
– это просто обертка вокруг компонента BrowserKit\HttpBrowser
от Symfony. Посмотрите на него в действии в нашем руководстве по веб-скреппингу с помощью Laravel.
Затем сохраните URL целевой веб-страницы в переменной и используйте метод request()
для получения ее содержимого:
$url = "https://www.scrapethissite.com/pages/forms/";
$crawler = $client->request("GET", $url);
Он отправляет GET-запрос на веб-страницу, получает ее HTML-документ и разбирает его для вас. В частности, объект $crawler
предоставляет доступ ко всем методам компонента DomCrawler
от Symfony. $crawler
– это объект, который вы будете использовать для навигации и извлечения данных со страницы.
Потрясающе! Теперь у вас есть все, что нужно для веб-скрапинга в Goutte.
Шаг № 4: Подготовка к соскабливанию интересующих данных
Прежде чем извлекать данные, необходимо ознакомиться с HTML-структурой целевой страницы.
Во-первых, помните, что интересующие вас данные представлены в виде строк внутри таблицы. Поскольку таблица содержит несколько строк, массив – отличная структура данных, в которой можно хранить собранные данные:
$teams = [];
Теперь сосредоточьтесь на HTML-структуре таблицы. Зайдите на целевую страницу в браузере, щелкните правой кнопкой мыши на таблице, содержащей интересующие вас данные, и выберите опцию “Инспектировать”:
В DevTools вы увидите, что таблица имеет класс table
и содержится в элементе
id=``"``хоккей``"
. Это означает, что вы можете нацелиться на таблицу с помощью следующего CSS-селектора:
#hockey .table
Примените CSS-селектор для выбора узла таблицы с помощью метода $crawler->filter()
:
$table = $crawler->filter("#hockey .table");
Затем обратите внимание, что каждая строка представлена элементом
team
. Выберите все строки и пройдитесь по ним итерацией, готовясь извлечь из них данные:
$table->filter("tr.team")->each(function ($tr) use (&$teams) {
// data extraction logic...
});
Замечательно! Теперь у вас есть готовый скелет для сбора данных Goutte.
Шаг #5: Реализация логики извлечения данных
Как и раньше, на этот раз проверьте строки внутри таблицы:
Вы можете заметить, что каждая строка содержит следующую информацию в специальных колонках:
- Название команды → внутри элемента
.name
- Год сезона → внутри элемента
.year
- Количество побед → внутри элемента
.wins
- Количество потерь → внутри элемента
.losses
- Потери в овертайме → внутри элемента
.ot-losses
- Процент побед → внутри элемента
.pct
- Забитые голы (Goals For – GF) → внутри элемента
.gf
- Пропущенные голы (Goals Against – GA) → внутри элемента
.ga
- Разница мячей → внутри элемента
.diff
Чтобы получить один фрагмент информации, необходимо выполнить эти два действия:
- Выберите элемент HTML с помощью
функции filter()
- Извлеките его текстовое содержимое с помощью метода
text()
и удалите лишние пробелы с помощьюtrim()
.
Например, вы можете выудить название команды с помощью:
$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());
После извлечения интересующих вас данных из строки сохраните их в массиве $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
];
После просмотра всех строк массив $teams
будет содержать:
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
)
)
Потрясающе! Скраппинг данных Goutte выполнен успешно.
Шаг #6: Реализуйте логику ползания
Не забывайте, что целевой сайт представляет данные на нескольких страницах, показывая только часть за раз. Под таблицей находится элемент пагинации, который предоставляет ссылки на все страницы:
Таким образом, вы можете управлять пагинацией в своем скрипте скраппинга с помощью этих простых шагов:
- Выберите элементы ссылок пагинации
- Извлеките URL-адреса страниц с пагинацией
- Зайдите на каждую страницу и примените логику скраппинга, разработанную ранее
Начните с изучения элементов ссылок пагинации:
Обратите внимание, что вы можете выбрать все ссылки пагинации с помощью следующего CSS-селектора:
.pagination li a
Чтобы реализовать шаг 2 и собрать все 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;
}
});
Инициализирует список URL, в котором будут храниться ссылки пагинации, начиная с URL первой страницы. Затем он выбирает все элементы пагинации и перебирает их, добавляя новые URL в массив $urls
только в том случае, если их еще нет. Поскольку URL-адреса на странице являются относительными, их необходимо преобразовать в абсолютные URL-адреса перед добавлением в список.
Поскольку обработка пагинации должна выполняться только один раз и не связана напрямую с извлечением данных, лучше всего обернуть ее в функцию:
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;
}
Вы можете вызвать функцию getPaginationUrls()
следующим образом:
$urls = getPaginationUrls($client, "https://www.scrapethissite.com/pages/forms/?page_num=1");
После выполнения $urls
будет содержать все пагинационные URL:
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
)
Отлично! Вы только что реализовали веб-ползание в Goutte.
Шаг №7: соскребите данные со всех страниц
Теперь, когда все URL-адреса страниц хранятся в массиве, вы можете скрести их по одному:
- Итерация над списком
- Получение и разбор HTML-содержимого для каждого URL-адреса
- Извлечение необходимых данных
- Храните собранную информацию в массиве
$teams
.
Реализуйте приведенную выше логику следующим образом:
$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
}
Обратите внимание на инструкцию echo
, которая регистрирует текущую страницу, на которой работает скребок. Эта информация полезна для понимания того, что делает скрипт во время выполнения.
Великолепно! Осталось только экспортировать собранные данные в человекочитаемый формат, например CSV.
Шаг #8: Экспортируйте собранные данные в CSV
Сейчас собранные данные хранятся в массиве $teams
. Чтобы сделать их доступными для других команд и более удобными для анализа, экспортируйте их в CSV-файл.
PHP обеспечивает встроенную поддержку экспорта данных в формате CSV с помощью функции fputcsv()
. Используйте ее, чтобы записать собранные данные в файл с именем teams.csv
, как показано ниже:
// 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);
Миссия выполнена! Скребок Goutte полностью готов к работе.
Шаг № 9: Соберите все вместе
Теперь ваш скрипт веб-скрапинга Goutte должен содержать:
<?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);
Запустите его с помощью этой команды:
php index.php
Скребок выдаст следующий результат:
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"...
По окончании выполнения в папке проекта появится файл teams.csv
, содержащий эти данные :
И вуаля! Точные данные с целевого сайта теперь доступны в структурированном формате.
Альтернативы библиотеке PHP Goutte для веб-скрапинга
Как уже упоминалось в начале статьи, Goutte устарел и больше не поддерживается. Это означает, что вам следует рассмотреть альтернативные решения.
Как объясняется на GitHub, поскольку Goutte v4 по сути стал прокси для класса HttpBrowser
из Symfony, вам следует перейти на него. Для этого вам нужно установить эти библиотеки:
composer require symfony/browser-kit symfony/http-client
Затем замените:
use Goutte\Client;
с
use Symfony\Component\BrowserKit\HttpBrowser;
Наконец, удалите Goutte как зависимость в своем проекте. Базовый API остался прежним, поэтому вам не придется ничего менять в своем скрипте.
Вместо Goutte вы можете использовать HTTP-клиент и HTML-парсер. Некоторые рекомендуемые альтернативы:
- Guzzle или cURL для выполнения HTTP-запросов.
Dom\HTMLDocument
, Simple HTML DOM Parser илиDomCrawler
для разбора HTML в PHP.
Все эти альтернативы обеспечивают большую гибкость и гарантируют, что ваш скрипт веб-скреппинга останется работоспособным в долгосрочной перспективе.
Ограничения данного подхода к веб-скрапингу
Goutte – мощный инструмент, но его использование для веб-скрапинга имеет ряд ограничений:
- Библиотека устарела
- Его API больше не поддерживается
- На него распространяются ограничители скорости и блоки, предотвращающие отскабливание.
- Он не может работать с динамическими страницами, основанными на JavaScript
- Он имеет ограниченную встроенную поддержку прокси, которая необходима для предотвращения IP-запретов.
Некоторые из этих ограничений можно смягчить, используя альтернативные библиотеки или другие подходы, как описано в нашем руководстве по веб-скрапингу с помощью PHP. Тем не менее, вы всегда будете сталкиваться с мерами по борьбе со скрапингом, которые можно обойти только с помощью API Web Unlocker.
Web Unlocker API – это специализированная конечная точка для скраппинга, предназначенная для обхода защиты от ботов и получения необработанного HTML-файла любой веб-страницы. Использовать его очень просто – достаточно сделать вызов API и разобрать полученный контент. Этот подход легко интегрируется с Goutte (или обновленными компонентами Symfony), как показано в этой статье.
Заключение
В этом руководстве вы узнали, что такое Goutte и что она предлагает для веб-скраппинга, с помощью пошагового руководства. Поскольку эта библиотека уже устарела, у вас также была возможность изучить некоторые ее альтернативы.
Независимо от того, какую библиотеку PHP-скрепинга вы выберете, главная проблема заключается в том, что большинство веб-сайтов защищают свои данные с помощью технологий защиты от ботов и скрепинга. Эти механизмы могут обнаруживать и блокировать автоматические запросы, делая традиционные методы скрапинга неэффективными.
К счастью, Bright Data предлагает набор решений, позволяющих избежать любых проблем:
- Web Unlocker: API, позволяющий обойти защиту от скаппинга и получить чистый HTML с любой веб-страницы с минимальными усилиями.
- Браузер для скрапинга: Облачный, управляемый браузер с рендерингом JavaScript. Он автоматически обрабатывает CAPTCHA, отпечатки пальцев браузера, повторные попытки и многое другое за вас. Он легко интегрируется с Panther или Selenium PHP.
- API для веб-скрапинга: Конечные точки для программного доступа к структурированным веб-данным из десятков популярных доменов.
Не хотите заниматься веб-скреппингом, но все еще заинтересованы в “онлайновых веб-данных”? Изучите наши готовые к использованию наборы данных!
Зарегистрируйтесь на сайте Bright Data прямо сейчас и начните бесплатную пробную версию, чтобы протестировать наши решения для сбора информации.
Кредитная карта не требуется