Пишем php парсер сайтов с нуля

Пишем php парсер сайтов с нуля

Опубликовано: 13.02.2015 12:48
Просмотров: 45048

Очень многие из нас хотели бы быстро наполнить сайт контентом. Я покажу вам, как несколько тысяч материалов собрать всего лишь за несколько часов.

Парсер на php - раз плюнуть!

Приветствую вас, наши дорогие читатели. Сегодня решил написать сложную статью про парсеры (сбор информации со сторонних ресурсов).

Скажу сразу, что вам потребуется знание основ программирования на php. В противном случае почитайте теории. Я не буду рассказывать азы, а сразу полезу показывать всё на практике.

Шаг 1 - PHP Simple HTML DOM Parser

Для парсинга сайтов мы будем использовать простецкую библиотечку под названием PHP Simple HTML DOM Parser, которую вы сможете скачать на сайте разработчика. Данный класс поможет вам работать с DOM-моделью страницы (дерево документа). Т.е. главная идея нашей будущей программы будет состоять из следующих пунктов:

  1. Скачиваем нужную страницу сайта
  2. Разбираем её по элементы (div, table, img и прочее)
  3. В соответствии с логикой получим определённые данные.

Давайте же начнём написание нашего php парсера сайтов.

Для начала подключим нашу библиотеку с помощью следующей строки кода:

1
include 'simple_html_dom.php';

Шаг 2 - Скачиваем страничку

На этом этапе мы смогли подключить файл к проекту и теперь пришла пора скачать страничку для парсинга.

В нашей библе есть две функции для получения удалённой страницы сайта. Вот эти функции

  1. str_get_htm() - получает в качестве параметров обычную строку. Это полезно, если вы стянули страничку с помощью CURL или метода file_get_contents. Пример использования: 
    1
    $seo = str_get_html('<html>Привет, наш любимый читатель блога SEO-Love.ru!</html>')
     
  2. file_get_html() - здесь же мы передаём в качестве параметра какой-то url, с которого нам потребуется скачать контент. 
  3. 1
    $seo = file_get_html('http://www.site.ru/');

После скачивания каждой страницы вам требуется подчищать память, дабы парсеру было легче работать и не так сильно грузился ваш сервер. Эта функция вызовется с помощью данного кода:

1
2
$seo = file_get_html('http://www.site.ru/');
$seo->clear();

Шаг 3 - Ищем нужные элементы на странице

После получения DOM-модели мы можем приступить непосредственно к поиску нужного элемента-блока в полученном коде.

Большая часть функций поиска использует метод find(selector, [index]). Если не указывать индекс, то функция возвратит массив всех полученных элементов. В противном случае метод вернёт элемент с номером [index].

 Давайте же приведу вам первый пример. Спарсим мою страничку и найдём все картинки.

1
2
3
4
5
6
7
8
9
10
11
12
//подключили библиотеку
require_once 'simple_html_dom.php';
//скачали страничку
$page = file_get_html('http://xdan.ru');
//проверка нашли ли хотя бы 1 блок img и не пустая ли страница
if($page->innertext!='' and count($data->find('img'))){
  //для всех элементов найдём элементы img
  foreach($data->find('img') as $img){
    //выведем данный элемент
    echo $a->innertext;
  }
}
 
Если что-то пошло не так, то прошу отписаться в комментариях. Здесь очень кстати будет мой предыдущий материал Запутываем PHP-код без зазрения совести. Полезно для тех, кто программирует как ниндзя. Больше не отвлекаюсь, идём дальше.

Шаг 4 - Параметры поиска

Надеюсь все уже поняли, что в метод find() можно писать как теги ('a'), так и id'шники ('#id'), классы ('.myclass'), комбинации из предыдущих элементов ('div #id1 span .class'). Таким образом вы сможете найти любой элемент на странице.

Если метод поиска ничего не найдёт, то он возвратит пустой массив, который приведёт к конфликту. Для этого надо указывать проверку с помощью фукнции count(), которую я использовал выше в примере.

Также вы можете производить поиск по наличию атрибутов у искомого элемента. Пример:

1
2
3
4
5
6
7
8
9
10
11
//Найдём все изображения с шириной 300
$seo->find('img[width=300px]');
//Найдём изображения, у которых задана ширина
$seo->find('img[width]');
//Поиск по наличию нескольких классов
$seo->find('img[class=class1 class2]');//<img class="aclass1 class2"/>
//Ищем несколько тегов вместе
$seo->find('div, span, img, a');
//Поиск по вложенности.
//В div ищем все спаны, а в спанах ссылки
$html->find('div span a');

Замечу, что у каждого вложенного тега так же есть возможность поиска!

Есть много вариантов поиска по атрибутам. Перечислять не стану, для более полного руководства прошу пройти на сайт разработчиков :)

Обычный текст, без тегов и прочего, можно искать так find('text'). Комментарии аналогично find('comment').

Шаг 5 - Поля элементов

Каждый найденный элемент имеет несколько структур:

  1. $seo->tag   Прочитает или запишет имя тега искомого элемента.
  2. $seo->outertext   Прочитает или запишет всю HTML-структуру элемента с ним включительно.
  3. $seo->innertext   Прочитает или запишет внутреннюю HTML-структуру элемента.
  4. $seo->plaintext   Прочитает или запишет обычный текст в элементе. Запись в данное поле ничего не поменяет, хоть возможность изменения как бы присутствует.

Примеры:

1
2
3
4
5
6
7
$seo = str_get_html("<div>first word <b>second word</b></div>");
echo $seo; // получим <div>first word <b>second word</b></div>, т.е. всю структуру
$div = $seo->find("div", 0);
echo $div->tag; // Вернет: "div"
echo $div->outertext; // Получим <div>first word <b>second word</b></div>
echo $div->innertext; // Получим first word <b>second word</b>
echo $div->plaintext; // Получим first word second word 

Эта возможность очень просто позволяет бегать по DOM-дереву и перебирать его в зависимости от ваших нужд.

Если вы захотите затереть какой-либо элемент из дерева, то просто обнулить значение outertext, т.е. $div->outertext = ""; Можно поэксперементировать с удалением элементов.

P.S. Я обнаружил проблему с кодировками при очистке и всяческими манипуляциями с полем innertext. Пришлось использовать outertext и затем с помощью функции strip_tags удалял ненужные теги.

Шаг 6 - Дочерние элементы

Разработчики данной библиотеки позаботились так же и о том, чтобы вам было легко перемещаться по дочерним и родительским элементам дерева. Для этого ими были любезно созданы следующие методы:

  1. $seo->children ( [int $index] )   Возвращает N-ый дочерний элемент, иначе возвращает массив, состоящий из всех дочерних элементов.
  2. $seo->parent()   Возвращает родительский элемент искомого элемента.
  3. $seo->first_child()   Возвращает первый дочерний элемент искомого элемента, или NULL, если результат пустой
  4. $seo->last_child()   Возвращает последний дочерний элемент искомого элемента, или null, если результат пустой
  5. $seo->next_sibling()   Возвращает следующий родственный элемент искомого элемента, или null, если результат пустой
  6. $seo->prev_sibling()   Возвращает предыдущий родственный элемент искомого элемента, или null, если результат пустой

 Я особо не пользовался этими возможностями, потому что они ещё ни разу не пригодились мне. Хотя один раз при разборе таблицы использовал, потому что они структурированы, что делает разбор очень простым и лёгким.

Шаг 7 - Практика

Перейдём к практике. Я решил отдать вам на растерзание одну функцию, что использовал при написании парсера текстов песен на один из своих сайтов. Пытался досконально подробно описать код. Смотрите комментарии и задавайте вопросы.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
public function parser_rock_txt() {
        $i = 0;
        $new_songs = 0;
        //номер категории, чтобы хранить в базе. У меня Рок = 1
        $category = 1;
  		//Скачиваем страничку с сайта Rock-Txt.ru
        $data = file_get_html('http://rock-txt.ru/');
        //нашли хотя бы одну ссылку на песни по буквам (проходим навигацию)
        if (count($data->find('div.a-z a'))) {
            //пробежим по всей навигации
            foreach ($data->find('div.a-z a') as $a) {
                //Выводим букву, которую парсим
                echo ('Текущая буква - ' . $a->plaintext . '<br />');
                //нашли список всех исполнителей
                $data_vocalist = file_get_html("http://rock-txt.ru" . $a->href);
                //если есть хотя бы один исполнитель
                if (count($data_vocalist->find('#dle-content div.full-news a'))) {
                    foreach ($data_vocalist->find('#dle-content div.full-news a') as $vocalist) {
                        //приводим название исполнителя к нижнему регистру
                        $vocalist->plaintext = mb_strtolower((mb_convert_encoding(($vocalist->plaintext), 'utf-8', mb_detect_encoding(($vocalist->plaintext)))), 'UTF-8');
                        //получаем id исполнителя из моей базы
                        $id_vocalist = $this->songs_model->check_vocalist(trim($this->db->escape($vocalist->plaintext)), trim($this->db->escape($this->translit($vocalist->plaintext))), $category);
                        //Нашли все песни исполнителя
                        $data_songs = file_get_html($vocalist->href);
                        //если есть хотя бы одна песня такого исполнителя - идём дальше
                        if (count($data_songs->find('#dle-content div.left-news-band a'))) {
                            foreach ($data_songs->find('#dle-content div.left-news-band a') as $songs) {
                                //Получим название песни. Удалим название исполнителя.
                                $name_song = substr(preg_replace('/\s\s+/', ' ', $songs->plaintext), strlen(trim($vocalist->plaintext)) + 1);
                                $name_song = trim($name_song);
                                //приводим название песни в нижний регистр
                                $name_song = mb_strtolower((mb_convert_encoding(($name_song), 'utf-8', mb_detect_encoding(($name_song)))), 'UTF-8');
                                //Транслитизируем название песни (моя самописная функция)
                                $name_song_translit = $this->translit($name_song);
                                //Отсекаем все пустые названия
                                if ($name_song == '' || $name_song_translit == '')
                                    continue;   
                                //Проходим по всем страницам навигации (пейджер, постраничная навигация)
                                $num_page = 0;
                                foreach ($songs->find('div.navigation a') as $num) {
                                    //если число - сравниваем, а не нашли ли мы ещё одну страницу навигации
                                    if (is_int($num->plaintext)) {
                                        if ($num->plaintext > $num_page)
                                            $num_page = $num->plaintext;
                                    }
                                }
                                echo $num_page . '<br />';
                                //загрузим текст песни
                                $text_songs = file_get_html($songs->href);
                                if (count($text_songs->find('div.full-news-full div[id] p'))) {
                                    foreach ($text_songs->find('div.full-news-full div[id] p') as $text_song) {
                                        //очищаем всякие ненужны ссылки и спаны
                                        foreach ($text_song->find('span') as $span) {
                                            $span->outertext = '';
                                        }
                                        foreach ($text_song->find('a') as $a) {
                                            $a->href = '';
                                            $a->outertext = '';
                                        }
                                        //выводим исполнителя, песню и текст
                                        echo $name_song . '<br />';
                                        echo $songs->href . '<br />';
                                        echo $text_song->outertext . '<br />';
                                        $text_song->outertext = preg_replace("/(<br[^>]*>\s*)+/i", "<br />", $text_song->outertext, 1);
                                        //вставляю в мою базу текст песни и исполнителя (самописная функция)
                                        $result = $this->songs_model->check_song(trim($this->db->escape($name_song_translit)), trim($this->db->escape($name_song)), trim($this->db->escape($id_vocalist)), trim($this->db->escape_str(preg_replace("#(:?<br />){2,}#i", "<br />", strip_tags($text_song->outertext, '<br /><br><b><strong><p>')))));
                                        //если добавили - увеличим счётчик новых песен
                                        if ($result != -1) {
                                            $new_songs++;
                                        }
                                        $i++;
                                        //выйдем, тут всякие косяки бывают
                                        break;
                                    }
                                }
                                //теперь аналогично пробегаем по остальным страницам
                                if ($num_page > 0) {
                                    $text_songs = file_get_html($songs->href . 'page/' . $num_page);
                                    if (count($text_songs->find('div.full-news-full div[id] p'))) {
                                        foreach ($text_songs->find('div.full-news-full div[id] p') as $text_song) {
                                            foreach ($text_song->find('span') as $span) {
                                                $span->outertext = '';
                                            }
                                            foreach ($text_song->find('a') as $a) {
                                                $a->href = '';
                                                $a->outertext = '';
                                            }
                                            echo $name_song . '<br />';
                                            echo $songs->href . '<br />';
                                            echo $text_song->outertext . '<br />';
                                            $text_song->outertext = preg_replace("/(<br[^>]*>\s*)+/i", "<br />", $text_song->outertext, 1);
                                            $result = $this->songs_model->check_song(trim($this->db->escape($name_song_translit)), trim($this->db->escape($name_song)), trim($this->db->escape($id_vocalist)), trim($this->db->escape_str(preg_replace("#(:?<br />){2,}#i", "<br />", strip_tags($text_song->outertext, '<br /><br><b><strong><p>')))));
                                            if ($result != -1) {
                                                $new_songs++;
                                            }
                                            $i++;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return "<br />Парсер сайта rock-txt.ru завершён. Спарсено песен всего " . $i . ", из них новых " . $new_songs . " ";
    }

 Получилась вот такая здоровая функция, которая парсит тексты песен с сайта о роке. Написал её я за час. Спарсил 10 000 текстов песен. Думаю, что руками вы бы набивали такую базу очень и очень долго :-)

Замечу, что в коде много самописных функций, которые используются для вставки в мой базу. Эти функции у каждого могут быть индивидуальными, так что в этом я вам не помощник. Либо обращайтесь за помощью в комментарии. Всегда буду рад помочь!

Пока что на этом всё. В следующих уроках расскажу, как можно быстро и просто спарсить кучу информации на несколько десятков сайтов. Этот кейс должен обогатить каждого!

Всего доброго! Ретвиты, лайки и репосты приветствуются!

Оставьте свой комментарий

Оставить комментарий от имени гостя

0 / 2000 Ограничение символов
Ваш текст должен быть в пределах 10-2000 символов
Ваш комментарий будет отправлен на модерацию