В большинстве своем 80% современного программирования сведены к анализу и работе с информацией. А информация всегда предоставляется нам в текстовой форме, просто потому, что другие ее формы анализу так легко не поддаются. Проще говоря одна из первых задач, с которой сталкивается любой программист, это парсинг информации. А где парсинг, там и регулярные выражения. О них мы сегодня и поговорим ибо выяснилось, что многие понятия не имеют, что это такое, и с чем это едят. После прочтения этой статьи вы поймете как создавать свои регулярные выражения, а так-же как с ними работать. Ниже даже пример на php есть.
О регулярных выражениях
И так, что же такое регулярное выражение?
/<a\s*href='(.+?)'/
Я, таки, могу прямо сюда скопировать описание из википедии, но думаю лучше просто показать, ибо объяснить как оно работает на словах очень сложно. Тут надо понять. Причем самому. Но определение я все-таки дам:
Регулярное выражение — это маска, по которой можно искать определенные данные в тексте или искать и заменять эти самые данные.
Что позволяют делать эти «магические конструкции» и почему так важно уметь их использовать? На самом деле все просто.
Предположим, нам надо найти все теги <div>
в строке. Пишем регулярное выражение, скармливаем ему строчку и все!
preg_match("/<div[^>]*>(.+?)<\/div>/is",$data,$matches);
Начинающий кодер, таки, может спросить: но разве то-же самое нельзя сделать через strpos и substr? Да, это можно сделать, но давайте просто сравним количество кода:
$spos=stripos($html,"<div");
$lpos=stripos($html,"</div");
if($spos !== false && $lpos !== false)
$result = substr($spos,$spos-$lpos);
Как вы видите, регулярные выражения экономят кучу времени. Да, для новичка они выглядят как нечто ужасное, сродни ктулху. Однако, страшного там ничего нет и сегодня вы в этом убедитесь.
Учимся понимать регулярные выражения
Прежде чем что-то писать или объяснять, я бы хотел представить вам инструмент для работы с регулярками, это RegExr. Откройте его в новой вкладке и прорабатывайте там все примеры, которые я буду приводить.
Начнем с простого, с чего начинал я сам. С получения содержимого атрибута тега. Предположим нам надо взять атрибут href="ссылка"
из тега ссылки. Как нам это сделать?
Давайте откроем ide RegExr и в нижнем поле впишем html код ссылки.
Теперь собственно составим регулярку. Что мы знаем?
1. Атрибут начинается с href и содержит знак равно, с двумя кавычками по краям.
2. Нужные нам данные находятся между этих кавычек и эти данные могут меняться случайный образом
Пункт 1 решается крайне просто. Давайте укажем в верхнем поле известные нам данные:
Теперь собственно обратимся к пункту номер 2.
Как указать строку, которая меняется случайным образом? Чтобы понять строку, надо понять из чего она состоит. (с) Любая случайная строка состоит из случайных букв и(или) символов, которые указаны в ней в различном (любом) порядке. Чтож, откроем справочник регулярных выражений. Лучший из встреченных мной, это справочник от microsoft. Не смотрите, что там указано, якобы справочник для javascript. В большинстве своем регулярные выражения работают одинаково на любых языках от C# до php. Ищем там описание кода для любого символа:
Как мы видим, в регулярке за любой символ отвечает точка (.), самая обыкновенная черная точка. Вот ее мы и укажем после href=""
Ничего? Да, все верно, регулярка не работает, (ожидаемо). А все потому, что мы указали 1 символ. То есть, если указать в атрибуте href всего 1 символ, то он выражение сработает. Давайте убедимся в этом:
Теперь перед нами стоит задача указать интерпретатору, что символов может быть больше 1 в несколько раз. Тут есть несколько различных путей, но остановимся на одном. Это банальное повторение предыдущего символа нужное кол-во раз. Ищем в справочнике по ссылке выше информацию об повторениях.
И так, за повторения отвечает или звездочка или знак плюса. Причем разница между ними есть. Выражение со звездочкой будет работать даже если символа вообще нет в анализируемом документе. Берем любой из них, я бы советовал вам взять для начала звездочку. Со временем научитесь их применять в зависимости от ситуации.
Ура! Заработало. Таким образом можно сказать мы написали наше первое регулярное выражение. Но не будем торопиться, ибо нет предела совершенству. Предположим, что нам надо получить из текста именно ту часть которая находится между кавычками. Делает ли это наше выражение? Нет. Оно получает полностью весь блок. Дабы убедиться наведем на мышь на выделенный текст:
То есть задача еще не выполнена. Снова открываем справочник и ищем как захватить часть регулярного выражения.
Простые кавычки () позволяют нам это сделать, хотя описание в документации как всегда крайне поехавшее. Укажем кавычки в нашем редакторе и посмотрим на результат.
Отлично. Теперь результатом работы регулярного выражения будет массив содержащий как полный текст искомого блока, так и содержимое этого самого блока.
В php эта регулярка переносится так:
/href="(.*)"/
Ленивые и Жадные квантификаторы
Собственно разница между этими квантификаторами в подходе к выборке данных. Жадное квантификаторы берут Максимально возможный текстовый блок. А Ленивые хапают то, до чего быстрее всего дотягиваются. Ибо остальное собирать — «лень».
В нашем случае ленивое регулярное выражение выглядит так:
/href="(.*?)"/
Я добавил знак вопроса после звездочки. Читаем:
Дабы осветить эту тему на примере нам придется открыть php sandbox, которая позволяет запускать php код прямо из браузера.
Код который мы будем запускать приведен ниже:
// Дублируем дважды нашу ссылку
$str = '<a href="http://aftamat4ik.ru">Это ссылка из которой мы будем брать href</a>
<a href="http://aftamat4ik.ru">Это ссылка из которой мы будем брать href</a>
';
// Жадно
preg_match('/href="(.*)"/is', $str, $matches);
echo "Жадное регулярное выражение: ".$matches[1].PHP_EOL;; echo '-------------------------'.PHP_EOL;
// Лениво
preg_match('/href="(.*?)"/', $str, $matches); echo "Ленивое регулярное выражение: ".$matches[1].PHP_EOL;
Результат выполнения этого кода наглядно покажет вам отличия жадного и ленивого выражения друг от друга.
Как вы видите, без вопросительного знака в коде регулярного выражения нам никак не обойтись, ибо без него выражение будет захватывать лишнюю информацию, превращая все в трэш угар и содомию.
/href="(.*?)"/
Но это еще не все!
Унифицируем регулярное выражение.
Переключимся назад к RegExr и представим ситуацию, при которой html код, анализируемый нами, содержит в href не только двойные, но и одинарные кавычки.
Не работает! Подходящий способ решения — это указать вместо кавычек, набор символов, где будут оба вида этих элементов. За создание набора символов отвечают квадратные скобки:
Собственно делается это так: ["']
— эта последовательность символов соответствует как "
и так и '
.
Итоговое регулярное выражение примет вид:
/href=['"](.*?)['"]/
Как вы видите, все определяется правильно. Но, обратите внимание на последнюю ссылку, где имеется
href='
и "
, то есть совершенно не работающий html код, который, тем не менее, определяется как работающий. Что делать, если его надо исключить? Используем ссылки.
Ссылки в регулярных выражениях
Ссылки позволяют нам, в самом выражении, ссылаться на его элементы. Например наш элемент — href=""
имеет два повторяющихся символа, на которые мы и будем опираться. Это "
и "
или '
и '
. Так как они повторяются, мы можем взять такой символ и сослаться на него второй раз, а не указывать снова. Тогда получится отфильтровать ошибку, ведь если мы ссылаемся на кавычку вида "
то регулярное выражение будет искать именно ее. Чтобы сослаться, надо просто указать конструкцию вида: \число
, к примеру \1
где цифра — номер скобочки. Да, элемент на который мы ссылаемся, должен быть в круглых скобках. В нашем случае:
Берем выражение ['"]
и оборачиваем в кавычки (['"])
а потом просто ссылаемся на кавычки через \1
В итоге получим:
/href=(['"])(.*?)\1/
Ура! Последняя строчка не учитывается.
В 90% случаев этих знаний вам хватит с лихвой для парсинга чего угодно, откуда угодно.
Пример парсинга информации
Давайте, используя регулярные выражения и php, спарсим, в маленьком скрипте, например, эту страницу — хабр. Для отработки навыков самое оно будет.
В php за работу регулярок отвечают функции preg_match и preg_match_all, которые и будут использоваться в данном примере.
Для начала, давайте определим регулярное выражение, которое соответствует заголовку статьи.
Я работаю в браузере гугл хром, поэтому открываю html-код страницы и ищу там блок с материалом.
и далее сюда —
Тут выделен блок, отвечающий за название материала, которое мы и будем парсить в первую очередь. Чтож, берем его, копируем его в regexr и подбираем регулярное выражение!
Как вы видите в Group#1 находится чистый заголовок. Разберем то, что я написал в выражении:
/class=['"]post__title-arrow.+<\/span>[\n\s\t]+?<span>(.+?)<\/span>/
Элемент (.+?)
, представляющий из себя строку не нулевой длинны, обрамленный спереди в строку с атрибутом class
содержащий блок post__title-arrow и любые .+ символы после этого блока, а так-же два элемента </span>
и <span>
, разделенные меж собой последовательностью из переноса строки, таба и пробела повторенных несколько раз [\n\s\t]+?
заканчивается на </span>
Да… описание работы выглядит как … в общем не пытайтесь это повторить.
preg_match("/class=['\"]post__title-arrow.+<\/span>[\n\s\t]+?<span>(.+?)<\/span>/",$html, $titles);
К стати в блоке ['\"]
символ \
означает экранирование кавычки. Он не учитывается в итоговом выражении и всего-лишь помогает интерпретатору воспринимать строку целиком. Осталось вставить эту регулярку в php с экранированием и обрамлением в символы границ.
Итоговый php код выглядит так:
$html = file_get_contents("https://habrahabr.ru/sandbox/15940/");
preg_match("~class=['\"]post__title-arrow.+<\/span>[\n\s\t]+?<span>(.+?)<\/span>~",$html,$titles);
echo $titles[1];
Функция preg_match
анализирует строку $html и ищет там блок, который подходит под регулярное выражение, после чего извлекает из этого блока группы в круглых скобочках () и помещает их в массив. В данном случае этот массив называется $titles и $titles[1] собственно вывод первой(и единственной в данном случае) кавычки.
И результат выполнения кода выглядит так:
Точно так-же определяем регулярное выражение для текста статьи. Опять копирем блок отвечающий за данный текст в regexr и подбираем выражение, а потом просто вставляем его в preg_match с экранированием.
И выражение:
/class=['"]content html_format['"]>([\s\S]+?)<div class=['"]post__tags/
Тут вы видите новый блок [\s\S]+?,
что он значит? Заголовок статьи — не имеет в себе переносов строки и поэтому там можно было справиться банальным повторением символа .+? тут же у нас есть переносы и пробелы. Ибо это контент. Чтобы решить проблему мы просто помещаем внутрь кавычек символ \s
— пробела и \S
— все что угодно, кроме пробела и указывам, что этот блок повторяется несколько раз через +?
Результат парсинга будет таков —
И вот мы спарсили сайт! Да, так это работает. Человек, потративший пару дней на изучение регулярок, в последствии сможет спарсить все что угодно!
Ну и на последок, разумеется, общий код.
// Тестовый парсер
header('Content-Type: text/html; charset=utf-8');
// Определяем заголовок материала
$html = file_get_contents("https://habrahabr.ru/sandbox/15940/");
preg_match("~class=['\"]post__title-arrow.+<\/span>[\n\s\t]+?<span>(.+?)<\/span>~",$html,$titles);
preg_match("~class=['\"]content html_format['\"]>([\s\S]+?)<div class=['\"]post__tags~",$html,$contents);
echo "<h3>".$titles[1]."</h3>";
echo $contents[1];
С небольшими отличиями, регулярные выражения одинаковы везде. В js,php,c# или любом другом языке программирования. Синтаксис везде один и тот-же, а значит и этот метод будет актуален и там тоже. К слову, несмотря на все свистопляски с regexr,которые я тут описал, опытный кодер способен написать регулярку на лету, не заморачиваясь. Учитесь, хехе.
Ну и, разумеется, вот исходник, если вам интересно:

доступно. спасибо.
Очень интересно, на выходных обязательно разберу материал. И ссылочки на regexr и php sandbox тоже полезные. Пиши чаще, шикарный и доступный материал. Кстати с наступившим тебя др-ом ))
спасибо)
Спасибо, Гарри, за классную статью! А как вот такое регулярное выражение «$s = str_replace(«@», «https://www.instagram.com/», $s); » написать в твой парсер? Как его превратить в такой вид «(.+?)[|]»? Для этого существуют какие-нибудь конвертеры?
а что, str_replace не работает?
вам надо заменять эмейлы? или там просто опечатка?
так попробуйте.
Да, я хотел заменять собачку в теле статьи body. То есть, чтобы: было @aftamat4ik, а стало https://www.instagram.com/aftamat4ik. В парсере по селекторам $res = str_replace(‘[email protected]~’, «https://www.instagram.com/», $res); не работает, может нужно это выражение написать в таком виде, как например (.+?)[|], чтобы заработало?
ааа, тогда сделайте так:
Спасибо вам большое!
рад, что смог помочь)
Здраствуйте, подскажите пожалуйста какая регулярка способна справиться с (src=»img/publishing/?id=275533″) подобными ссылками на изображения?
никакая. изображения имеют формат. а тут все что вы можете это получить ссылку на их источник.
Дело в том, что даже ссылку получить не получается, потому как все что после вопросительного знака отсекается
аа, ясно, попробуйте так:
Все равно не работает. Дело в том, что картинки размещены в директории, полный путь к изображению следующий https://site.ru/ru/img/publishing/?123456/. Можно ли путем замены (или может другой вариант) добавить к имеющемуся адресу эту директорию?
так
Все равно не работает. Вот html код страницы http://joxi.ru/8Ano7GNUjEElQr, а вот то, что парсит парсер без приведенного вами, кода http://joxi.ru/zANYOyBTBaaDG2 . То есть он сам конвертирует ссылки вида src=»img/publishing/?id=275533″ в src=»https://site.ru/img/publishing/»
парсер такие ссылки не умел парсить и никогда не сумеет. Я думал вам регулярка для других нужд нужна.
я понял, спасибо за уделенное время
никак регулярку не напишу
нужно искать ссылки по примеру:
http://somesite.ru/somecategorywithsimbols-_/sometopicwithsimbols_-.html
\bhttp\:.*\.html\b такой срабатывает редко
(https?:\/\/)?(www\.)?([-а-яa-z0-9_\.]{2,}\.(рф|[a-z]{2,6}))((\/[-а-яa-z0-9_]{1,}\/)|(\/[-а-яa-z0-9_]{1,}\/)([-а-яa-z0-9_]{2,}\.(рф|[a-z]{2,6})))?((\?[a-z0-9_]{2,}=[-0-9]{1,})?((\&[a-z0-9_]{2,}=[-0-9]{1,}){1,})?)?
такой выбирает только до категории
то есть вам надо выбирать категорию и название?
вот так попробуйте
Спасибо, очень доступно и наглядно. Кстати, после редизайна Хабра исходный код html-страниц изменился и ваш код, соответственно, уже не работает. Нужно писать другие регулярки.
ну, все течет, все изменяется)
Здраствуйте. Скажите пожалуйста, умеет ли парсер работать с подобными ссылками href=»http://site.ru/?p=7240 ? Пробовал вот так href=[\'»]([^*]+?p=[0-9]+?)[\»‘] не получается
да вот так попробуйте, примерно
Не получается(
Здраствуйте Гарри. Сори за навязчивость, можете помочь с регуляркой? Перебрал множество разных вариантов, но никак не получается найти правильный. Есть сайты, у которых URL-ы содержат вопросительный знак (пример: http://site.ru/ru/news.html?_m=publications&_t=rec&id=215980, или как в примере выше). Пробовал по Вашему примеру, но ничего не получается. Парсер почему-то отказывается сканировать ссылки для отложенного парсинга.