Парсинг HTML с помощью PHP и SQL. Немного провокационный пример с анализом пользователей Хабра
Выковыривание информации из html — это скучно. Очень. Между тем, эта потребность выстреливает редко, но метко (© Суворов). Из-за этого есть спрос на готовые и короткие инструкции о том, как это сделать, чтобы не тратить время на изучение. Перед вами как раз такая.
Чтобы добавить хоть какой-то интерес скучнейшему занятию мы для примера будем парсить пользователей Хабра. А чтобы не мелочиться — ещё и реанимируем для этого экспериментальную библиотеку 11-летней давности.
Есть такой проект — htmlSQL. Старая библиотека времён Очаковских и покоренья Крыма (© Грибоедов). Она позволяет делать выборку из HTML в стиле SQL-запросов как на КДПВ, чем она мне когда-то и полюбилась.
Замечу, что результатами анализа пользователей кто-то будет возмущён, но мои намерения только самые хорошие и я вам это сейчас докажу.
Для нетерпеливых сразу основные изменения в библиотеке:
- Я переписал её с php4, чтобы работала на php5/7/8.
- Мелкие исправления, позволяющие парсить страницы не только с http, но и https.
- Исправлены некоторые старые ошибки, добавлены новые 🙂
- Немного выросла скорость работы (вызовы устаревшей и медленной функции each, например, заменены на более шустрый обход foreach).
- Чтобы хоть как-то повысить читабельность начат рефакторинг, но через два вечера внезапно остановлен.
- Логические ошибки кое-где ещё остались. Из-за них библиотека пропускает некоторые комбинации «тег + класс».
- Исходники прилагаются.
Статьи (и новости) я взял не все, а только на самые холиварные темы. Их оказалось меньшинство, но всё равно немало. Итог по пользователям хабра из таких статей:
- Ценные комментарии — прежняя фишка хабра — ушли в прошлое. Котиков и маникюр в комментариях ещё не обсуждают, но исходный код и образцы конфигов из комментариев почти исчезли (по крайней мере, предназначенный для этого тег code встречается околонулевое количество раз).
- Количество орфографических ошибок немалое, но катастрофы нет. Всё-таки Хабр пока не стал ресурсом для недоучившихся школьников.
- Не существует единого портрета среднестатистического комментатора. Образовалось целое семейство (назовём его Habracommentator Sapiens, или просто H. s). Семейство включает в себя различные виды, например: H. s. Talker, H. s. Newscaster и — конечно же! — H. s. Politicus.
- Семейство HabraWriter Sapiens, вопреки паническим сообщениям, не вымерло, хотя их популяция сократилась. В комментариях почти не участвуют.
- Основные изменения кармы происходят в комментариях, а не в результате публикаций*.
*по техническим причинам гипотеза требует перепроверки, но в этот раз мы это пропустим.
Увы, труд авторов на хабре ценят мало и мы как будто стремительно превращаемся в пикабу. Но я знаю, что это поправимо.
Библиотека htmlSQL
Она появилась как эксперимент и, похоже, очень хорошо зашла некоторым пользователям. На гитхабе даже появились форки. Впрочем, и они давно померли, также как и оригинал (автор завершил эксперимент и забросил проект). Но я сделал форк и чуть-чуть изменил.
Пользоваться этим просто — чтобы спарсить какой-нибудь сайт вам нужно:
- вызвать функцию connect(), куда передать URL сайта
- вызвать функцию query(), куда передать SQL-подобный запрос того, что вы хотите выбрать.
Пример скажет сам за себя:
$wsql = new htmlsql(); if ($wsql->connect('url', "https://habr.com/ru/news/page4/") === false) < echo 'Не удалось подключиться к сайту: ' . $wsql->error . PHP_EOL; exit; > if ($wsql->query('SELECT * FROM article WHERE $class=="tm-articles-list__item"') === false) < echo "Ошибка запроса: " . $wsql->error . PHP_EOL; exit; > foreach ($wsql->fetch_array() as $row)
Приведённый код напечатает список всех новостей с четвёртой страницы новостного раздела хабра. Всё просто!
Обращу внимание, что функция connect() позволяет принимать на вход не только url, но также какой-нибудь локальный файл, либо — это прекрасно! — просто текстовую переменную, содержащую какой-нибудь html-код.
В каталоге с библиотекой есть 12 крошечных файлов с примерами не больше 35-45 строк.
- можно назначить в настройках свой User-Agent
- можно не парсить всю html-структуру целиком, а только её кусок, указав границы «от» и «до»
- можно ограничить работу в рамках одного тега (header или body, например)
- никто не любит регулярные выражения, но библиотека их тоже поддерживает и даст вам возможность не любить их ещё раз.
Пару слов о коде внутри htmlSQL
Когда мне в декабре потребовалось спарсить некий сайт, я не сумел воспользоваться этой библиотекой, так как она категорически отказалась запускаться с php8. Хорошо, что кода там немного — меньше 700 строк — и я за вечер всё исправил. Код я тогда менял в целом не сильно.
Замечу, что автор, скорее всего, немец. По стилю кода он, вероятно, ещё и специалист в области немецкого жёсткого порно. Я не знаю, какие были нравы в эпоху php4 и, возможно, тогда этот код считался нормальным, но. Как по мне — это самое искромётное порево, которое я видел за последнее время. Поэтому в новогодние выходные я решил заняться рефакторингом. Обстоятельства сложились так, что уже через день-другой эту работу пришлось оставить — одна подруга срочно позвала меня есть блины и я был не в силах отказаться от излюбленного русского лакомства.
По итогу, я успел лишь поменять сигнатуру нескольких функций. Автор первоначальной версии явно любит самые пышные формы функций. Эти толстушки принимали в себя много аргументов, которые в основном передавались по ссылке. Когда их вызываешь, они в ответ, обычно, могут сделать всякое ничего не дают. Я, напротив, предпочитаю иметь дело с небольшими и стройными, поэтому заставил функции похудеть, принимать меньше параметров и что-то давать в ответ на вызов.
Также я организовал явное разделение полей и функций класса на приватные и публичные (в php4, по-моему, ещё не было ключевых слов public и private, поэтому все поля объявлялись через var). Для порядка весь исходный код в htmlsql.php приобрёл некоторую структуру: хаотично разбросанные функции упорядочены в более-менее логической последовательности по заветам Роберта Мартина. Теперь функция, вызываемая внутри другой функции, находится следом за ней и ниже, а функции, которые вызываются уже в ней — ещё ниже.
Из смешных мелочей: я не случайно отметил увлечение автора темой XXX, ведь даже в циклах со счётчиком он, обычно, использует переменную с именем $x. У менее искушённых разработчиков сложился иной обычай: счётчики называют $i или $j для вложенных циклов. Я решил не отступать от этого обычая.
Для работы библиотека использует локально установленный curl. Да, это не оговорка — консольная утилита вызывается в php через exec()! Возможно, в то время ещё не существовало расширения php-curl и иного способа попросту не было. По-хорошему так лучше не делать, но пока я не успел убрать этот атавизм. К чему я отметил эту особенность? У разных дистрибутивов путь к утилите curl может отличаться. У меня это, например, /usr/bin/curl, у кого-то может быть /usr/local/bin/curl или что-то похожее. Как вы уже поняли, даже путь к утилите захардкожен 🙂
Именно в таком виде код и пребывает в настоящее время. Возможно, что я или кто-то ещё продолжит доработку, так как назвать эту библиотеку законченной нельзя. Она появилась с формулировкой «экспериментальная», такой же и осталась, хотя использовать в работе безусловно можно.
Практическая часть. Сможет ли htmlSQL взять Хабр?
Соблюдайте меры предосторожности: когда парсите, вас могут вычислить по IP забанить, поэтому работа на некоторое время встанет. Хабр блокировал меня пока что три раза.
Для выполнения работы потребуются:
- Крайне поверхностные знания SQL (уровня select + where, а всевозможные join, group by и т. п. не нужны)
- Более-менее ориентироваться в html-разметке (теги и классы)
- Уметь читать PHP или любой язык с си-подобным синтаксисом
- 5 минут времени для прочтения по диагонали или 30 минут, если нужно научиться использовать примеры из статьи.
Для начала, самым прямолинейным способом переберём последние 20 страниц статей с Хабра и сохраним их:
for ($i = 1; $i
Функция save_posts() может быть примерно такой:
Тут скучный код
function save_posts($link) < echo "Обход статей на странице $link" . PHP_EOL; $wsql = new htmlsql(); if ($wsql->connect('url', $link) === false) < echo 'Не удалось подключиться к сайту: ' . $wsql->error . PHP_EOL; return false; > if ($wsql->query('SELECT * FROM article WHERE $class=="tm-articles-list__item"') === false) < echo "Ошибка запроса: " . $wsql->error . PHP_EOL; return false; > $values = array(); foreach ($wsql->fetch_array() as $row) // есть ещё ->fetch_objects() < $text = trim($row['text']); $posts = parse_article($text); foreach ($posts as $article) < $title = $article['title']; $url = $article['url']; $url_to_comments = $article['comments']; $post_id = get_post_id($url); echo "Найдена статья $title" . PHP_EOL; $values[] = array($post_id, $title, $url, $url_to_comments); >> // тут $values можно сохранить в базу данных, например. return true; >
Функция ищет все теги article с css-классом «tm-articles-list__item». Именно он назначен всем статьям в ленте хабра.
Встроенная функция fetch_array() вернёт массив всех найденных статей. Это всё ещё сырой html, который дальше снова придётся парсить. Этим занимается функция parse_article(). Она может быть примерно такой:
Здесь ещё скучный код
function parse_article($text) < $result = parse_fragment($text, 'h2', "tm-article-snippet__title tm-article-snippet__title_h2"); if ($result === false) < echo "Не удалось разобрать вводную часть статьи" . PHP_EOL; return false; >$posts = array(); foreach ($result as $row) < $introduce = trim($row['text']); $link = parse_fragment($introduce, 'a', "tm-article-snippet__title-link"); $link = $link[0]; $post_title = parse_fragment($link['text'], 'span'); $post_title = $post_title[0]; $post_title = $post_title['text']; $posts[] = array('url' =>$link['href'], 'title' => $post_title, 'comments' => $link['href'] . 'comments/'); > return $posts; >
Здесь мы выбираем все теги h2 с классами «tm-article-snippet__title tm-article-snippet__title_h2», так как в них хранятся ссылка на статью и название. Обратите внимание, что мы больше не подключаемся к статье по url и разбираем с уже выцарапанный ранее html с помощью функции parse_fragment(). Она имеет примерно такой вид:
Здесь видна возможность парсинга строковой переменной
function parse_fragment($text, $tag_name, $class_name = '') < $wsql = new htmlsql(); if ($wsql->connect('string', $text) === false) // error; return false; > $sql = "SELECT * FROM $tag_name"; if (empty($class_name) === false) < $filter = "WHERE \$class==\"$class_name\""; $sql = "$sql $filter"; >if ($wsql->query($sql) === false) < echo "Ошибка разбора тегов $tag с классом $class_name: " . $wsql->error; return false; > return $wsql->fetch_array(); >
Собственно, схематично я показал все функции, которые спарсили хабр. По итогу мы получили:
- «номер» статьи
- название
- ссылку на статью
- ссылку на комментарии к статье
Сохранив всё это в БД спарсим некоторые метрики, чтобы понять портрет среднестатистического комментатора хабра.
Так как возможности библиотеки уже показаны, не будем останавливаться на остальном коде, а сразу укажу, что можно собрать:
- Текст комментариев. Он всегда в тегах p без классов.
- Список пользователей. Они живут в тегах span с классом «tm-user-info tm-comment__user-info». Внутри две ссылки: первая на профиль пользователя, вторая — на сам комментарий.
- Карму пользователя. Она находится в профиле пользователя в теге div с классом «tm-user-card__meta» (с некоторым мусором).
- Счётчики пользователя. Количество публикаций и комментариев пользователя расположены в одинаковых тегах span с классом «tm-tabs tm-user__tabs tm-tabs». Обратите внимание, что количество комментариев (и даже публикаций!) — это не всегда число. Количество комментариев может быть 4K, например.
Как уже говорил, я исключил из обзора все исключительно технические статьи, оставив те, где обсуждается Илон Маск, Мишустин, Роскосмос, SpaceX, НАСА, релокация, Минцифры, Байкал, Эльбрус, налоги и прочее, где чаще всего тусуются те, кто «мимокрокодил». Осталось всего 82 статьи из 400. Таким же образом выбрал новости, которых оказалось 250 из 400.
Нельзя сказать, что в этом перечне нет ничего технического, и что выборка вообще репрезентативна. Но нужно же как-то подбивать исходные данные для самых провокационных выводов? 🙂
Для каждой статьи и новости был собран набор пользователей, написавших комментарии. Я покажу только обезличенные и только сводные данные. Ниже выложил сгруппированную таблицу с количеством комментариев, оставленных пользователями с кармой X и количеством публикаций Y. Результат грустный — в основном в комментариях таких статей участвуют люди, у которых вообще нет своих публикаций и почти нет кармы.
parse_url
Эта функция разбирает URL и возвращает ассоциативный массив, содержащий все компоненты URL, которые в нём присутствуют. Элементы массива не будут декодированы как URL.
Эта функция не предназначена для проверки на корректность данного URL, она только разбивает его на нижеперечисленные части. Частичные и недопустимые URL также принимаются, parse_url() пытается сделать всё возможное, чтобы разобрать их корректно.
Список параметров
URL для разбора.
Укажите одну из констант PHP_URL_SCHEME , PHP_URL_HOST , PHP_URL_PORT , PHP_URL_USER , PHP_URL_PASS , PHP_URL_PATH , PHP_URL_QUERY или PHP_URL_FRAGMENT , чтобы получить только конкретный компонент URL в виде строки ( string ). Исключением является указание PHP_URL_PORT , в этом случае возвращаемое значение будет типа int .
Возвращаемые значения
При разборе значительно некорректных URL-адресов parse_url() может вернуть false .
- scheme — например, http
- host
- port
- user
- pass
- path
- query — после знака вопроса ?
- fragment — после знака решётки #
Если параметр component определён, функция parse_url() вернёт строку ( string ) (или число ( int ), в случае PHP_URL_PORT ) вместо массива ( array ). Если запрошенный компонент не существует в данном URL, будет возвращён null . Начиная с PHP 8.0.0, parse_url() различает отсутствующие и пустые запросы и фрагменты:
http://example.com/foo → query = null, fragment = null http://example.com/foo? → query = "", fragment = null http://example.com/foo# → query = null, fragment = "" http://example.com/foo?# → query = "", fragment = ""
Ранее во всех случаях запрос и фрагмент были null .
Обратите внимание, что управляющие символы (смотрите ctype_cntrl() ) в компонентах заменяются подчёркиванием ( _ ).
Список изменений
| Версия | Описание |
|---|---|
| 8.0.0 | parse_url() теперь различает отсутствующие и пустые запросы и фрагменты. |
Примеры
Пример #1 Пример использования parse_url()
var_dump ( parse_url ( $url ));
var_dump ( parse_url ( $url , PHP_URL_SCHEME ));
var_dump ( parse_url ( $url , PHP_URL_USER ));
var_dump ( parse_url ( $url , PHP_URL_PASS ));
var_dump ( parse_url ( $url , PHP_URL_HOST ));
var_dump ( parse_url ( $url , PHP_URL_PORT ));
var_dump ( parse_url ( $url , PHP_URL_PATH ));
var_dump ( parse_url ( $url , PHP_URL_QUERY ));
var_dump ( parse_url ( $url , PHP_URL_FRAGMENT ));
?>
Результат выполнения данного примера:
array(8) < ["scheme"]=>string(4) "http" ["host"]=> string(8) "hostname" ["port"]=> int(9090) ["user"]=> string(8) "username" ["pass"]=> string(8) "password" ["path"]=> string(5) "/path" ["query"]=> string(9) "arg=value" ["fragment"]=> string(6) "anchor" > string(4) "http" string(8) "username" string(8) "password" string(8) "hostname" int(9090) string(5) "/path" string(9) "arg=value" string(6) "anchor"
Пример #2 Пример использования parse_url() при отсутствии протокола
// До 5.4.7 в path выводилось «//www.example.com/path»
var_dump ( parse_url ( $url ));
?>
Результат выполнения данного примера:
array(3) < ["host"]=>string(15) "www.example.com" ["path"]=> string(5) "/path" ["query"]=> string(17) "googleguy=googley" >
Примечания
Предостережение
Функция может выдать некорректные результаты для относительных или недействительных URL-адресов и результаты могут даже не соответствовать общему поведению HTTP-клиентов. Если необходимо проанализировать URL-адреса из ненадёжных входных данных, потребуется дополнительная проверка, например, с помощью filter_var() с фильтром FILTER_VALIDATE_URL .
Замечание:
Эта функция предназначена специально для разбора URL-адресов, а не URI. Однако, чтобы соответствовать требованиям обратной совместимости PHP, она делает исключение для протокола file://, в которой допускаются тройные слеши (file:///. ). Для любого другого протокола это недопустимо.
Смотрите также
- pathinfo() — Возвращает информацию о пути к файлу
- parse_str() — Разбирает строку в переменные
- http_build_query() — Генерирует URL-кодированную строку запроса
- dirname() — Возвращает имя родительского каталога из указанного пути
- basename() — Возвращает последний компонент имени из указанного пути
- » RFC 3986
User Contributed Notes 35 notes
11 years ago
[If you haven’t yet] been able to find a simple conversion back to string from a parsed url, here’s an example:
function unparse_url ( $parsed_url ) <
$scheme = isset( $parsed_url [ ‘scheme’ ]) ? $parsed_url [ ‘scheme’ ] . ‘://’ : » ;
$host = isset( $parsed_url [ ‘host’ ]) ? $parsed_url [ ‘host’ ] : » ;
$port = isset( $parsed_url [ ‘port’ ]) ? ‘:’ . $parsed_url [ ‘port’ ] : » ;
$user = isset( $parsed_url [ ‘user’ ]) ? $parsed_url [ ‘user’ ] : » ;
$pass = isset( $parsed_url [ ‘pass’ ]) ? ‘:’ . $parsed_url [ ‘pass’ ] : » ;
$pass = ( $user || $pass ) ? » $pass @» : » ;
$path = isset( $parsed_url [ ‘path’ ]) ? $parsed_url [ ‘path’ ] : » ;
$query = isset( $parsed_url [ ‘query’ ]) ? ‘?’ . $parsed_url [ ‘query’ ] : » ;
$fragment = isset( $parsed_url [ ‘fragment’ ]) ? ‘#’ . $parsed_url [ ‘fragment’ ] : » ;
return » $scheme$user$pass$host$port$path$query$fragment » ;
>
1 year ago
Unfortunately parse_url() DO NOT parse correctly urls without scheme or ‘//’. For example ‘www.xyz.com’ is consider as path not host:
Code:
var_dump ( parse_url ( ‘www.xyz.com’ ));
?>
Output:
array(1) [«path»]=>
string(10) «www.xyz.com»
>
To get better output change url to:
‘//www.xyz.com’ or ‘http://www.xyz.com’
8 years ago
It may be worth reminding that the value of the #fragment never gets sent to the server. Anchors processing is exclusively client-side.
9 years ago
Here is utf-8 compatible parse_url() replacement function based on «laszlo dot janszky at gmail dot com» work. Original incorrectly handled URLs with user:pass. Also made PHP 5.5 compatible (got rid of now deprecated regex /e modifier).
$parts = parse_url ( $enc_url );
if( $parts === false )
throw new \InvalidArgumentException ( ‘Malformed URL: ‘ . $url );
>
foreach( $parts as $name => $value )
$parts [ $name ] = urldecode ( $value );
>
15 years ago
URL’s in the query string of a relative URL will cause a problem
16 years ago
Here’s a function which implements resolving a relative URL according to RFC 2396 section 5.2. No doubt there are more efficient implementations, but this one tries to remain close to the standard for clarity. It relies on a function called «unparse_url» to implement section 7, left as an exercise for the reader (or you can substitute the «glue_url» function posted earlier).
/**
* Resolve a URL relative to a base path. This happens to work with POSIX
* filenames as well. This is based on RFC 2396 section 5.2.
*/
function resolve_url ( $base , $url ) if (! strlen ( $base )) return $url ;
// Step 2
if (! strlen ( $url )) return $base ;
// Step 3
if ( preg_match ( ‘!^[a-z]+:!i’ , $url )) return $url ;
$base = parse_url ( $base );
if ( $url < 0 >== «#» ) // Step 2 (fragment)
$base [ ‘fragment’ ] = substr ( $url , 1 );
return unparse_url ( $base );
>
unset( $base [ ‘fragment’ ]);
unset( $base [ ‘query’ ]);
if ( substr ( $url , 0 , 2 ) == «//» ) // Step 4
return unparse_url (array(
‘scheme’ => $base [ ‘scheme’ ],
‘path’ => $url ,
));
> else if ( $url < 0 >== «/» ) // Step 5
$base [ ‘path’ ] = $url ;
> else // Step 6
$path = explode ( ‘/’ , $base [ ‘path’ ]);
$url_path = explode ( ‘/’ , $url );
// Step 6a: drop file from base
array_pop ( $path );
// Step 6b, 6c, 6e: append url while removing «.» and «..» from
// the directory portion
$end = array_pop ( $url_path );
foreach ( $url_path as $segment ) if ( $segment == ‘.’ ) // skip
> else if ( $segment == ‘..’ && $path && $path [ sizeof ( $path )- 1 ] != ‘..’ ) array_pop ( $path );
> else $path [] = $segment ;
>
>
// Step 6d, 6f: remove «.» and «..» from file portion
if ( $end == ‘.’ ) $path [] = » ;
> else if ( $end == ‘..’ && $path && $path [ sizeof ( $path )- 1 ] != ‘..’ ) $path [ sizeof ( $path )- 1 ] = » ;
> else $path [] = $end ;
>
// Step 6h
$base [ ‘path’ ] = join ( ‘/’ , $path );
13 years ago
I was writing unit tests and needed to cause this function to kick out an error and return FALSE in order to test a specific execution path. If anyone else needs to force a failure, the following inputs will work:
7 years ago
I have coded a function which converts relative URL to absolute URL for a project of mine. Considering I could not find it elsewhere, I figured I would post it here.
The following function takes in 2 parameters, the first parameter is the URL you want to convert from relative to absolute, and the second parameter is a sample of the absolute URL.
Currently it does not resolve ‘../’ in the URL, only because I do not need it. Most webservers will resolve this for you. If you want it to resolve the ‘../’ in the path, it just takes minor modifications.
function relativeToAbsolute ( $inurl , $absolute ) // Get all parts so not getting them multiple times 🙂
$absolute_parts = parse_url ( $absolute );
// Test if URL is already absolute (contains host, or begins with ‘/’)
if ( ( strpos ( $inurl , $absolute_parts [ ‘host’ ]) == false ) ) // Define $tmpurlprefix to prevent errors below
$tmpurlprefix = «» ;
// Formulate URL prefix (SCHEME)
if (!(empty( $absolute_parts [ ‘scheme’ ]))) <
// Add scheme to tmpurlprefix
$tmpurlprefix .= $absolute_parts [ ‘scheme’ ] . «://» ;
>
// Formulate URL prefix (USER, PASS)
if ((!(empty( $absolute_parts [ ‘user’ ]))) and (!(empty( $absolute_parts [ ‘pass’ ])))) <
// Add user:port to tmpurlprefix
$tmpurlprefix .= $absolute_parts [ ‘user’ ] . «:» . $absolute_parts [ ‘pass’ ] . «@» ;
>
// Formulate URL prefix (HOST, PORT)
if (!(empty( $absolute_parts [ ‘host’ ]))) <
// Add host to tmpurlprefix
$tmpurlprefix .= $absolute_parts [ ‘host’ ];
// Check for a port, add if exists
if (!(empty( $absolute_parts [ ‘port’ ]))) // Add port to tmpurlprefix
$tmpurlprefix .= «:» . $absolute_parts [ ‘port’ ];
>
>
// Formulate URL prefix (PATH) and only add it if the path to image does not include ./
if ( (!(empty( $absolute_parts [ ‘path’ ]))) and ( substr ( $inurl , 0 , 1 ) != ‘/’ ) ) <
// Get path parts
$path_parts = pathinfo ( $absolute_parts [ ‘path’ ]);
// Add path to tmpurlprefix
$tmpurlprefix .= $path_parts [ ‘dirname’ ];
$tmpurlprefix .= «/» ;
>
else <
$tmpurlprefix .= «/» ;
>
// Lets remove the ‘/’
if ( substr ( $inurl , 0 , 1 ) == ‘/’ ) < $inurl = substr ( $inurl , 1 ); >
// Lets remove the ‘./’
if ( substr ( $inurl , 0 , 2 ) == ‘./’ ) < $inurl = substr ( $inurl , 2 ); >
return $tmpurlprefix . $inurl ;
>
else // Path is already absolute. Return it 🙂
return $inurl ;
>
>
// Define a sample absolute URL
$absolute = «http://» . «user:pass@example.com:8080/path/to/index.html» ; // Just evading php.net spam filter, not sure how example.com is spam.
/* EXAMPLE 1 */
echo relativeToAbsolute ( $absolute , $absolute ) . «\n» ;
/* EXAMPLE 2 */
echo relativeToAbsolute ( «img.gif» , $absolute ) . «\n» ;
/* EXAMPLE 3 */
echo relativeToAbsolute ( «/img.gif» , $absolute ) . «\n» ;
/* EXAMPLE 4 */
echo relativeToAbsolute ( «./img.gif» , $absolute ) . «\n» ;
/* EXAMPLE 5 */
echo relativeToAbsolute ( «../img.gif» , $absolute ) . «\n» ;
/* EXAMPLE 6 */
echo relativeToAbsolute ( «images/img.gif» , $absolute ) . «\n» ;
/* EXAMPLE 7 */
echo relativeToAbsolute ( «/images/img.gif» , $absolute ) . «\n» ;
/* EXAMPLE 8 */
echo relativeToAbsolute ( «./images/img.gif» , $absolute ) . «\n» ;
/* EXAMPLE 9 */
echo relativeToAbsolute ( «../images/img.gif» , $absolute ) . «\n» ;
?>
OUTPUTS:
http :// user:pass@example.com:8080/path/to/index.html
http :// user:pass@example.com:8080/path/to/img.gif
http :// user:pass@example.com:8080/img.gif
http :// user:pass@example.com:8080/path/to/img.gif
http :// user:pass@example.com:8080/path/to/../img.gif
http :// user:pass@example.com:8080/path/to/images/img.gif
http :// user:pass@example.com:8080/images/img.gif
http :// user:pass@example.com:8080/path/to/images/img.gif
http :// user:pass@example.com:8080/path/to/../images/img.gif
Sorry if the above code is not your style, or if you see it as «messy» or you think there is a better way to do it. I removed as much of the white space as possible.
Improvements are welcome 🙂
Пишем php парсер сайтов с нуля
Очень многие из нас хотели бы быстро наполнить сайт контентом. Я покажу вам, как несколько тысяч материалов собрать всего лишь за несколько часов.
Парсер на php — раз плюнуть!
Приветствую вас, наши дорогие читатели. Сегодня решил написать сложную статью про парсеры (сбор информации со сторонних ресурсов).
Скажу сразу, что вам потребуется знание основ программирования на php. В противном случае почитайте теории. Я не буду рассказывать азы, а сразу полезу показывать всё на практике.
Шаг 1 — PHP Simple HTML DOM Parser
Для парсинга сайтов мы будем использовать простецкую библиотечку под названием PHP Simple HTML DOM Parser, которую вы сможете скачать на сайте разработчика. Данный класс поможет вам работать с DOM-моделью страницы (дерево документа). Т.е. главная идея нашей будущей программы будет состоять из следующих пунктов:
- Скачиваем нужную страницу сайта
- Разбираем её по элементы (div, table, img и прочее)
- В соответствии с логикой получим определённые данные.
Давайте же начнём написание нашего php парсера сайтов.
Для начала подключим нашу библиотеку с помощью следующей строки кода:
include 'simple_html_dom.php';
Шаг 2 — Скачиваем страничку
На этом этапе мы смогли подключить файл к проекту и теперь пришла пора скачать страничку для парсинга.
В нашей библе есть две функции для получения удалённой страницы сайта. Вот эти функции
-
str_get_htm() — получает в качестве параметров обычную строку. Это полезно, если вы стянули страничку с помощью CURL или метода file_get_contents. Пример использования:
$seo = str_get_html('Привет, наш любимый читатель блога SEO-Love.ru!')
$seo = file_get_html('http://www.site.ru/');
После скачивания каждой страницы вам требуется подчищать память, дабы парсеру было легче работать и не так сильно грузился ваш сервер. Эта функция вызовется с помощью данного кода:
$seo = file_get_html('http://www.site.ru/'); $seo->clear();
Шаг 3 — Ищем нужные элементы на странице
После получения DOM-модели мы можем приступить непосредственно к поиску нужного элемента-блока в полученном коде.
Большая часть функций поиска использует метод find(selector, [index]). Если не указывать индекс, то функция возвратит массив всех полученных элементов. В противном случае метод вернёт элемент с номером [index].
Давайте же приведу вам первый пример. Спарсим мою страничку и найдём все картинки.
//подключили библиотеку 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(), которую я использовал выше в примере.
Также вы можете производить поиск по наличию атрибутов у искомого элемента. Пример:
//Найдём все изображения с шириной 300 $seo->find('img[width=300px]'); //Найдём изображения, у которых задана ширина $seo->find('img[width]'); //Поиск по наличию нескольких классов $seo->find('img[class=class1 class2]');////Ищем несколько тегов вместе $seo->find('div, span, img, a'); //Поиск по вложенности. //В div ищем все спаны, а в спанах ссылки $html->find('div span a');
Замечу, что у каждого вложенного тега так же есть возможность поиска!
Есть много вариантов поиска по атрибутам. Перечислять не стану, для более полного руководства прошу пройти на сайт разработчиков 🙂
Обычный текст, без тегов и прочего, можно искать так find(‘text’). Комментарии аналогично find(‘comment’).
Шаг 5 — Поля элементов
Каждый найденный элемент имеет несколько структур:
- $seo->tag Прочитает или запишет имя тега искомого элемента.
- $seo->outertext Прочитает или запишет всю HTML-структуру элемента с ним включительно.
- $seo->innertext Прочитает или запишет внутреннюю HTML-структуру элемента.
- $seo->plaintext Прочитает или запишет обычный текст в элементе. Запись в данное поле ничего не поменяет, хоть возможность изменения как бы присутствует.
$seo = str_get_html("first word second word"); echo $seo; // получимfirst word second word, т.е. всю структуру $div = $seo->find("div", 0); echo $div->tag; // Вернет: "div" echo $div->outertext; // Получимfirst word second wordecho $div->innertext; // Получим first word second word echo $div->plaintext; // Получим first word second word
Эта возможность очень просто позволяет бегать по DOM-дереву и перебирать его в зависимости от ваших нужд.
Если вы захотите затереть какой-либо элемент из дерева, то просто обнулить значение outertext, т.е. $div->outertext = «» ; Можно поэксперементировать с удалением элементов.
P.S. Я обнаружил проблему с кодировками при очистке и всяческими манипуляциями с полем innertext . Пришлось использовать outertext и затем с помощью функции strip_tags удалял ненужные теги.
Шаг 6 — Дочерние элементы
Разработчики данной библиотеки позаботились так же и о том, чтобы вам было легко перемещаться по дочерним и родительским элементам дерева. Для этого ими были любезно созданы следующие методы:
- $seo->children ( [int $index] ) Возвращает N-ый дочерний элемент, иначе возвращает массив, состоящий из всех дочерних элементов.
- $seo->parent() Возвращает родительский элемент искомого элемента.
- $seo->first_child() Возвращает первый дочерний элемент искомого элемента, или NULL, если результат пустой
- $seo->last_child() Возвращает последний дочерний элемент искомого элемента, или null, если результат пустой
- $seo->next_sibling() Возвращает следующий родственный элемент искомого элемента, или null, если результат пустой
- $seo->prev_sibling() Возвращает предыдущий родственный элемент искомого элемента, или null, если результат пустой
Я особо не пользовался этими возможностями, потому что они ещё ни разу не пригодились мне. Хотя один раз при разборе таблицы использовал, потому что они структурированы, что делает разбор очень простым и лёгким.
Шаг 7 — Практика
Перейдём к практике. Я решил отдать вам на растерзание одну функцию, что использовал при написании парсера текстов песен на один из своих сайтов. Пытался досконально подробно описать код. Смотрите комментарии и задавайте вопросы.
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 . '
'); //нашли список всех исполнителей $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 . '
'; //загрузим текст песни $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 . '
'; echo $songs->href . '
'; echo $text_song->outertext . '
'; $text_song->outertext = preg_replace("/(
]*>\s*)+/i", "
", $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("#(:?
)#i", "
", strip_tags($text_song->outertext, ''
))))); //если добавили - увеличим счётчик новых песен 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 . '
'; echo $songs->href . '
'; echo $text_song->outertext . '
'; $text_song->outertext = preg_replace("/(
]*>\s*)+/i", "
", $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("#(:?
)#i", "
", strip_tags($text_song->outertext, ''
))))); if ($result != -1) $new_songs++; > $i++; break; > > > > > > > > > return "
Парсер сайта rock-txt.ru завершён. Спарсено песен всего " . $i . ", из них новых " . $new_songs . " "; >
Получилась вот такая здоровая функция, которая парсит тексты песен с сайта о роке. Написал её я за час. Спарсил 10 000 текстов песен. Думаю, что руками вы бы набивали такую базу очень и очень долго 🙂
Замечу, что в коде много самописных функций, которые используются для вставки в мой базу. Эти функции у каждого могут быть индивидуальными, так что в этом я вам не помощник. Либо обращайтесь за помощью в комментарии. Всегда буду рад помочь!
Пока что на этом всё. В следующих уроках расскажу, как можно быстро и просто спарсить кучу информации на несколько десятков сайтов. Этот кейс должен обогатить каждого!
Всего доброго! Ретвиты, лайки и репосты приветствуются!
Как парсить сайт: 20+ инструментов на все случаи жизни
Парсинг (web scraping) — это автоматизированный сбор открытой информации в интернете по заданным условиям. Парсить можно данные с сайтов, поисковой выдачи, форумов и социальных сетей, порталов и агрегаторов. В этой статье разбираемся с парсерами сайтов.
Часто требуется получить и проанализировать большой массив технической и коммерческой информации, размещенной на своих проектах или сайтах конкурентов. Для сбора таких данных незаменимы парсеры — программы или сервисы, которые «вытаскивают» нужную информацию и представляют ее в структурированном виде.
Парсинг — это законно?
Сбор открытой информации в интернете не запрещен законодательством РФ. Более того, в п.4 статьи 29 Конституции закреплено «право свободно искать, получать, передавать, производить и распространять информацию любым законным способом». Парсинг данных часто сравнивают с фотографированием ценников в магазинах: если информация есть в открытом доступе, не защищена авторским правом или другими ограничениями, значит, ее можно копировать и распространять.
Применительно к данным в интернете это значит, что законным является сбор сведений, для получения которых не требуется авторизация. А вот персональные данные пользователей защищены отдельным законом и парсить их с целью таргетирования рекламы или email-рассылок нельзя.
Кому и зачем нужны парсеры сайтов
Парсеры экономят время на сбор большого объема данных и группировку их в нужный вид. Такими сервисами пользуются интернет-маркетологи, вебмастера, SEO-специалисты, сотрудники отделов продаж.
Парсеры могут выполнять следующие задачи:
- Сбор цен и ассортимента. Это полезно для интернет-магазинов. При помощи парсера можно мониторить цены конкурентов и наполнять каталог на своем ресурсе в автоматическом режиме.
- Парсинг метаданных сайта (title, description, заголовков H1) пригодится SEO-специалистам.
- Анализ технической оптимизации ресурса (битые ссылки, ошибки 404, неработающие редиректы и др.) потребуется сеошникам и вебмастерам.
- Программы для скачивания сайтов целиком или парсеры контента (текстов, картинок, ссылок) находятся в «серой» зоне. С их помощью недобросовестные вебмастера клонируют сайты для последующей продажи с них ссылок. Сюда же отнесем парсинг данных с агрегаторов и картографических сервисов: Авито, Яндекс Карт, 2gis и других. Собранные базы используются для спамных обзвонов и рассылок.
Кому и для каких целей требуются парсеры, разобрались. Если вам нужен этот инструмент, есть несколько способов его заполучить.
- При наличии программистов в штате проще всего поставить им задачу сделать парсер под нужные цели. Так вы получите гибкие настройки и оперативную техподдержку. Самые популярные языки для создания парсеров — PHP и Python.
- Воспользоваться бесплатным или платным облачным сервисом.
- Установить подходящую по функционалу программу.
- Обратиться в компанию, которая разработает инструмент под ваши нужды (ожидаемо самый дорогой вариант).
С первым и последним вариантом все понятно. Но выбор из готовых решений может занять немало времени. Мы упростили эту задачу и сделали обзор инструментов.
Классификация парсеров
Парсеры можно классифицировать по различным признакам.
- По способу доступа к интерфейсу: облачные решения и программы, которые требуют установки на компьютер.
- По технологии: парсеры на основе языков программирования (Python, PHP), расширения для браузеров, надстройки в Excel, формулы в Google таблицах.
- По назначению: мониторинг конкурентов, сбор данных в определенной нише рынка, парсинг товаров и цен для наполнения каталога интернет-магазина, парсеры данных соцсетей (сообществ и пользователей), проверка оптимизации своего ресурса.
Разберем парсеры по разным признакам, подробнее остановимся на парсерах по назначению.

Многофункциональные парсеры
Такие инструменты способны собирать данные под разные задачи — наполнение интернет-магазинов, мониторинг цен конкурентов, парсинг агрегаторов данных, сбор SEO-параметров и прочее. К этой группе относятся все браузерные расширения с функцией парсинга.
Другие примеры многофункциональных парсеров:
- Import.io, Mozenda — комплексы инструментов для извлечения и визуализации данных. Подходят для среднего и крупного бизнеса с большим объемом задач.
- Octoparce — инструмент для мониторинга цен и сбора данных с любого сайта. Данные выгружаются в форматы CSV или Excel. Есть доступ по API.
- ParseHub — облачный парсер для сбора цен, контактов, маркетинговых данных, скачивания файлов, мониторинга конкурентов. Работает со всеми типами сайтов, в том числе, агрегаторами и маркетплейсами. Данные доступны в форматах CSV, Excel, Google Sheets, предоставляется доступ по API.
- Datacol. Извлекает данные с сайтов, агрегаторов, соцсетей, Яндекс Карт и других источников. Базовые функции можно расширить при помощи плагинов. Программа платная, но есть демо-версия для тестирования.
- ParserOK. С помощью программы можно парсить данные из интернет-магазинов, контактов, загружать файлы различных форматов в облачное хранилище.
SEO-парсеры
Парсеры используются SEO-специалистами для комплексного анализа сайта: внутренней, технической и внешней оптимизации. У одних может быть узкий функционал, другие представляют собой мощные SEO-комбайны из различных профессиональных инструментов.
Задачи, которые могут выполнять SEO-парсеры:
- указывать на корректность настройки главного зеркала;
- анализировать содержание robots.txt и sitemap.xml;
- указывать наличие, длину и содержание метатегов title и description, количество и содержание заголовков h1 — h6;
- определять коды ответа страниц;
- генерировать XML-карту сайта;
- определять уровень вложенности страниц и визуализировать структуру сайта;
- указывать наличие/отсутствие атрибутов alt у картинок;
- определять битые ссылки;
- определять наличие атрибута rel=«canonical»;
- предоставлять данные по внутренней перелинковке и внешней ссылочной массе;
- отображать сведения о технической оптимизации: скорости загрузки, валидности кода, оптимизации под мобильные устройства и др.
Кратко охарактеризуем функционал популярных SEO-парсеров:
- Screaming Frog SEO Spider
- Netpeak Spider
- ComparseR
- SiteAnalyzer от Majento
- SE Ranking
- A-Parser
- PR-CY
- Xenu’s Link Sleuth
Screaming Frog SEO Spider
Пожалуй, самый популярный SEO-анализатор от британских разработчиков. С его помощью можно быстро и наглядно выяснить:
- содержимое, код ответа, статус индексации каждой страницы;
- длину и содержимое title и description;
- наличие и содержимое заголовков h1 и h2;
- информацию об изображениях на сайте — формат, размер, статус индексации;
- информацию по настройке канонических ссылок и пагинации;
- другие важные данные.
Для использования сервиса из России необходимо подключить VPN соединение.

Интерфейс бесплатной версии Screaming Frog
Бесплатная версия ограничена 500-ми url. В платной (лицензию можно купить на год) количество страниц для парсинга не ограничено, и она имеет гораздо больше возможностей. Среди них — парсинг цен, названий и описаний товаров с любого сайта. Как это сделать, мы подробно описали в гайде.
Netpeak Spider
Популярный инструмент для комплексного анализа сайта. Проверяет ресурс на ошибки внутренней оптимизации, анализирует важные для SEO параметры: битые ссылки, дубли страниц и метатегов, коды ответа, редиректы и другие. Можно импортировать данные из Google Search Console и систем веб-аналитики. Для агентств есть возможность сформировать брендированный отчет.

Интерфейс Netpeak Spider
В комплекте с NetPeak Spider идет еще один полезный для seo-специалистов инструмент — NetPeak Checker. Он позволяет сравнить разные url по десяткам параметров: от метатегов страниц и видимости в поисковых системах до параметров DR и DA (если у вас подключены и оплачены соответствующие сервисы).
Инструменты NetPeak платные, базовые функции доступны во всех тарифах. Бесплатный пробный период — 14 дней.
Воспользоваться NetPeak Spider с российского IP сейчас не получится. Для корректной работы программы необходимо подключать VPN.
ComparseR
Это программа, которая анализирует ресурс на предмет технических ошибок. Особенность парсера в том, что он также показывает все страницы сайта в индексе Яндекс и Google. Эта функция полезна, чтобы выяснить, какие url не попали в индекс, а какие находятся в поиске (и те ли это страницы, которые нужны оптимизатору).

Интерфейс демо-версии ComparseR 1.2.1.
Программу можно купить и установить на один компьютер. Чтобы ознакомиться с принципом работы, скачайте демо-версию.
SiteAnalyzer от Majento
Бесплатная программа для сканирования всех страниц, скриптов, документов и изображений сайта. Используется для проведения технического SEO-аудита. Требует установки на ПК (ОС Windows), но может работать и со съемного носителя. «Вытаскивает» следующие данные: коды ответа сервера, наличие и содержимое метатегов и заголовков, определение атрибута rel=»canonical», внешние и внутренние ссылки для каждой страницы, дубли страниц и другие.

Интерфейс SiteAnalyzer 2.9.6.
Отчет можно экспортировать в форматы CSV, XLS и PDF.
Анализ сайта от SE Ranking
Инструмент анализирует ключевые параметры оптимизации сайта: наличие robots.txt и sitemap.xml, настройка главного зеркала, дубли страниц, коды ответа, метатеги и заголовки, технические ошибки, скорость загрузки, внутренние ссылки. По итогам сканирования сайту выставляется оценка по 100-балльной шкале. Есть опция создания XML-карты сайта. Полезная возможность для агентств — формирование брендированного отчета, который можно скачать в удобном формате или отправить на email. Отчеты запускаются вручную или по расписанию.
Сейчас для корректной работы с сервисом из России необходимо подключать VPN.

Дашборд со сводными данными анализа сайта от SE Ranking
Возможны две модели оплаты — за проверки позиций и ежемесячная подписка. Бесплатный пробный период — 2 недели.
A-Parser
Этот сервис объединяет более 70 парсеров под разные цели: парсинг выдачи популярных поисковых систем, ключевых слов, приложений, социальных сетей, Яндекс и Google карт, крупнейших интернет-магазинов, контента и другие. Кроме использования готовых инструментов есть возможности для программирования собственных парсеров на основе регулярных выражений, XPath, JavaScript. Разработчики также предоставляют доступ по API.

Интерфейс демо-версии A-Parser: парсинг результатов Яндекс Маркета по названию модели телевизора.
Тарифы зависят от количества опций и срока бесплатных обновлений. Возможности парсера можно оценить в демо-версии, которая будет доступна в течение шести часов после регистрации.
Анализ сайта от PR-CY
Онлайн-инструмент для анализа сайтов более чем по 70 пунктам. Указывает на ошибки оптимизации, предлагает варианты их решения, формирует SEO-чеклист и рекомендации по улучшению ресурса. По итогам сканирования сайту выставляется оценка в процентах.

Сводные данные сканирования сайта в интерфейсе PR-CY
Бесплатно можно получить лишь общую информацию по количеству страниц в индексе, наличию/отсутствию вирусов и фильтров поисковых систем, ссылочному профилю и некоторые другие данные. Более детальный анализ платный. Тариф зависит от количества сайтов, страниц в них и проверок на аккаунте. Есть возможность для ежедневного мониторинга, сравнения с показателями конкурентов и выгрузки брендированных отчетов. Бесплатный пробный период — 7 дней.
Упомянем также о парсерах, которые решают узконаправленные задачи и могут быть полезны владельцам сайтов, вебмастерам и SEO-специалистам.
Xenu’s Link Sleuth
Бесплатная программа для парсинга всех url сайта: внешних и внутренних ссылок, ссылок на картинки и скрипты и т. д. Можно использовать для разных задач, в том числе, для поиска битых ссылок на сайте. Программу нужно скачать и установить на компьютер (ОС Windows).
По каждой ссылке будет показан ее статус, тип (например, text/plain или text/html), размер, анкор и ошибка.

Интерфейс программы Xenu’s Link Sleuth
Парсер метатегов и заголовков PromoPult
Это инструмент, который парсит метатеги title, description, keywords и заголовки h1-h6. Можно воспользоваться им для анализа своего проекта или сайтов-конкурентов. В первом случае легко выявить незаполненные, неинформативные, слишком длинные или короткие метатеги, дубли метаданных, во втором — выяснить, какие ключевые запросы используют конкуренты, определить структуру и логику формирования метатегов.

Добавить список url можно вручную, XLSX-файлом или ссылкой на XML-карту сайта. Отчеты выгружаются в форматах HTML и XLSX. Первые 500 запросов — бесплатно. Все нюансы работы с инструментом мы описали в гайде.
Как выбрать парсер
- Определитесь с целью парсинга: мониторинг конкурентов, наполнение каталога, проверка SEO-параметров, совмещение нескольких задач.
- Выясните, какие данные в каком объеме и в каком виде вам нужно получить на выходе.
- Подумайте о том, насколько регулярно вам нужно собирать и обрабатывать данные: разово, ежемесячно, ежедневно?
- Если у вас большой ресурс со сложным функционалом, имеет смысл заказать создание парсера с гибкими настройками под ваши цели. Для стандартных проектов на рынке достаточно готовых решений.
- Выберите несколько инструментов и изучите отзывы. Особое внимание обратите на качество технической поддержки.
- Соотнесите уровень подготовки (свой или ответственного за работу с данными лица) со сложностью инструмента.
- На основе перечисленных выше параметров выберите подходящий инструмент и тариф. Возможно, под ваши задачи хватит бесплатного функционала или пробного периода.
