PHP та автоматизоване збору даних

image

Зміст:

Стаття нижче підводить проміжний підсумок серії матеріалів по світу дата-харвестингу за допомогою мов програмування. Сайт проксі сервер Dexodata з системою гнучких налаштувань вже вивчив потенціал Python, JavaScript та мови Java. Тепер час попрацювати з PHP (в майбутньому ми плануємо торкнутися й інших тем).

Існує думка, що ця опція застаріла. Чому розкривати її для читачів, яким потрібна оренда проксі для роботи з власноруч написаною програмою? Причини є. PHP є серверною скриптовою мовою, що з'явилася в 1995 році. В цьому статусі вона досі обслуговує потреби веб-розробників та розвивається (остання версія PHP — 8.4):  

  • В інтернет-світі не так багато людей, які не знають, що таке WordPress. Ця CMS працює на PHP та підтримує близько 40% сайтів у світі. Не всі вони популярні. 40% сторінок не дорівнюють 40% трафіку або 40% даних. Але частка суттєва; 
  • Окрім управління контентом, в 80% випадків PHP забезпечує серверні потреби сайтів (коли цей факт можна встановити);
  • На PHP досі з'являються сучасні продукти. Користувачі, що звертаються до наших найкращих резидентних та мобільних проксі для роботи в соціальних мережах, можуть не знати, але Slack побудований на PHP. Так, це не соцмережа, але схожі елементи там є.

Як мова, спочатку «предрасположена» для інтернету, PHP є робочим варіантом для сценаріїв низької та середньої складності. З його допомогою реально:

  1. Ініціювати запити;
  2. Забирати інформацію;
  3. Зберігати дані в підходящому форматі, як то CSV, JSON або XML.

Підсумок: PHP є ресурсом для парсингу, оскільки він здатний взаємодіяти з сайтами, розуміти та обробляти HTML, а також збирати інформацію про сторінку за допомогою ряду функцій та бібліотек. Він не такий просунутий та гнучкий, і його не слід вибирати для динамічного контенту. І все ж він є життєздатною альтернативою. Ми говоримо про це як експерти в сфері веб-скрейпінгу.

Основи та вимоги до дата-харвестингу з PHP

Перш ніж рухатися далі, варто впевнитися в наступних моментах:

  1. HTTP-запити. Тут ми маємо на увазі протокол, застосовуваний серверами для відправки та отримання інформації. Щоб прийняти дані про сайт, користувач повинен надіслати HTTP-запит на сервер, що відповідає за розміщення цієї сторінки. В PHP є такі вбудовані функції, як cURL, file_get_contents(), а також fopen(), що дозволяють направляти HTTP-запити та «знімати» інформацію зі сторінок;
  2. Парсинг HTML. Як мова, призначена для «конструювання» веб-сторінок, PHP пропонує такі вбудовані функції, як DOMDocument та SimpleXMLElement, що дозволяють аналізувати HTML для отримання даних;
  3. Регулярні вирази слугують інструментом для співставлення шаблонів та здійснення маніпуляцій з текстовим контентом. Знову ж, і тут PHP пропонує вбудовані функції, наприклад, preg_match() разом з preg_replace(), що дозволяють використовувати регулярні вирази для скрейпінгу.

Цих понять буде достатньо, якщо користувачі мають намір створити в PHP просту програму для передачі HTTP-запитів на сайт, «ухоплення» HTML та аналізу коду.

 

Автоматизований збір даних через PHP: Пробний сценарій

 

Припустимо, необхідно витягти відомості, що містяться на сайті e-commerce у вигляді списку товарів (нижче показується простий випадок, у наступних розділах будуть більш складні задачі). Потрібно отримати найменування, вартість та зображення кожного предмета та зберегти цю інформацію у вигляді CSV. Ось найпрямолінійніший шлях.

<?php

$url = 'https://sample.com/items';

$html = file_get_contents($url);

$dom = new DOMDocument();

$dom->loadHTML($html);

$items = $dom->getElementsByTagName('article');

$data = [];

foreach ($items as $item) { // Fixed variable name from $items to $item

    $name = $item->getElementsByTagName('h2')[0]->textContent;

    $price = $item->getElementsByTagName('span')[0]->textContent;

    $image = $item->getElementsByTagName('img')[0]->getAttribute('src');

    $data[] = [$name, $price, $image];

}

$fp = fopen('items.csv', 'w');

foreach ($data as $fields) {

    fputcsv($fp, $fields);

}

fclose($fp);

?>

Ми спочатку передаємо HTTP-запит на сайт за допомогою file_get_contents(), потім переключаємося на синтаксичний аналіз вмісту HTML з DOMDocument та отримуємо відповідну інформацію за допомогою методу getElementsByTagName(). Потім можна зберегти отримані дані в масиві для запису в CSV-файл через fputcsv()

 

Еволюція. Бібліотеки та інструменти PHP

1. Goutte

 

Goutte слугує повнофункціональною бібліотекою в рамках роботи з PHP. Її здатні освоїти новачки, і все завдяки зрозумілому об'єктно-орієнтованому характеру. Що стосується інших переваг, то це ком'юніті вражаючих розмірів, обширна документація і, нарешті, швидкість. Через те, що Goutte покладається на HTTP 1.1, браузеру потрібно лише одне з'єднання з сервером. Після цього одне й те ж з'єднання буде використовуватися для всіх наступних запитів. Є й недоліки. Наприклад, можуть виникнути непереборні перешкоди щодо динамічного контенту.

Спробуємо Goutte, щоб зробити клік на потрібному сайті (як і при інших подібних маніпуляціях, не забувайте про резидентні проксі від Dexodata, а також про наші мобільні адреси та серверні IP від 3,65 USD за 1 Гб).

Для початку встановимо Composer. Так можна буде управляти залежностями і взагалі працювати з бібліотеками. Даємо вводну в терміналі:

composer require fabpot/goutte

Потім створюємо PHP-скрипт і називаємо, як хочемо. Відкриваємо його і вносимо код для початку роботи з Goutte:

require 'vendor/autoload.php';

use Goutte\Client;

$client = new Client();

Goutte ми запустили! Ставимо цей код в кінці файлу, щоб ухопити URL через функцію client->request().

$url = "http://instance.com/";

$crawler = $client->request('GET', $url);

 

// Click on the "More information..." link

$link = $crawler->selectLink('More information…')->link();

$crawler = $client->click($link);

Ось так ми й відправилися до потрібної сторінки і дали команду на клік.

 

2. Simple HTML DOM

 

Будучи парсером, ця програма призначена для роботи з будь-яким документом HTML. Плюс в тому, що його легко освоїти. Він не строго прив'язаний до зовнішніх матеріалів, пов'язаних з JS, які потрібно було б підвантажувати окремо до старту роботи. Інші достоїнства включають швидкість (користувачам не потрібно завантажувати в пам'ять цілий сайт, тому можна одночасно обробляти кілька HTML-сторінок без шкоди для продуктивності) і простоту установки (немає необхідності ставити додаткове ПЗ або бібліотеки, достатньо PHP).

Обмеження теж є. Доступ до компонентів сторінки буде обмежений: можна виконувати маніпуляції зі структурою, але вміст залишиться поза зоною доступу. І взагалі треба непогано знати HTML. Але перейдемо до практики.

Ідемо на сторінку з документацією Simple HTML DOM. Копіюємо simple_html_dom.php. Вставляємо в активний проект. Генеруємо файл PHP (імена знову неважливі), нехай буде webscrapingstuff.php. Тепер відкриваємо файл і «озброюємо» проект бібліотекою Simple HTML DOM:

include('simple_html_dom_php');

Далі додаємо код, який направить нас до сторінки:

$html = file_get_html('http://instance.com/')

file_get_html, як частина Simple HTM DOM, здатна витягти HTML зі сторінки з заданим URL. Потім вона підвантажує DOM-об'єкт для зберігання змінної $html.

Припустимо, нам потрібен головний заголовок. Щоб отримати H1, застосовуємо код:

echo $html->find('h1', 0)->plaintext;

Відкриваємо підсумковий файл і знаходимо його там.

 

3. PHP Scraper

 

Як екосистема для етичного дата-харвестингу, Dexodata не може обійти увагою PHP Scraper. Для користувачів цей варіант відкриває канал для скрейпінгу за допомогою PHP-скриптів. Такий підхід не передбачає необхідності писати багато коду. Все, що потрібно, — це застосування таких незамысловатих команд, як find_element_by_class або find_element_by_id. Їх вистачить для збору елементів з сайту при введенні id та класу.

Але й без проблем не обійдеться. PHP Scraper може не відповідати кожному можливому варіанту використання. Його використання «заточене» під сайти на Apache або Nginx та налаштовані через активований за замовчуванням mod_rewrite. PHP Scraper — не варіант, якщо треба отримувати інформацію з API або аналізувати складні HTML-блоки.

Рухаємось далі.

Потрібно встановити Composer, як це було зроблено в розділі з Goutte. Після чого ставимо там код для активації:

composer require spekulatius/phpscraper

Установка позаду, автоматичний підвантажувач Composer підхопить наш пакет. Якщо користувач працює з VanillaPHP, то підвантажувачі треба інтегрувати в скрипт, ось так:

require 'vendor/autoload.php'

В якості вправи створимо проект, який здатний зайти на сайт, порахувати та зібрати розміщені там посилання. Запускаємо програму і даємо їй змінну:

$web = new \spekulatius\phpscraper();

Вказуємо програмі напрямок у вигляді URL:

$web->go(' [http://instance.com/](http://instance.com/) ');

Вносимо цей розділ коду, відповідальний за «висмикування» кожного посилання на сайті та виведення їх кількості.

// Print the number of links.

echo "This page contains " . count($web->links) . " links. ";

 

// Loop through the links

foreach ($web->links as $link) {

echo " - " . $link . " ";

}

 

/**

* This code will print out:

*

* This page contains 1 link.

*

* - https://www.iana.org/domains/instance

*/

Не забувайте: ми тут використовуємо вигадані адреси.

 

4. cURL для збору даних з PHP

 

Розглянувши три приклади, виділимо в окремий розділ, можливо, найвідомішу опцію — cURL. Це бібліотека та інструмент для роботи з командним рядком, що дозволяє відправляти та отримувати файли через HTTP та FTP. При його використанні можна застосовувати геотаргетовані проксі від Dexodata (в тому числі ті, що надаються в рамках безкоштовного пробного періоду), передавати дані через SSL-з'єднання та багато іншого. Протестуємо цей варіант на практиці.

Створюємо PHP-скрипт і використовуємо запропонований код:

// Initialize curl

$ch = curl_init();

// URL for Scraping

curl_setopt($ch, CURLOPT_URL,

'https://example.com');

// Retun Transfer True

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$output = curl_exec($ch);

// Closing cURL

curl_close($ch);

Визначення CURLOPT_RETURNTRANSFER як TRUE надає нам сторінку у вигляді рядка. А значить, наш код може забрати інформацію для подальших дій.

Взглянути на ці дані можна через echo з змінною для зберігання даних:

echo $output

Наступний крок — розмістити інформацію в документі DOM, що дозволить нам піддати її скрейпінгу.

$dom = new DOMDocument;

$dom->loadHTML($output);

В даний момент у нас є інформація у вигляді якоїсь HTML-структури всередині змінної. Розмістіть додатковий код, щоб роздрукувати кожне посилання, доступне на HTML-сторінці:

$tags = $dom->getElementsByTagName('a');

for($I=0; $I < $tags->length; $I++){

$link = $tags->item($i);

echo " - " . $link . " ";

}

Як екосистема геотаргетованих проксі, закликаємо не забувати про захист від виявлення з боку адміністраторів за допомогою адрес, які ми надаємо (наприклад, наші резидентні IP від інтернет-провайдерів або мобільні проксі з ротацією). Dexodata підкаже, як використовувати їх у cURL. Просто дотримуйтесь синтаксису:

curl --proxy <proxy-ip>:<proxy-port> <url>

В <<proxy-ip>> ставимо адресу, а секція <<proxy-port>> призначена для номера порту. На виході повинно вийти нещо на зразок:

curl --proxy 193.191.56.12:7070 -k https://example.com

Якщо використовуваний проксі є функціональним, а запит увінчався успіхом, наш скрипт покаже потрібний вміст. 

 

Створюємо скрейпер на PHP: приклад

 

Ще раз уявимо собі сайт e-commerce зі списком товарів, які ми хочемо витягти за допомогою PHP, вже на більш складному рівні. Ось набір дій від Dexodata.

Стартуйте з завантаження всього HTML-коду за рахунок можливостей PHP та cURL:

// initialize the cURL request 

$curl = curl_init(); 

// set the URL to reach with a GET HTTP request 

curl_setopt($curl, CURLOPT_URL, "https://scrapeme.live/shop/"); 

// get the data returned by the cURL request as a string 

curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 

// make the cURL request follow eventual redirects, 

// and reach the final page of interest 

curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); 

// execute the cURL request and 

// get the HTML of the page as a string 

$html = curl_exec($curl); 

// release the cURL resources 

curl_close($curl);

Тепер матеріал, пов'язаний з HTML, знаходиться в нашому розпорядженні у вигляді змінної $html. Наступний етап — завантажити $html в HtmlDomParser з функцією str_get_html():

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

use voku\helper\HtmlDomParser; 

$curl = curl_init(); 

curl_setopt($curl, CURLOPT_URL, "https://scrapeme.live/shop/"); 

curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 

curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); 

$html = curl_exec($curl); 

curl_close($curl); 

// initialize HtmlDomParser 

$htmlDomParser = HtmlDomParser::str_get_html($html);

Тепер можна застосувати HtmlDomParser для роботи з DOM для ініціювання скрейпінгу. Щоб це зробити, «висмикуємо» повний список усіх посилань, пов'язаних з пагінацією. Це дасть нам можливість просканувати весь сайт магазину. Щелкніть правою кнопкою миші на компонент HTML, пов'язаний з пагінацією, і виберіть «Inspect». Після цього DevTools покажуть нам елемент, пов'язаний з DOM.

Як збирати дані в Мережі з PHP та орендою проксі

В рамках нашого сценарію можна очікувати, що .page-numbers, в якості CSS-класу, вказують на пов'язаний з пагінацією HTML. Однак CSS-клас не обов'язково буде однозначно ідентифікувати потрібний нам компонент. Одразу кілька елементів можуть бути пов'язані з одним і тим же класом.

Тому, якщо є намір застосувати селектор CSS для «захоплення» компонентів у DOM, доведеться застосувати клас CSS у поєднанні з іншими селекторами. Зокрема, користувачі можуть застосувати помічник HtmlDomParser з .page-numbers в якості селектора CSS, щоб вибрати всі HTML-компоненти, пов'язані з нумерацією сторінок. Пройдемося по сайту, щоб отримати всі необхідні URL-адреси з атрибута href.

Дивіться:

// retrieve the HTML pagination elements with 

// the ".page-numbers a" CSS selector 

$paginationElements = $htmlDomParser->find(".page-numbers a"); 

$paginationLinks = []; 

foreach ($paginationElements as $paginationElement) { 

// populate the paginationLinks set with the URL 

// extracted from the href attribute of the HTML pagination element 

$paginationLink = $paginationElement->getAttribute("href"); 

// avoid duplicates in the list of URLs 

if (!in_array($paginationLink, $paginationLinks)) { 

$paginationLinks[] = $paginationLink; 

}  

}

 

// print the paginationLinks array 

print_r($paginationLinks);

Зверніть увагу, що find() дозволяє ухоплювати компоненти DOM на базі CSS селектора. В цьому гіпотетичному сценарії від Dexodata компоненти, пов'язані з пагінацією, представлені по два рази на кожній сторінці. Потрібно застосувати спеціальний трюк для вирішення проблеми дублікації компонентів у світлі масиву $paginationLinks.

Ось що ми побачимо, якщо спробуємо запустити наш скрипт:

Array ( 

[0] => https://scrapeme.live/shop/page/2/ 

[1] => https://scrapeme.live/shop/page/3/ 

[2] => https://scrapeme.live/shop/page/4/ 

[3] => https://scrapeme.live/shop/page/46/ 

[4] => https://scrapeme.live/shop/page/47/ 

[5] => https://scrapeme.live/shop/page/50/ 

)

Зверніть увагу, що всі адреси мають однакову структуру. Всі вони відзначені кінцевою цифрою, що відповідає за пагінацію. Ось як її витягти:

// remove all non-numeric characters in the last element of 

// the $paginationLinks array to retrieve the highest pagination number 

$highestPaginationNumber = preg_replace("/\D/", "", end($paginationLinks));

Припустимо, показник $highestPaginationNumber буде «50».

А тепер зберемо дані про кожен окремий продукт. Робимо правий клік по елементу і активуємо DevTools, а потім жмемо на «Inspect». Можна очікувати, що товар буде «зроблений» з HTML-елемента li.product, що включає URL, зображення, заголовок та цінник. Така інформація виходить з «a», «mg», «h2» та «span» елементів HTML.

Захоплюємо інформацію через HtmlDomParser:

$productDataLit = array(); 

// retrieve the list of products on the page 

$productElements = $htmlDomParser->find("li.product"); 

foreach ($productElements as $productElement) { 

// extract the product data 

$url = $productElement->findOne("a")->getAttribute("href"); 

$image = $productElement->findOne("img")->getAttribute("src"); 

$name = $productElement->findOne("h2")->text; 

$price = $productElement->findOne(".price span")->text; 

// transform the product data into an associative array 

$productData = array( 

"url" => $url, 

"image" => $image, 

"name" => $name, 

"price" => $price 

); 

$productDataList[] = $productData; 

}

На цій стадії наша логіка здатна зібрати всі фрагменти інформації про елемент з кожної сторінки і в подальшому зберегти її як масив $productDataList. Застосуємо її до всіх сторінок.

// iterate over all "/shop/page/X" pages and retrieve all product data 

for ($paginationNumber = 1; $paginationNumber <= $highestPaginationNumber; $paginationNumber++) { 

$curl = curl_init(); 

curl_setopt($curl, CURLOPT_URL, "https://scrapeme.live/shop/page/$paginationNumber/"); 

curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 

$pageHtml = curl_exec($curl); 

curl_close($curl); 

$paginationHtmlDomParser = HtmlDomParser::str_get_html($pageHtml); 

// scraping logic... 

}

Місія виконана.

 

Підсумкові зауваження

 

Завершуючи цей огляд, наведемо кілька підсумкових зауважень, важливих при скрейпінгу. Постарайтеся не бути поміченими при харвестингу даних, для цього використовуйте проксі (інакше IP може потрапити під обмеження). Ось ще один ремайндер про те, як це можна зробити при роботі з cURL.

curl_setopt($curl, CURLOPT_PROXY, "<PROXY_URL>"); 

curl_setopt($curl, CURLOPT_PROXYPORT, "<PROXY_PORT>"); 

curl_setopt($curl, CURLOPT_PROXYTYPE, "<PROXY_TYPE>");

1. Додатковий рівень захисту може також дати HTTP-заголовок користувацького агента. За визначенням cURL завжди буде встановлювати щось на зразок curl/XX.YY.ZZ. Ідентифікувати користувача з таким заголовком та зрозуміти, що йдеться про скрейпінг, не складе великої праці. Замість цього спробуйте тут «пограти» з заголовком.

curl_setopt($curl, CURLOPT_USERAGENT, "<USER_AGENT_STRING>");

В результаті повинно вийти нещо на зразок цього прикладу з інтернету:

curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6")

2. Ще одна ремарка від Dexodata стосується динамічного контенту. Ми вже підкреслювали, що звичайний запит GET з cURL вам з ним не допоможе, знадобляться додаткові методи. Поверніться до нашого гайду, присвяченого JS, та вивчіть тему headless-браузерів. Це допоможе створити парсер, що взаємодіє з сайтом як реальний користувач та витягує динамічний контент. 

3. Наше третє зауваження присвячене паралельному парсингу. Многопоточність у PHP — тема складна, але подеколи неминуча. Ось варіант вирішення цієї проблеми від Dexodata. Наша концепція полягає в тому, щоб скрипт міг виконуватися на кількох сторінках, застосовуючи параметри HTTP GET не до всього сайту, а до його частин. 

Модифікуйте скрипт, щоб він не охоплював сайт цілком, а зосередився на більш дрібних паралельних фрагментах. Ось як можна встановити межі таких «кусочків»: 

$from = null; 

$to = null; 

if (isset($_GET["from"]) && is_numeric($_GET["from"])) { 

$from = $_GET["from"]; 

if (isset($_GET["to"]) && is_numeric($_GET["to"])) { 

$to = $_GET["to"]; 

if (is_null($from) || is_null($to) || $from > $to) { 

die("Invalid from and to parameters!"); 

// scrape only the pagination pages whose number goes 

// from "$from" to "$to" 

for ($paginationNumber = $from; $paginationNumber <= $to; $paginationNumber++) { 

// scraping logic... 

}

// write the data scraped to a database/file

Тепер користувач може проставити в браузері такі приклади, як посилання нижче, щоб обробити їх одразу кілька. Скрейпінг буде виконуватися паралельно.

https://instance.com/scripts/scrapeme.live/scrape-products.php?from=1&to=5 

https://instance.com/scripts/scrapeme.live/scrape-products.php?from=6&to=10 

І так далі.

Ось і все. Дякуємо за те, що пройшли цей шлях з Dexodata, етичною екосистемою проксі, що застосовує стандарти KYC та AML для гарантії вашої безпеки.

Back

Ми їмо печиво. Дізнайтеся більше про політику використання файлів cookie