ООО "Солнечный Ветер" Ларионов Андрей Николаевич
Создатель проекта: ООО "Солнечный Ветер" Ларионов Андрей Николаевич
Система автоматического захода на сайт через авторизацию для парсинга данных

Система автоматического захода на сайт через авторизацию для парсинга данных


Автор: admin Дата: 2025-02-23 11:23:19

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

В этой статье, я опишу, как реализовать такой механизм. За основу буду брать самый популярный язык программирования у бэкекнд-разработчиков - это 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, в котором будут все исходные данные, полученые из парсенных данных сайта.

Если вы считаете текст данного блога оскорбительным или некорректным, напишите об этом по адресу IntegralAL@mail.ru с URL-адресом блога. В теме письма пишите <Жалоба>, либо <Неккоректно>.

Теги блога:

Web-программирование
Технологии
Laravel
Назад к блогам