У меня достаточно часто появляется задача получить данные от стороннего сайта, при этом далеко не всегда этот сайт предоставляет возможность удобно получить эти данные через API. Единственное решение в таком случае — парсить html содержимое страниц. Когда-то я писал регэкспы, потом появились библиотеки, позволяющие получить нужное содержимое по css-селектору, а сейчас и это кажется сложной задачей, которую хотелось бы упростить.
Сегодня я хочу рассказать вам о моей небольшой библиотеке, позволяющей описать в API-стиле http-запросы и парсить ответ сервера в нужный вам формат.
Примечание: не стоит забывать об авторских правах, если вы используете чужие данные.
Установка
Библиотека доступна к установке через composer, поэтому все, что необходимо сделать — это добавить зависимость «sleeping-owl/apist»: «1.*» в ваш composer.json и вызвать composer update.
У данной библиотеки нет зависимостей от каких-либо фреймворков, поэтому вы можете использовать ее с любым фреймворком, либо же в чистом PHP-проекте. Для сетевых запросов используется Guzzle, для манипуляций с dom-деревом используется «symfony/dom-crawler».
Использование
После установки вы можете приступить к созданию нового класса, олицетворяющего API нужного вам сайта. Библиотека не накладывает никаких ограничений на то, как и где вы будете создавать свой класс. Нужно расширить класс SleepingOwlApistApist и указать базовый урл:
use SleepingOwlApistApist;
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) после загрузки данных заменяется на нужное значение, он сохраняет не только сам селектор, по которому он будет искать данные, но и всю вереницу вызовов, которые к нему были применены. После загрузки данных он пытается применить эти методы к найденным элементам.
Вот некоторые типы методов, которые могут быть применены (вы можете комбинировать их в нужной вам последовательности):
- Методы класса SymfonyComponentDomCrawlerCrawler для перемещения по 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(); // Вернет объект класса SymfonyComponentDomCrawlerCrawler, отвечающий за элемент 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 | Документация и примеры
Автор: sleeping-owl