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 curl, file_get_contents
, Symfony 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
Вы должны увидеть, что он корректно извлекает ссылки:
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
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:
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
Хотя 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:
Для начала работы установите 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
orcreateSeleniumClient
instead ofcreateFirefoxClient
.
Поскольку 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.
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
При выполнении веб-скрапинга практически на любом реальном сайте вы, скорее всего, столкнетесь с ситуацией, когда все данные загружаются не полностью, потому что информация размещается по частям на страницах. Пагинация может быть двух типов:
- 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
orexample.com/page/3
. - 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
andlibxml
- Парсинг статического сайта с помощью 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.