Пишем парсер записей блога

3.8K
.
Ей 25
Вот накатал сегодня за примерно 3 часа статью как написать парсер новостей\статей\записей Блога и подобного.
Сам код парсера написал за минут 15-20, а вот статья отняла много времени.
Реализацию придумал сегодня ночью: лёг спать в пол первого ночи и дождь пошёл, с громом и молнией. Короче уснуть было невозможно, хоть и говорят что в дождь лучше спится(но когда гремит так что стёкла трясутся на окнах и молния мигает каждые 30 сек освещая пол комнаты, реально не до сна). Вот я лежал и думал, ну и надумал.
Сейчас буду по кускам её сюда кидать. Просьба пока не захламлять тему.
Да, статью с самого начала задумал написать для Блога helltar.ru, так что и писал её как для читателей того блога. Уже после написания решил её и сюда добавить. Так что не нужно кричать что я рекламирую helltar.ru, это даже не мой сайт. Это просто бложек, одно знакомо человека с Украины. Когда то вместе кодить начинали... Но если администрация посчитает это за рекламу, готов переписать статью под тот сайт который не будет считаться рекламой.
.
ValekS
Ей 25
Доброго времени суток! Сейчас мы будем учиться писать парсер на PHP.

Вступление
Не буду сыпать терминами, а просто скажу - парсер это весч которая берёт к примеру новости с какого то сайта и копирует вам на сайт. Может быть и парсер контента, тот который копирует файлы.
Парсер, в отличие от грабера, копирует новости\статьи\файлы и т.д. к вам насовсем(пока вы их не удалите) и ваш сайт не будет зависеть от сайта-донора. Главное что бы сайт-донор работал в тот момент когда вы будете его парсить.
Парсер запускается один раз, потом его можно удалить или переделать, что бы спарсить другие новости\статьи\файлы и т.д.
Если хотите узнать о парсере подробнее, то воспользуйтесь гуглом, он вам поможет.

Итак, что мы будем парсить? Я решил далеко не ходить, поэтому парсить мы будем записи блога helltar.ru, вот так.

Небольшая оговорка: я буду писать парсер под мобильный движок JohnCMS - http://johncms.com, думаю он вам известен. Так же думаю все знают что helltar.ru начался именно с ДжонЦМС. Ну и приведу ещё пару аргументов:
- я НЕ буду использовать переменные или функции JohnCMS, поэтому вы без труда сможете допилить мой код под свой сайт или CMS;
- у ДжонЦМС хоть дизайн есть, если бы я писал так сказать на чистом PHP, т.е. без CMS, то был бы чёрный текст и белый фон, а не дизайн;
- у ДжонЦМС есть хорошая стандартная Библиотека, которая хорошо подходит под мои запросы в этом уроке. Если бы я писал без ЦМС, то пришлось бы ещё и библиотеку писать. Иначе куда бы мы записи парсили?

Ещё оговорка: если вы знаете азы PHP то думаю код парсера будет вам понятен. А если вы ещё и ранее писали Граберы, то вы точно должны понять код. Ну а если вы код не понимаете, то Sorry - я ничем не могу вам помочь.

И ещё оговорка: я буду парсить стандартными средствами PHP. Т.Е. я НЕ буду использовать различные библиотеки для парсинга, например - «PHPQuery», «Simple HTML DOM», «Zend DOM Query», «Nokogiri». Хотя соглашусь с теми, кто скажет что парсить с библиотеками удобнее, быстрее и лучше. Но дабы не замарачивать голову новичкам различными библиотеками и т.д., я не буду их использовать.
.
Ей 25
Подготовка к написанию парсера
Как и в любом скрипте, выполняющим действия с каким то сторонним сайтом, нам сначала нужно разобрать структуру этого стороннего сайта.
Заходим на helltar.ru и смотрим. Версия сайта роли не играет. Мы видим записи, которые нам нужно взять и кинуть себе в Базу Данных. Смотрим ссылки к записям, к примеру последняя запись блога - http://helltar.ru/post-176.html, потыкав ещё по ссылкам понимаем что в них меняется только ай-ди. Так как предпоследняя запись - post-175.html, пред предпоследняя - post-174.html и так далее. Это нам только наруку. Знаете почему? Нет? Позже поймёте.
Так же на данном этапе следует учесть, что некоторые записи удалялись - ID последней записи 176, всего записей 106. Значит удалено 70(176 - 106 = 70) записей. В этом легко убедиться - заходим на последнюю страницу блога - 22 и видим что ID первой записи “Сайт открыт” - 4. То что некоторые записи удалены не беда, раз мы про это знаем.

Теперь посмотрим на саму запись, на html код записи. Из всего хлама что размещён на странице записи - http://helltar.ru/post-176.html - нам нужно только взять Название записи и её Текст. Название записи у нас содержится в диве - title, в нашем случае это - <div class="title"><b>Catch Box for Windows</b></div>. Но я не буду парсить Название от сюда, так как если внимательно посмотреть, то можно обнаружить что Название статьи содержится ещё в title - <title>Catch Box for Windows</title>, откуда его взять удобнее.
Текст статьи я буду брать начиная от дива <div class="post"> и заканчивая дивом <div style="float:left">. Так как раз весь текст записи получится у нас и не надо будет вырезать с него всякие ссылки на Комментарии и т.д.

Ну что, подготовительный этап можно считать законченным. Переходим дальше.
.
Ей 25
Пишем код парсера одной странички
Тут сразу я напишу 2 оговорки:
1) Я буду парсить только сам текст записи, картинки в мои планы не входят. Так что их я буду вырезать.
2) Как сказано в первой оговорке, я буду парсить сам текст записи. Поэтому я вырежу все ссылки и HTML теги из текста.
Почему я буду так делать? Это ознакомительный код парсера, в котором я не хочу мучиться с обработкой картинок(тем более что в JohnCMS картинку в статью, в библиотеке, не вставишь), ссылок, жирного текста и т.д.

Итак, приступим! Я начну с того что создам в корне чистого JohnCMS папку parser. В ней я создам файл index.php, в кодировке UTF-8(без BOM), в котором напишу заготовку обычной странички для движка JohnCMS:

<?php

define('_IN_JOHNCMS', 1);
require_once('../incfiles/core.php');
require_once('../incfiles/head.php');



require_once('../incfiles/end.php');
?>


Далее получим последнюю запись с блога helltar.ru:
$file = file_get_contents('http://helltar.ru/post-176.html');


Как видите данные я получаю стандартной функцией PHP и заношу их в переменную $file. Конечно можно и даже нужно использовать cURL(рекомендуется его использовать, но использовать file_get_contents никто не запрещает). Но я выбрал file_get_contents что бы было меньше строчек кода не знакомого новичкам.

Затем я напишу две функции, которые будут вытаскивать Название записи и Текст:
function title($var) {      
    preg_match('/<title>(.*?)<\/title>/is', $var, $title);
    $titles = str_replace('&quot;', '"', $title['1']);
    return $titles;
    }

function text($var) {      
    preg_match('/<div class=\"post\">(.*?)<div style=\"float:left\">/is', $var, $text);
    $texts = preg_replace('/<img(.*?)\/>/si','',$text['1']);
    $texts = preg_replace('/<a(.*?)<\/a>/si','',$texts);
    $texts = str_replace('&quot;', '"', $texts);
    $texts = strip_tags($texts);
    return $texts;
    }


Как видите я пока хочу написать код который будет брать одну запись,ссылку на которую мы укажем, и заносить её к нам в БД.
Код наших функций весьма прост. Разберём сначала первую функцию title():
функция принимает параметр $var, это код нашей странички с записью, затем идёт обработка этого кода. Функция preg_match(‘’, ‘’, ‘’); выполняет у нас поиск текста по регулярному выражению, из переменной $var(весь текст здесь хранится), и заносит найденный текст массивом, в нашем случае Название записи, в переменную $title.
Затем идёт функция str_replace('&quot;', '"', $title['1']); , она в уже Названии записи заменяет html код кавычек на сами кавычки - “, если конечно этот HTML код кавычек есть в Названии. Обратите внимание как выводится Название записи - $title['1'] а не просто $title, Название у нас же в массиве.
Ну и наконец идёт строка, которая указывает что наша функция отдаёт - return $titles; , тут думаю всё просто и комментариев не надо.

Вторая функция - text() - делает практически тоже самое что и первая функция. Принимает $var, затем вырезает текст записи, это у нас делает preg_match(), потом идёт preg_replace(), эта функция вырезает изображения и ссылки в Тексте записи, дальше идёт уже знакомая нам функция str_replace(), делает она тоже самое что и в функции title(), и напоследок весь текст обрабатывается функцией strip_tags, которая вырезает все HTML теги с текста.

Что будем делать дальше? Разумеется записывать всё это добро в нашу Базу Данных:
$title = title($file);
$text = text($file);

mysql_query("INSERT INTO `lib` SET
               `refid` = '1',
               `time` = '" . time() . "',
               `type` = 'bk',
               `name` = '" . mysql_real_escape_string(mb_substr(trim($title), 0, 100)) . "',
               `announce` = '" . mysql_real_escape_string(mb_substr(trim($text), 0, 100)) . "',
               `text` = '" . mysql_real_escape_string($text) . "',
               `avtor` = 'ValekS',
               `ip` = '0',
               `soft` = '1',
               `moder` = '1'
           ");
echo 'Готово!';


Это MySQL запрос для добавления статьи в библиотеку, в движке ДжонЦМС. Перед ним идут 2 переменные которым мы присваиваем Название и Текст записи - вызываем функции обработки Названия и Текста, код которых мы написали перед этим, и присваиваем переменным. В качестве параметра передаём содержимое переменной $file.
Сам запрос как видите состоит из 10 полей:
- refid - это ай-ди категории в которую мы будем добавлять записи. Я в библиотеке создал категорию “Записи Helltar.ru” и она получила у меня ай-ди 1.
- time - время добавления записи. Записываем время стандартной функцией time().
- type - это поле нужно для того что бы библиотека знала что мы добавили статью или категорию.
- name - Название статьи, перед занесением в БД мы обрабатываем его, что бы не было уязвимостей и обрезаем если оно более 100 символов.
- announce - анонс статьи, мы вырезаем первые 100 символов Текста статьи и заполняем это поле.
- text - Текст статьи.
- avtor - автор статьи. Тот кто её добавил. Пишем свой ник.
- ip - ай-пи добавившего статью. Что бы не замарачиваться пишем просто 0.
- soft - браузер и т.д. добавившего статью. Что бы не замарачиваться пишем просто - 1.
- moder - модерация статьи. Если 1 - то промодерирована, если 0 - то не промодерирована. Пишем 1.

После запроса выводим текст “Готово”.

Так. Ну можно проверять. Последнюю запись блога нам в библиотеку должно добавить.
.
Ей 25
Пишем код парсера всех записей
Теперь нам нужно усовершенствовать наш код, что бы он парсил все записи а не только одну. Значит нам нужно сделать так, что бы скрипт пробежался по всем страничкам с записями и занёс их нам в БД.
Приведу сразу код, потом объясню:
function title($var) {      
   preg_match('/<title>(.*?)<\/title>/is', $var, $title);
   $titles = str_replace('&quot;', '"', $title['1']);
   return $titles;
    }

function text($var) {      
   preg_match('/<div class=\"post\">(.*?)<div style=\"float:left\">/is', $var, $text);
   $texts = preg_replace('/<img(.*?)\/>/si','',$text['1']);
   $texts = preg_replace('/<a(.*?)<\/a>/si','',$texts);
   $texts = str_replace('&quot;', '"', $texts);
   $texts = strip_tags($texts);
   return $texts;
    }

for ($i = 1; $i < 177; $i++) {
$file = file_get_contents('http://helltar.ru/post-'.$i.'.html');
$title = title($file);
$text = text($file);

if ($title && $text) {
mysql_query("INSERT INTO `lib` SET
               `refid` = '450',
               `time` = '" . time() . "',
               `type` = 'bk',
               `name` = '" . mysql_real_escape_string(mb_substr(trim($title), 0, 100)) . "',
               `announce` = '" . mysql_real_escape_string(mb_substr(trim($text), 0, 100)) . "',
               `text` = '" . mysql_real_escape_string($text) . "',
               `avtor` = 'ValekS',
               `ip` = '0',
               `soft` = '1',
               `moder` = '1'
           ");

echo 'Пост '.$i.' - Готово!<br/>';
} else {
echo 'Пост '.$i.' -Ошибка!<br/>';
}
  }

Это кстати весь код файла. Как видим функции я переместил в самый верх. Что бы они не путались с остальным кодом и не мешали нам. Затем добавил Цикл for(). Он нужен для того что бы скрипт выполнился заданное кол-во раз. Без цикла наш скрипт не сможет пробежаться по всем записям. Цикл прост: for ($i = 1; $i < 177; $i++) { } - сначала мы создаём цикл, затем присваиваем переменной $i значение 1, потмо ставим условие повторения цикла - если $1 меньше 177 то повторяем цикл, ну и увеличиваем $1 на единицу каждый раз при повторении цикла. Цифра 1 это ай-ди первой записи, хоть её и нет, а 177 это ай-ди последней записи увеличенное на единицу, можно было и не увеличивать - $i >= 176.
Тут ещё есть одно важное дополнение нашего начального кода - условие IF. Условие if ($title && $text) проверяет установлены ли переменные Титл и Текст, и есть они есть то только тогда выполняется запись в Базу Данных и вывод текста “Готово”. Если переменных нет выводим ошибку. Таким способом мы откинем все несуществующие(удалённые) записи, ведь если записи нет то где переменные возьмут Название записи и Текст?
Ну и стоит ещё заметить что для получения данных странички с записью мы подставляем в ссылку переменную $1 вместо ай-ди:
было - $file = file_get_contents('http://helltar.ru/post-176.html');
стало - $file = file_get_contents('http://helltar.ru/post-'.$i.'.html');
.
Ей 25
Вот и всё. Если мы запустим наш скрипт, перейдя по адресу site.ru/parser/, он через некоторое время выдаст нам результаты своей работы: столбик текста вида “Пост 1 - Ошибка! … Пост 4 - Готово!”.
Прикрепляю скриншоты и архив с парсером.

Напоследок хочется сказать ещё пару слов. Как видите написать парсер не так уж и сложно, но зато он экономит уйму времени. Ведь если бы мы добавляли записи с Блога в библиотеку в ручную, то это отняло бы у нас больше времени. Плюс рутинная работа, которая быстро надоедает. А парсер за примерно 25-30 сек легко занёс нам все записи Блога в библиотеку.
Да, помните я вначале говорил что если в ссылке записи изменяется только ай-ди то это нам наруку? Это нам не просто наруку, это очень хорошо! Ведь достаточно простого цикла с перебором ай-ди всех записей что бы занести их(записи) в Базу Данных. Но что делать если ссылка на запись примерно такая - site.ru/post/zapis_nomer_odin и т.д.? И как парсить не все записи, а к примеру записи из определённой категории(например Программирование)? Так как эта статья и так очень большая, ответы на эти вопросы я дам в следующей статье.

С вами был ValekS, до встречи!
.
Ей 25
Теперь скрины. Первый:
Прикрепленные файлы:
.
Ей 25
Второй скрин:
Прикрепленные файлы:
.
Ей 25
Третий скрин:
Прикрепленные файлы:
.
Ей 25
Готовый парсер:
Прикрепленные файлы:
Всего: 116