Регулярні вирази для чайників
- Про регулярні вирази
- Вчимося розуміти регулярні вирази
- Ледачі і Жадібні квантіфікатори
- Уніфікуємо регулярний вираз.
- Посилання в регулярних виразах
- Приклад парсинга інформації
Здебільшого 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.
Як вказати рядок, яка змінюється випадковим чином? Щоб зрозуміти рядок, треба зрозуміти з чого вона складається. (c) Будь-випадкова рядок складається з літер і (або) символів, які вказані в ній в різному (будь-якому) порядку. Щож, відкриємо довідник регулярних виразів. Кращий з зустрінутих мною, це довідник від 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=(['"])(.*?)/
Ура! Останній рядок не враховується.
У 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, які я тут описав, досвідчений кодер здатний написати регулярку на льоту, не заморочуючись. Вчіться, хехе.
Ну і, зрозуміло, ось исходник, якщо вам цікаво:
схоже
Що дозволяють робити ці «магічні конструкції» і чому так важливо вміти їх використовувати?Як нам це зробити?
Що ми знаємо?
Чи робить це наше вираз?
Що робити, якщо його треба виключити?
Lt;\/span>[\n\s\t]+?
Символи після цього блоку, а так-же два елементи </ span> і <span> , розділені між собою послідовністю з розриву рядків, таба і пробілу повторених кілька разів [\ n \ s \ t] +?
Lt;\ / span> [\ n \ s \ t] +?
Lt;\ / span> [\ n \ s \ t] +?
О він означає?