Як використовувати API сайту, у якого немає API?

У мене досить часто постає завдання отримати дані від стороннього сайту, при цьому далеко не завжди цей сайт надає можливість зручно отримати ці дані через API. Єдине рішення у такому випадку — парсити html вміст сторінок. Коли я писав регэкспы, потім з'явилися бібліотеки, що дозволяють отримати потрібний вміст по css-селектору, а зараз і це видається складним завданням, яку хотілося б спростити.

Сьогодні я хочу розповісти вам про мою невеликий бібліотеці, що дозволяє описати в API-стилі http-запити і парсити відповідь сервера в потрібний вам формат.

Примітка: не варто забувати про авторські права, якщо ви використовуєте чужі дані.

Установка
Бібліотека доступна до установки через composer, тому все, що необхідно зробити — це додати залежність «sleeping-owl/apist»: «1.*» у ваш composer.json і викликати composer update.

У даної бібліотеки немає залежностей від будь-яких фреймворків, тому ви можете використовувати його з будь фреймворком, або ж в чистому PHP-проект. Для мережевих запитів використовується Guzzle, для маніпуляцій з dom-деревом використовується «symfony/dom-crawler».

Використання
Після установки ви можете приступити до створення нового класу, який уособлює API потрібного вам сайту. Бібліотека не накладає ніяких обмежень на те, як і де ви будете створювати свій клас. Потрібно розширити клас SleepingOwl\Apist\Apist і задати базовий url:

use SleepingOwl\Apist\Apist;

class HabrApi extends Apist
{
protected $baseUrl = 'http://habrahabr.ru';
}

Це все, що потрібно для базового опису. Далі ви можете додавати в даний клас методи, які вам потрібні:

public function index()
{
return $this- > get('/', [
'title' => Apist::filter('.page_head .title')->text()->trim(),
'posts' => Apist::filter('.posts .post')->each([
'title' => Apist::filter('h1.title a')->text(),
'link' => Apist::filter('h1.title a')->attr('href'),
'hubs' => Apist::filter('.hubs a')->each(Apist::filter('*')->text()),
'author' => [
'username' => Apist::filter('.author a'),
'profile_link' => Apist::filter('.author a')->attr('href'),
'rating' => Apist::filter('.author .rating')->text()
]
])
]);
}

Тут метод «get» — це тип використовуваного http-запиту, також доступні інші методи (post, put, patch, delete і т.д.).
Перший параметр — урл даного методу, він може бути як відносних, так і абсолютних.
Другий параметр — це і є та основа, з-за якої я створив цю бібліотеку. Він описує структуру, яку необхідно отримати в результаті виклику даного методу. Це може бути як масив, так і одиночне значення. Тобто для описаного вище методу результат буде такого виду:

$api = new HabrApi;
$result = $api->index();

Примітка: результат буде типу array, json-формат тут використаний для зручності.

{
"title": "Публікації",
"posts": [
{
"title": "Перевірте свого хостера на вразливість Shellshock (частина 2)",
"link": "http:\/\/habrahabr.ru\/company\/host-tracker\/blog\/240389\/",
"hubs": [
"Блозі компанії ХостТрекер",
"Серверне адміністрування",
"Інформаційна безпека"
],
"author": {
"username": "smiHT",
"profile_link": "http:\/\/habrahabr.ru\/users\/smiHT\/",
"rating": "26,9"
}
},
{
"title": "Курси етичного хакінгу і тестування на проникнення від PentestIT",
"link": "http:\/\/habrahabr.ru\/company\/pentestit\/blog\/240995\/",
"hubs": [
"Блозі компанії PentestIT",
"Навчальний процес в IT",
"Інформаційна безпека"
],
"author": {
"username": "pentestit-team",
"profile_link": "http:\/\/habrahabr.ru\/users\/pentestit-team\/",
"rating": "36,4"
}
},
...
]
}

Третім опціональним параметром можуть йти будь-які додаткові параметри запиту get або post змінні, завантажувані файли, заголовки запиту і т.п. З повним списком можна ознайомитися документації Guzzle.

Створення фільтрів
Пара слів про те, як це працює: кожен об'єкт, створений через Apist::filter($cssSelector) після завантаження даних замінюється на потрібне значення, він зберігає не лише сам селектор, за яким він буде шукати дані, але і всю низку викликів, які до нього були застосовані. Після завантаження даних він намагається застосувати ці методи до знайдених елементів.

Ось деякі типи методів, які можуть бути застосовані (ви можете комбінувати їх в потрібній вам послідовності):
  • Методи класу Symfony\Component\DomCrawler\Crawler для переміщення по dom-дерева і отримання даних:

    Apist::filter('.navbar li')->eq(3)->filter('a.active')->text();
    Apist::filter('input')->first()->attr('value');
    Apist::filter('.content')->html();
    

  • Створені мною методи:

    Apist::filter('body')->element();
    // Поверне об'єкт класу Symfony\Component\DomCrawler\Crawler, що відповідає за елемент body
    
    Apist::filter('.post')->each(...);
    // Цей об'єкт буде замінений на масив, кожен елемент якого буде створений згідно зі схемою, яка була передана параметром. Всі внутрішні css-селектори будуть застосовані щодо поточного елемента.
    
    Apist::filter('.errors')->exists()->then(...)->else(...);
    // Описує умову, якщо елемент з класом "errors" був знайдений, то використовується значення з блоку "then", інакше з блоку "else"
    

  • PHP-функції або функції, описані в кореневому namespace. При цьому поточний елемент буде переданий в якості першого параметра, а іншими параметрами будуть ті, що ви вказали при ініціалізації.

    Apist::filter('.title')->text()->mb_strtoupper()->trim()->substr(5);
    
    function myFunc($string, $find, $replace)
    {
    return str_replace($find, $replace, $string);
    }
    Apist::filter('.title')->text()->myFunc('My', 'Your');
    // Якщо прибрати ->text(), то функцію буде переданий об'єкт, а не рядок. Це можна використовувати в своїх цілях при необхідності.
    

Исходники демо-класу HabrApi.php, використовуваного в прикладах на сайті проекту можна подивитися тут.

Исходники на GitHub | Документація та приклади

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.