Веб-скрапинг с помощью PHP: пошаговое руководство

Узнайте, как легко создать свой собственный простой веб-парсер с помощью PHP с нуля.
2 min read
Web scraping with PHP

Thanks to its extensive libraries and tools, PHP is a great language for building web scrapers. Designed specifically for web development, PHP handles web scraping tasks with ease and reliability.

There are many different methods for scraping websites using PHP, and you’ll explore a few different methods in this article. Specifically, you’ll learn how to scrape websites using curlfile_get_contentsSymfony BrowserKit, and Symfony’s Panther component. Additionally, you’ll learn about some common challenges you may face during web scraping and how to avoid them.

Contents

Web Scraping with PHP

В этом разделе вы познакомитесь с несколькими часто используемыми методами веб-скрапинга как относительно простых, так и более сложных, а также динамических сайтов.

Please note: While we cover various methods in this tutorial, this is by no means an exhaustive list.

Prerequisites

To follow along with this tutorial, you need the latest version of PHP and Composer, a dependency manager for PHP. This article was tested using PHP 8.1.18 and Composer 2.5.5.

Once PHP and Composer are set up, create a directory named php-web-scraping and cd into it:

mkdir php-web-scraping
cd $_

В этом каталоге вы будете работать вплоть до завершения курса.

curl

curl — низкоуровневая библиотека и CLI-инструмент, написанный на языке C. С его помощью можно получить содержимое страницы по протоколу HTTP или HTTPS. Практически на всех платформах PHP поставляется с поддержкой curl.

В этом разделе вы будете парсить очень простую страницу, на которой представлены страны, добавленные в список в соответствии с количеством их населения, по актуальным данным ООН. Вы извлечете ссылки в меню вместе с их анкорами.

To start, create a file called curl.php and then initialize curl in that file with the curl_init function:

<?php
$ch = curl_init();

Then set the options for fetching the web page. This includes setting the URL and the HTTP method (GET, POST, etc.) using the function curl_setopt:

curl_setopt($ch, CURLOPT_URL, 'https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)');

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

In this code, you set the target URL to the web page and the method to GET. The CURLOPT_RETURNTRANSFER tells curl to return the HTML response.

Once curl is ready, you can make the request using curl_exec:

$response = curl_exec($ch);

Получение HTML-данных — только первый шаг в веб-скрапинге. Для извлечения данных из готового HTML-ответа можно применить сразу несколько методов. Наиболее простой способ — использование регулярных выражений для получения самых простых HTML-файлов. Однако следует иметь в виду, что с помощью regex не получится парсить произвольный HTML-код. Однако для максимально простого веб-скрапинга использования regex вполне достаточно.

For example, extract the <a> tags, which have href and title attributes and contain a <span>:

if(! empty($ch)) {
    preg_match_all(
        '/<a href="([^"]*)" title="([^"]*)"><span>([^<]*)<\/span><\/a>/',
        $response, $matches, PREG_SET_ORDER
    );
    foreach($matches as $link) {
        echo $link[1] . " => " . $link[3] . "\n";
    }
}

Then release the resources by using the curl_close function:

curl_close($ch);

Выполните следующий код:

php curl.php

Вы должны увидеть, что он корректно извлекает ссылки:

php curl.php

curl gives you very low-level control over how a web page is fetched over HTTP/HTTPS. You can fine-tune the different connection properties and even add additional measures, such as proxy servers (more on this later), user agents, and timeouts.

Кроме того, curl установлен по умолчанию в большинстве операционных систем, что делает его отличным выбором для написания кроссплатформенного веб-парсера.

Однако, как вы увидели, одного curl недостаточно, поскольку для полноценного веб-скрапинга данных необходим HTML-парсер. curl также не может выполнять на странице JavaScript. Последнее означает, что с его помощью не получится выполнить парсинг динамических страниц и одностраничных приложений (SPA).

file_get_contents

The file_get_contents function is primarily used for reading the contents of a file. However, by passing an HTTP URL, you can fetch HTML data from a web page. This means file_get_contents can replace the usage of curl in the previous code.

В этом разделе вы будете парсить ту же страницу, что и ранее, но на этот раз скрапер будет более продвинутым, и вы сможете извлечь из таблицы названия всех стран.

Create a file named file_get-contents.php and start by passing a URL to file_get_contents:

<?php

$html = file_get_contents('https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)');

The $html variable now holds the HTML code of the web page.

Similar to the previous example, fetching the HTML data is just the first step. To spice things up, use libxml to select elements using XPath selectors. To do that, you first need to initialize a DOMDocument and load the HTML into it:

$doc = new DOMDocument;
libxml_use_internal_errors(true);
$doc->loadHTML($html);
libxml_clear_errors();

Here, you select the countries in the following order: the first tbody element, a tr element inside the tbody, the first td in the tr element, and an a with a title attribute inside the td element.

The following code initializes a DOMXpath class and uses evaluate to select the element using the XPath selector:

$xpath = new DOMXpath($doc);

$countries = $xpath->evaluate('(//tbody)[1]/tr/td[1]//a[@title=true()]');

Остается только выполнить цикл перебора элементов и вывести текст:

foreach($countries as $country) {
    echo $country->textContent . "\n";
}

Выполните следующий код:

php file_get_contents.php
php file_get_contents.php

As you can see, file_get_contents is simpler to use than curl and is often used to quickly fetch the HTML code of a web page. However, it suffers the same drawbacks as curl—you need an additional HTML parser, and you can’t scrape dynamic web pages and SPAs. Additionally, you lose the fine-tuned controls provided by curl. However, its simplicity makes it a good choice for scraping basic static sites.

Symfony BrowserKit

Symfony BrowserKit is a component of the Symfony framework that simulates the behavior of a real browser. This means you can interact with the web page like in an actual browser; for example, clicking on buttons/links, submitting forms, and going back and forward in history.

In this section, you’ll visit the Bright Data blog, enter PHP in the search box, and submit the search form. Then you’ll scrape the article names from the result:

Блог Bright Data, раздел PHP

To use Symfony BrowserKit, you must install the BrowserKit component with Composer:

composer require symfony/browser-kit

You also need to install the HttpClient component to make HTTP requests over the internet:

composer require symfony/http-client

BrowserKit supports selecting elements using XPath selectors by default. In this example, you use CSS selectors. For that, you need to install the CssSelector component as well:

composer require symfony/css-selector

Create a file named symfony-browserkit.php. In this file, initialize HttpBrowser:

<?php
require "vendor/autoload.php";

use Symfony\Component\BrowserKit\HttpBrowser;

$client = new HttpBrowser();

Use the request function to make a GET request:

$crawler = $client->request('GET', 'https://brightdata.com/blog');

To select the form where the search button is, you need to select the button itself and use the form function to get the enclosing form. The button can be selected with the filter function by passing its ID. Once the form is selected, you can submit it using the submit function of the Httpbrowser class.

By passing a hash of the values of the inputs, the submit function can fill up the form before it’s submitted. In the following code, the input with the name q has been given the value PHP, which is the same as typing PHP into the search box:

$form = $crawler->filter('#blog_search')->form();

$crawler = $client->submit($form, ['q' => 'PHP']);

The submit function returns the resulting page. From there, you can extract the article names using the CSS selector .col-md-4.mb-4 h5:

$crawler->filter(".col-md-4.mb-4 h5")->each(function ($node) {
    echo $node->text() . "\n";
});

Выполните следующий код:

php symfony-browserkit.php
php symfony-browserkit.php

Хотя Symfony BrowserKit является шагом вперед по сравнению с двумя предыдущими методами с точки зрения взаимодействия со страницей, он все еще ограничен, поскольку не может выполнять JavaScript. Это означает, что с помощью BrowserKit невозможно парсить динамические сайты и SPA-приложения.

Symfony Panther

Symfony Panther is another Symfony component that wraps around the BrowserKit component. However, Symfony Panther offers one major advantage: instead of simulating a browser, it executes the code in an actual browser using the WebDriver protocol to remotely control a real browser. This means you can scrape any website, including dynamic websites and SPAs.

In this section, you’ll load the OpenWeather home page, type the name of your city in the search box, perform the search, and scrape the current weather of your city:

Домашняя страница OpenWeather

Для начала работы установите Symfony Panther и Composer:

composer require symfony/panther

You also need to install dbrekelmans/browser-driver-installer, which can automatically detect the installed browser on your system and install the correct driver for it. Make sure you have either a Firefox- or a Chromium-based browser installed in your system:

composer require dbrekelmans/bdi

To install the appropriate driver in the drivers directory, run the bdi tool:

vendor/bin/bdi detect drivers

Create a file named symfony-panther.php and start by initializing a Panther client:

<?php
require 'vendor/autoload.php';

use Symfony\Component\Panther\Client;


$client = Client::createFirefoxClient();

Note: Depending on your browser, you may need to use createChromeClient or createSeleniumClient instead of createFirefoxClient.

Поскольку Panther использует Symfony BrowserKit, последующий код будет очень напоминать тот, что присутствовал в разделе с Symfony BrowserKit.

You start by loading the web page using the request function. When the page loads, it’s initially covered by a div with the owm-loader class, which shows the loading progress bar. You need to wait for this div to disappear before you start interacting with the page. This can be done using the waitForStaleness function, which takes a CSS selector and waits for it to be removed from the DOM.

After the loading bar is removed, you need to accept the cookies so that the cookies banner is closed. For that, the selectButton function comes in handy, as it can search a button by its text. Once you have the button, the click function performs a click on it:

$client->request('GET', 'https://openweathermap.org/');
try {
    $crawler = $client->waitForStaleness(".owm-loader");
} catch (Facebook\WebDriver\Exception\NoSuchElementException $e) {

}
$crawler->selectButton('Allow all')->click();

Note: Depending on how fast the page loads, the loading bar may disappear before the waitForStaleness function runs. This throws an exception. That’s why that line has been wrapped in a try-catch block.

OpenWeather

Now it’s time to type Kolkata into the search bar. Select the search bar with the filter function and use the sendKeys function to provide input to the search bar. Then click on the Search button:

$crawler->filter('input[placeholder="Search city"]')->sendKeys('Kolkata');
$crawler->selectButton('Search')->click();

Once the button is selected, an autocomplete suggestion box pops up. You can use the waitForVisibility function to wait until the list is visible and then click on the first item using the combination of filter and click as before:

$crawler = $client->waitForVisibility(".search-dropdown-menu li");
$crawler->filter(".search-dropdown-menu li")->first()->click();

Finally, use waitForElementToContain to wait for the results to load, and extract the current temperature using filter:

$crawler = $client->waitForElementToContain(".orange-text+h2", "Kolkata");
$temp = $crawler->filter(".owm-weather-icon+span.orange-text+h2")->text();

echo $temp;

Here, you’re waiting for the element with selector .orange-text+h2 to contain Kolkata. This indicates that the results have been loaded.

Выполните следующий код:

php symfony-panther.php

Полученный результат будет выглядеть следующим образом:

Web Scraping Challenges and Possible Solutions

Even though PHP makes it easy to write web scrapers, navigating real-life scraping projects can be complex. Numerous situations can arise, presenting challenges that need to be addressed. These challenges may stem from factors such as the structure of the data (eg pagination) or antibot measures taken by the owners of the website (eg honeypot traps).

В этом разделе вы узнаете о некоторых распространенных проблемах и способах их решения.

Navigating through Paginated Websites

При выполнении веб-скрапинга практически на любом реальном сайте вы, скорее всего, столкнетесь с ситуацией, когда все данные загружаются не полностью, потому что информация размещается по частям на страницах. Пагинация может быть двух типов:

  1. All the pages are located at separate URLs. The page number is passed through a query parameter or a path parameter. For example, example.com?page=3 or example.com/page/3.
  2. The new pages are loaded using JavaScript when the Next button is selected.

In the first scenario, you can load the pages in a loop and scrape them as separate web pages. For instance, using file_get_contents, the following code scrapes the first ten pages of an example site:

for($page = 1; $page <= 10; $page++) {
    $html = file_get_contents('https://example.com/page/{$page}');
    // DO the scraping
}

Во втором случае необходимо использовать решение, способное выполнять JavaScript. Например, Symfony Panther. В данном примере необходимо нажать на соответствующую кнопку, которая откроет следующую страницу. Понадобится только немного подождать, пока она полностью загрузится:

for($page = 1; $page <= 10; $page++>) {
    // Do the scraping

    // Load the next page
    $crawler->selectButton("Next")->click();
    $client->waitForElementToContain(".current-page", $page+1)
}

Note: You should substitute appropriate waiting logic that makes sense for the particular website that you’re scraping.

Rotating Proxies

Прокси-сервер выступает в роли посредника между вашим компьютером и целевым веб-сервером. Он не позволяет последнему увидеть настоящий IP-адрес, сохраняя вашу анонимность.

However, you shouldn’t rely on one single proxy server since it can be banned. Instead, you need to use multiple proxy servers and rotate through them. The following code provides a very basic solution where an array of proxies is used and one of them is chosen at random:

$proxy      =   array();
$proxy[]    =   '1.2.3.4';
$proxy[]    =   '5.6.7.8';

// Add more proxies

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com");
curl_setopt($ch, CURLOPT_PROXY, $proxy[array_rand($proxy)]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);


$result =   curl_exec($ch);
curl_close($ch);

Handling CAPTCHAs

CAPTCHA используется на многих сайтах, чтобы убедиться, что пользователь является человеком, а не роботом. К сожалению, это повышает риск блокировки веб-парсера.

CAPTCHAs can be very primitive, like a simple checkbox asking, “Are you human?” Or they can use a more advanced algorithm, like Google’s reCAPTCHA or hCaptcha. You can probably get away with primitive CAPTCHAs using basic web page manipulation (eg checking a checkbox), but to battle advanced CAPTCHAs, you need a dedicated tool like 2Captcha. 2Captcha uses humans to solve CAPTCHAs. You simply need to pass the required details to the 2Captcha API, and it returns the solved CAPTCHA.

To get started with 2Captcha, you need to create an account and get an API key.

Установите 2Captcha с помощью Composer:

composer require 2captcha/2captcha

In your code, create an instance of TwoCaptcha:

$solver = new \TwoCaptcha\TwoCaptcha('YOUR_API_KEY');

Затем используйте 2Captcha для обхода CAPTCHA:

// Normal captcha
$result = $solver->normal('path/to/captcha.jpg');

// ReCaptcha
$result = $solver->recaptcha([
    'sitekey' => '6Le-wvkSVVABCPBMRTvw0Q4Muexq1bi0DJwx_mJ-',
    'url'   => 'https://mysite.com/page/with/recaptcha',
    'version' => 'v3',
]);

// hCaptcha

$result = $solver->hcaptcha([
    'sitekey'   => '10000000-ffff-ffff-ffff-000000000001',
    'url'       => 'https://www.site.com/page/',
]);

Avoiding Honeypot Traps

Ловушки для ботов представляют собой продвинутые меры по борьбе с роботами, которые служат для заманивания скраперов и краулеров, чтобы отвлечь их от реальной цели. Несмотря на то, что такие системы весьма полезны для предотвращения бот-атак, они могут стать серьезной проблемой при веб-скрапинге.

There are all kinds of measures you can take to avoid being lured into a honeypot trap. For instance, honeypot links are often hidden so that a real user doesn’t see them, but a bot can pick them up. To avoid the trap, you can try to avoid clicking on hidden links (links with display: none or visibility: none CSS properties).

Другой вариант — чередовать прокси-серверы, чтобы в случае, если один из IP-адресов прокси-сервера будет заблокирован или попадет в honeypot, к сайту можно было бы подключиться через другие.

Conclusion

Благодаря превосходной библиотеке и фреймворкам языка программирования PHP создать с его помощью веб-скрапер не составляет большого труда. В этой статье вы узнали, как выполнить следующее:

  • Парсинг статического сайта с помощью curl и regex
  • Scrape a static website using file_get_contents and libxml
  • Парсинг статического сайта с помощью Symfony BrowserKit и отправки форм
  • Парсинг сложного динамического сайта с помощью Symfony Panther

К сожалению, используя эти методы на практике, вы убедитесь, что веб-скрапинг с помощью PHP сопровождается дополнительными сложностями. Например, может потребоваться сразу несколько прокси и тщательно сконструированный веб-парсер, который позволит избежать различных ловушек для ботов.

If you don’t want to deal with these complexities, consider using the Bright Data Web Scraper IDE, an all-encompassing web scraping service. It can help you scrape websites without any hassle.