94KONG
Проверенные
- Сообщения
- 196
- Реакции
- 87
- Баллы
- 11,040
Введение
Иногда бывают задачи когда нужно реализовать обертку для работы с API некоторого сервиса для нужд заказчика и сделать подобною задачу в основном довольно просто, но в сервиса не всегда есть этот API, либо возникает мысль что лучше бы его не было, поэтому приходиться парсить полностью страницу контента.
В качестве примера для данной статьи мы будем использовать выданное
Сам парсер реализуем в виде компонента для удобного использования в Yii2.
Начнем
Создадим компонент ParserXenforo. Так как нам событий не нужно, вполне достаточно будет наследоваться от Object.
Нам необходимо добавить свойства и константы для загрузки страницы. Сами же свойства host, username, password, curlOpt, будут задаваться в настройках компонента.
Добавим методы загрузки страницы.
Первым реализуем метод для получения установленных значений header и user-agent которые будут храниться в curlOpt, и в будущем передаваться в параметры cURL
Для авторизации на форуме нужно передать через POST логин и пароль пользователя. Для этого сформируем url авторизации (host + url авторизации)
И строку POST запроса
Для сохранения авторизации будем использовать файл с cookies в runtime. Для получения полного пути этого файла, создадим метод который получает с alias пути полный путь и добавляет к нему название файла.
Реализуем метод парсинга страницы с переданными параметрами. Сначала мы переходим на action авторизации где передаем POST значения и возвращаемся на переданный url но уже в авторизированном аккаунте. На всякий случай. Так как например часто видел что на этом форуме устанавливают модуль скрытия контента от неавторизированных пользователей.
После успешной загрузки данных в _data, логируем методом Yii::info() что данные загруженные.
Базовая часть компонента реализованная. Теперь нужно его подключить в компонентах и настроить. Указав в user-agent данные своего компьютера например, где находиться компонент, базовый url и данные для авторизации.
Параметры для авторизации дали в демо admin:admin. Одно только но дали на несколько дней, а точнее до Mar 24, 2014 at 7:26 AM
В котроллере можем проверить работоспособность, вызвав в action и посмотреть в логах app.log все ли хорошо выполнилось
Парсинг данных
Начнем с создания метода для получения объекта класса DOMDocument нашей страницы и добавим свойство для хранения его. Перед тем отключив ошибки libxml и делаем обратное после загрузки. Чтобы избежать некоторых проблем с парсингом страницы. В итоге мы получаем DOM нашей страницы для дальнейшей работы с ним. Так же можно было бы использовать регулярные выражения. Но работа с DOM в данном случае более удобная.
Переходим к методу получения нового объекта класса DOMXPath, чтобы было удобно выполнять заданное XPath выражение для получения требуемых данных.
Ну все теперь можно смело переходить к выполнению XPath запросов для получения наших данных:title, timestamp и content.
Сначала получим заголовок и добавим свойство _title
Дальше timestamp нашей темы
Последнее получим контент
Вертаемся немного назад и рассмотрим более подробно, что за XPath запросы мы сделали
Cозданим метод для завершения парсинга (может и он не совсем нужен но все же более наглядно будет видно что парсинг данных завершен и все ли данные получили), а также методы для доступа к полученным данным
Вывод результатов
Компонент можно сказать что готов, можем посмотреть как он работает добавив в action нашего controller необхибые действия а view их вывод
В результате получаем подобный результат
Вывод
В этой статье мы рассмотрели как сделать парсер контента страницы в виде компонента для Yii на примере парсинга темы форума XenForo.
По-аналогии можно сделать парсинг и других данных, или же создать немного другой класс который будет использовать созданный нами для парсинга например всех тем форума, по-принципу:
Теоретический аспект не был затронут в данной статье, статья была ориентированная чтобы показать на более менее реальном но простом примере как получить данные страницы.
Иногда бывают задачи когда нужно реализовать обертку для работы с API некоторого сервиса для нужд заказчика и сделать подобною задачу в основном довольно просто, но в сервиса не всегда есть этот API, либо возникает мысль что лучше бы его не было, поэтому приходиться парсить полностью страницу контента.
В качестве примера для данной статьи мы будем использовать выданное
У Вас недостаточно прав для просмотра ссылок.
Вход или Регистрация
форума XenForo и заранее созданной темой, откуда будем парсить типичные данные: заголовок, время создания и сам текст темы, при этом парсинг будет осуществляться в авторизированном аккаунте форума. Все остальные данные можно будет взять по аналогии.Сам парсер реализуем в виде компонента для удобного использования в Yii2.
У Вас недостаточно прав для просмотра ссылок.
Вход или Регистрация
.Начнем
Создадим компонент ParserXenforo. Так как нам событий не нужно, вполне достаточно будет наследоваться от Object.
PHP:
namespace app\components;
use Yii;
use \yii\base\Object;
class ParserXenforo extends Object
{
}
Нам необходимо добавить свойства и константы для загрузки страницы. Сами же свойства host, username, password, curlOpt, будут задаваться в настройках компонента.
PHP:
namespace app\components;
use Yii;
use \yii\base\Object;
class ParserXenforo extends Object
{
/**
* Uri к действию авторизации на форуме
*/
const REQUEST_URI_LOGIN = 'login/login';
/**
* Название файла для сохранения cookies
*/
const COOKIES_FILE_NAME = 'cookies.txt';
/**
* @var string загруженные данные страницы
*/
private $_data;
/**
* @var string хост форума
*/
public $host;
/**
* @var string логин пользователя
*/
public $username;
/**
* @var string пароль пользователя
*/
public $password;
/**
* @var array конфигурация cURL
*/
public $curlOpt;
}
Добавим методы загрузки страницы.
Первым реализуем метод для получения установленных значений header и user-agent которые будут храниться в curlOpt, и в будущем передаваться в параметры cURL
PHP:
protected function getCurlOpt($nameOpt)
{
if ($nameOpt !== 'userAgent' && $nameOpt !== 'header') {
return false;
}
return $this->curlOpt[$nameOpt];
}
Для авторизации на форуме нужно передать через POST логин и пароль пользователя. Для этого сформируем url авторизации (host + url авторизации)
PHP:
protected function getLoginUrl()
{
return $this->host . self::REQUEST_URI_LOGIN;
}
И строку POST запроса
PHP:
protected function createPostRequestForCurl()
{
return 'login=' . $this->username . '&password=' . $this->password . '&remember=1';
}
Для сохранения авторизации будем использовать файл с cookies в runtime. Для получения полного пути этого файла, создадим метод который получает с alias пути полный путь и добавляет к нему название файла.
PHP:
protected function getPathToCookieFile($cookieFileName = self::COOKIES_FILE_NAME)
{
return Yii::getAlias('@app/runtime') . DIRECTORY_SEPARATOR . $cookieFileName;
}
Реализуем метод парсинга страницы с переданными параметрами. Сначала мы переходим на action авторизации где передаем POST значения и возвращаемся на переданный url но уже в авторизированном аккаунте. На всякий случай. Так как например часто видел что на этом форуме устанавливают модуль скрытия контента от неавторизированных пользователей.
После успешной загрузки данных в _data, логируем методом Yii::info() что данные загруженные.
PHP:
public function loadUsingCurl($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->loginUrl);
curl_setopt($ch, CURLOPT_FAILONERROR, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_REFERER, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->getCurlOpt('header'));
curl_setopt($ch, CURLOPT_COOKIEFILE, $this->pathToCookieFile);
curl_setopt($ch, CURLOPT_COOKIEJAR, $this->pathToCookieFile);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
curl_setopt($ch, CURLOPT_USERAGENT, $this->getCurlOpt('userAgent'));
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->createPostRequestForCurl());
$this->_data = curl_exec($ch);
if (curl_exec($ch) === false) {
throw new \Exception(curl_errno($ch) . ': ' . curl_error($ch));
}
curl_close($ch);
Yii::info(Yii::t('app', 'Loading data page'));
return $this;
}
Базовая часть компонента реализованная. Теперь нужно его подключить в компонентах и настроить. Указав в user-agent данные своего компьютера например, где находиться компонент, базовый url и данные для авторизации.
Параметры для авторизации дали в демо admin:admin. Одно только но дали на несколько дней, а точнее до Mar 24, 2014 at 7:26 AM
PHP:
....
'components' => [
...
'parser' => [
'class' => 'app\components\ParserXenforo',
'host' => 'http://9af5766eb2759a49.demo-xenforo.com/130/index.php?',
'username' => 'admin',
'password' => 'admin',
'curlOpt' => [
'userAgent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36',
'header' => [
'Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1',
'Accept-Language: en-US,en;q=0.8,ru;q=0.6,uk;q=0.4',
'Accept-Charset: Windows-1251, utf-8, *;q=0.1',
'Accept-Encoding: deflate, identity, *;q=0',
]
]
],
...
],
....
В котроллере можем проверить работоспособность, вызвав в action и посмотреть в логах app.log все ли хорошо выполнилось
PHP:
$urlThread = 'http://9af5766eb2759a49.demo-xenforo.com/130/index.php?threads/some-thread.1/';
/** @var \app\components\ParserXenforo $dataParse */
$dataParse = Yii::$app->parser->loadUsingCurl($urlThread);
Парсинг данных
Начнем с создания метода для получения объекта класса DOMDocument нашей страницы и добавим свойство для хранения его. Перед тем отключив ошибки libxml и делаем обратное после загрузки. Чтобы избежать некоторых проблем с парсингом страницы. В итоге мы получаем DOM нашей страницы для дальнейшей работы с ним. Так же можно было бы использовать регулярные выражения. Но работа с DOM в данном случае более удобная.
PHP:
public function createDomDocument()
{
$this->_dom = new \DOMDocument();
libxml_use_internal_errors(true);
if ($this->_dom->loadHTML($this->_data)) {
Yii::info(Yii::t('app', 'Create DomDocument'));
} else {
Yii::info(Yii::t('app', 'An error occurred when creating an object of class DOMDocument'));
}
libxml_use_internal_errors(false);
return $this;
}
Переходим к методу получения нового объекта класса DOMXPath, чтобы было удобно выполнять заданное XPath выражение для получения требуемых данных.
PHP:
public function createDomXpath()
{
$this->_xpath = new \DOMXPath($this->_dom);
Yii::info(Yii::t('app', 'Create DomXpath'));
return $this;
}
Ну все теперь можно смело переходить к выполнению XPath запросов для получения наших данных:title, timestamp и content.
Сначала получим заголовок и добавим свойство _title
PHP:
public function parseTitle()
{
$xpathQuery = '*//h1';
$nodes = $this->_xpath->query($xpathQuery, $this->_dom);
if ($nodes->length === 0) {
Yii::info(Yii::t('app', 'Error parse title'));
}
$this->_title = $nodes->item(0)->nodeValue;
Yii::info(Yii::t('app', 'Parse title'));
return $this;
}
Дальше timestamp нашей темы
PHP:
public function parseTimestamp()
{
$xpathQuery = '*//p[@id="pageDescription"]/a/abbr';
$nodes = $this->_xpath->query($xpathQuery, $this->_dom);
if ($nodes->length === 0) {
Yii::info(Yii::t('app', 'Error parse timestamp'));
return $this;
}
// получаем значение timestamp
$this->_timestamp = $nodes->item(0)->getAttribute('data-time');
Yii::info(Yii::t('app', 'Parse timestamp'));
return $this;
}
Последнее получим контент
PHP:
public function parseContent()
{
$xpathQuery = '*//blockquote[@class="messageText ugc baseHtml"]';
$nodes = $this->_xpath->query($xpathQuery, $this->_dom);
if ($nodes->length === 0) {
Yii::info(Yii::t('app', 'Error parse content'));
return $this;
}
$this->_content = $nodes->item(0)->nodeValue;
Yii::info(Yii::t('app', 'Parse content'));
return $this;
}
Вертаемся немного назад и рассмотрим более подробно, что за XPath запросы мы сделали
- '*//h1' ищем в DOM h1. *// означает искать по всему DOM
- *//p[@id=«pageDescription»]/a/abbr ищем элемент p c id pageDescription в которого есть ссылка с элементом abbr
- *//blockquote[@class=«messageText ugc baseHtml»] ищем цитату с class messageText ugc baseHtml
Cозданим метод для завершения парсинга (может и он не совсем нужен но все же более наглядно будет видно что парсинг данных завершен и все ли данные получили), а также методы для доступа к полученным данным
PHP:
/**
* @return \app\components\ParserXenforo
*/
public function endParse()
{
if (isset($this->_content, $this->_timestamp, $this->_content)) {
Yii::info(Yii::t('app', 'End parse'));
} else {
Yii::info(Yii::t('app', 'Some data were not received'));
}
return $this;
}
/**
* @return string title
*/
public function getTitle()
{
return $this->_title;
}
/**
* @return int timestamp
*/
public function getTimestamp()
{
return $this->_timestamp;
}
/**
* @return string content
*/
public function getContent()
{
return $this->_content;
}
Вывод результатов
Компонент можно сказать что готов, можем посмотреть как он работает добавив в action нашего controller необхибые действия а view их вывод
PHP:
$urlThread = 'http://9af5766eb2759a49.demo-xenforo.com/130/index.php?threads/some-thread.1/';
/** @var \app\components\ParserXenforo $dataParse */
$dataParse = Yii::$app->parser
->loadUsingCurl($urlThread)
->createDomDocument()
->createDomXpath()
->parseTitle()
->parseTimeStamp()
->parseContent()
->endParse();
return $this->render('index', ['data' => $dataParse]);
PHP:
<?php
/**
* @var yii\web\View $this
* @var \app\components\ParserXenforo $data
*/
$this->title = 'My Yii Application';
?>
<div class="site-index">
<h1><?= $data->title; ?></h1>
<p>Created At: <?= date('Y-m-d H:i:s', $data->timestamp); ?></p>
<p><?= $data->content; ?></p>
</div>
В результате получаем подобный результат
Вывод
В этой статье мы рассмотрели как сделать парсер контента страницы в виде компонента для Yii на примере парсинга темы форума XenForo.
По-аналогии можно сделать парсинг и других данных, или же создать немного другой класс который будет использовать созданный нами для парсинга например всех тем форума, по-принципу:
- Получаем пагинацию страницы если есть.
- Проходимся по страницах получая ссылки тем и записываем в какое-то промежуточное хранилище
- Получаем контент по этим ссылкам.
Теоретический аспект не был затронут в данной статье, статья была ориентированная чтобы показать на более менее реальном но простом примере как получить данные страницы.