Некоторые веб-разработчики сталкивались с такой проблемой, как создание автоматизированного входа в приватную часть сайта, который нужно, например, спарсить или просто сделать какие-то действия роботом или даже ИИ без участия человека.
В этой статье, я опишу, как реализовать такой механизм. За основу буду брать самый популярный язык программирования у бэкекнд-разработчиков - это PHP + использовать буду Framework Laravel. Итак разложим по полочкам как такое реализовать.
Сначала расскажу, в чем проблема. Проблема заключается в том, что приватный контент сайта защищен от какого-либо просмотра неавторизованными юзерами. Соответственно, чтобы осуществить вход роботом или ИИ движком в чужой проект, нужно осуществить авторизацию на сайте.
Для начала напишем следующий класс, который бы помог нам реализовать данную идею.
Назовем его ClientService.php.
Вот код класса:
namespace App\Classes; use GuzzleHttp\Client; use Symfony\Component\DomCrawler\Crawler; class ClientService { const MIN_DELAY = 2000; const MAX_DELAY = 5000; public $client; protected $stack; protected $options = []; protected $proxies; protected $current_proxy = 0; public function __construct() { } /** * @param array $options */ public function setOptions(array $options): ClientService { $this->options = $options; return $this; } /** * @param array $options */ public function addOptions(array $options): ClientService { $this->options = array_merge($this->options, $options); return $this; } public function newRequest(string $url, array $options = [], string $method = 'GET') { if (!$this->client) { $this->client = new Client(); } $options = array_merge($this->getDefaultOptions(), $this->options, $options); return $this->client->request($method, $url, $options); } protected function getHtml(string $url, array $options = [], string $method = 'GET') { return $this->newRequest($url, $options, $method)->getBody()->getContents(); } public function getCrawler(string $url, array $options = [], string $method = 'GET') { $html = $this->getHtml($url, $options, $method); $crawler = new Crawler(); $crawler->addHtmlContent($html, 'UTF-8'); return $crawler; } protected function getDefaultOptions() { return [ 'delay' => random_int(static::MIN_DELAY, static::MAX_DELAY) ]; }
В классе, мы будем использовать компонент Crawler. Он нам понадобиться для получения контента и установки кодировки исходного кода, полученного от стороннего сайта. По сути класс ClientService является оберткой того, что мы потом реализуем в своем классе Parsing.php.
Однако перед тем я хотел бы немного рассказать, почему так нужно. Дело в том, что когда мы пытаемся парсить приватную страницу сайта, защищенную системой аутентификации, в этом случае сервер не вернет Response код 200. Вместо этого мы получит такой ответ Response, что-то вроде этого сообщения: 401 - Unauthorized: Access is denied due to invalid credentials.
Потому нам нужно спроектировать класс Parsing. Вот его код:
namespace App\Classes; use GuzzleHttp\Cookie\CookieJar; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use PHPHtmlParser\Dom; use Symfony\Component\DomCrawler\Crawler; class Parsing { private $main_url; //'https://site.ru'; private $login; private $password; public function __construct($login, $password) { $this->login = $login; //'OpticSite@mail.ru', $this->password = $password; //'AwsGGu' } public function set_url($url) { $this->main_url = $url; } public function get_url() { return $this->main_url; } public function Catalog($numpage = 1) { ini_set('memory_limit', '9182M'); $service = new ClientService(); $cookieJar = new CookieJar(); $baseUrl = $this->get_url(); $formData = [ 'AUTH_FORM' => 'Y', 'TYPE' => 'AUTH', 'backurl' => '/login/', 'USER_LOGIN' => $this->login, 'USER_PASSWORD' => $this->password, 'USER_REMEMBER' => 'Y' ]; $loginPageUrl = "$baseUrl/login"; $loginHtml = $service->newRequest($loginPageUrl, [ 'cookies' => $cookieJar, 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', ], ])->getBody()->getContents(); $loginCrawler = new Crawler($loginHtml, $baseUrl); $loginForm = $loginCrawler->filter('form[name="form_auth"]')->form($formData, 'POST'); try { $loginResponse = $service->newRequest($loginForm->getUri(), [ 'form_params' => $loginForm->getValues(), 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', ], 'cookies' => $cookieJar, ], 'POST'); if ($loginResponse->getStatusCode() === 200) { if ($numpage == 1) $catalog_url = $baseUrl."/home/catalog/?SECTION_ID=11"; else $catalog_url = $baseUrl."/home/catalog/?SECTION_ID=11&PAGEN_1=".$numpage; $response = $service->client->get($catalog_url, [ 'cookies' => $cookieJar, // Используем те же куки ]); return $this->GetAllURL($response->getBody()); } else { return 'Login failed. Please check your credentials.'; } } catch (\Exception $e) { //print_r('An error occurred: ' . $e->getMessage() . ': ' . $e->getLine()); return $e->getMessage() . ': ' . $e->getLine(); } } ////////////////////////////////////////////////////////////// // // Вернуть данные // ////////////////////////////////////////////////////////////// public function GetPage($url) { if ($url == 'https://site.ru/') { $Arr = Array(); return $Arr; } else { ini_set('memory_limit', '9182M'); $service = new ClientService(); $cookieJar = new CookieJar(); $baseUrl = $this->get_url(); $formData = [ 'AUTH_FORM' => 'Y', 'TYPE' => 'AUTH', 'backurl' => '/login/', 'USER_LOGIN' => $this->login, 'USER_PASSWORD' => $this->password, 'USER_REMEMBER' => 'Y' ]; $loginPageUrl = "$baseUrl/login"; $loginHtml = $service->newRequest($loginPageUrl, [ 'cookies' => $cookieJar, 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', ], ])->getBody()->getContents(); $loginCrawler = new Crawler($loginHtml, $baseUrl); $loginForm = $loginCrawler->filter('form[name="form_auth"]')->form($formData, 'POST'); $loginResponse = $service->newRequest($loginForm->getUri(), [ 'form_params' => $loginForm->getValues(), 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', ], 'cookies' => $cookieJar, ], 'POST'); if ($loginResponse->getStatusCode() === 200) { /* $response = $service->client->get($baseUrl.$url, [ 'cookies' => $cookieJar, // Используем те же куки ]); */ try { $response = $service->client->get($url, [ 'cookies' => $cookieJar, ]); } catch (\GuzzleHttp\Exception\RequestException $e) { if ($e->hasResponse()) { $errorResponse = $e->getResponse(); print_r("Response: " . $errorResponse->getBody()); } } finally { $Arr = array(); if (isset($response)) { $dom = new Dom; $dom->loadStr($response->getBody()); $contents = $dom->find('.product-item-detail-properties-item'); foreach ($contents as $content) { $content_site = str_replace('_', ' ', $content->find('.product-item-detail-properties-value')[0]->text); $Arr['property'][] = array($content->find('.product-item-detail-properties-name')[0]->text, $content_site); } if (!is_null($dom->find('.my-3')[0])) $Arr['property'][] = array('Название', $dom->find('.my-3')[0]->text); if (!is_null($dom->find('.btn-soft-github')[0])) { $val = $dom->find('.btn-soft-github')[0]->text; $val = str_replace(' ', '', $val); $Arr['property'][] = array('Цена', $val); } if (!is_null($dom->find('.product-item-detail-slider-container'))) { $contener_img = $dom->find('.product-item-detail-slider-container')[0]->id; $pos = strpos($contener_img, 'big'); $euro_price = substr($contener_img, 0, $pos) . "price"; //Лимит bx_117848907_42073_quant_limit $limit = substr($contener_img, 0, $pos) . "quant_limit"; $old_price = substr($contener_img, 0, $pos) . "old_price"; if (!is_null($dom->find("#" . $limit)[0])) { $result_limit = $dom->find("#" . $limit)[0]->innerHTML; $Arr['property'][] = array('Остаток', $result_limit); } if (!is_null($dom->find("#" . $old_price)[0])) { $old_price = $dom->find("#" . $old_price)[0]->innerHTML; $old_price = trim(str_replace('€', '', $old_price)); $Arr['property'][] = array('Старая цена', $old_price); } $identic = str_replace('bx_', '', $contener_img); $identic = str_replace('_big_slider', '', $identic); $Arr['property'][] = array('ID_идентификатор', $identic); if (!is_null($dom->find('#' . $euro_price))) { $euro_ = $dom->find('#' . $euro_price)->Text; $euro_ = trim(str_replace('€', '', $euro_)); $Arr['property'][] = array('Цена в Евро', $euro_); } } if (!is_null($dom->find('.product-item-detail-slider-controls-image')[0])) { $Arr['property'][] = array('Картинки', $dom->find('.product-item-detail-slider-controls-image')[0]->innerHtml); } else { $Arr['property'][] = array('Картинки', $dom->find('.product-item-detail-slider-image')[0]->innerHtml); } if (!is_null($dom->find('.product-item-detail-slider-controls-image'))) { $v = $dom->find('.product-item-detail-slider-controls-image'); foreach ($v as $it) { $Arr['property'][] = array('Картинки', $it->innerHtml); } } if (!is_null($dom->find('.mb-2'))) { $data = $dom->find('.mb-2'); //->innerHTML; if (!is_null($data)) foreach ($data as $key => $content) { if (!is_null($content)) foreach ($content as $it) { if ($it->text == 'Ориентировочные сроки поставки:') { $Arr['property'][] = array('Ориентировочные сроки поставки', $data[$key]->innerHtml); } } } } $Arr['property'][] = array('URL', $url); } // Вернуть все данные return $Arr; } } else { // Вернуть только ошибку и никаких данных $Arr[] = 'Ошибка подключения!'; return $Arr; } } } public function GetAllURL($Body) { $dom = new Dom; $dom->loadStr($Body); //dd($dom->outerHtml); $Arr = Array(); $contents = $dom->find('.img-section'); foreach ($contents as $content) $Arr[] = $content->href; //$Arr[] = $content->src; return $Arr; }
Здесь я привел в пример частный случай класса, который автоматически авторизуется на сайте, а потом парсит данные используя для этого соответствующие библиотеки. Как видно из кода для парсинга мы использовали use PHPHtmlParser\Dom;
А для системы авторизации на сайте написали следующий use:
use GuzzleHttp\Cookie\CookieJar; use Symfony\Component\DomCrawler\Crawler;
Для того, чтобы использовать данные компоненты, нужно предварительно проинсталлировать их через composer.
Чтобы воспользоваться данным классом, можно написать следующий код:
use App\Classes\Parsing; $Pars = new Parsing('User@mail.ru', 'PWD123'); // Логин и пароль для авторизации $Pars->set_url('https://site.ru'); // Сеттер для установки адреса сайта $url = 'https://site.ru/catalog/?page=1'; // Страница для парсинга $Arr = $Pars->GetPage($url); // Возвращаем методом GetPage спарсенные данные со страницы указанной в $url dd($Arr); // Выводим их на странице
Таким легким и простым способом, мы можем создать класс Parsing, передав конструктору логин и пароль клиента. А затем установим значение текущего сайта, и определим какую страницу мы хотим парсить. Вернем метод GetPage и сформируем массив $Arr, в котором будут все исходные данные, полученые из парсенных данных сайта.