DevGang
Авторизоваться

Python Regex Superpower

Вы офисный работник, студент, разработчик программного обеспечения, менеджер, блогер, исследователь, автор, копирайтер, преподаватель или работающий не по найму фрилансер? Скорее всего, вы проводите много часов за компьютером, день за днем. В любом случае, повышение вашей ежедневной производительности - только на небольшую долю процента - приведет к тысячам, если не к десяткам тысяч долларов прироста производительности. И что еще более важно, если вы не просто тратите время на работу, повышение производительности вашего компьютера даст вам больше свободного времени для более эффективного использования.

В этом посте показана чрезвычайно недооцененная технология, которая помогает мастерам более эффективно использовать свое время при работе с текстовыми данными. Технология называется «регулярные выражения». В этом посте показано десять способов использования регулярных выражений для решения повседневных задач с меньшими усилиями, временем и энергией.

Написание вашего первого веб-парсера с регулярными выражениями

Почему вы должны заботиться о регулярных выражениях? Потому что вы будете встречаться с ними регулярно, если вы занимаетесь программированием.

Предположим, вы работаете в качестве внештатного разработчика программного обеспечения. Ваш клиент - это стартап Fintech, которому нужно быть в курсе последних событий в области криптовалюты. Они нанимают вас, чтобы написать веб-парсер, который регулярно извлекает исходный код HTML новостных сайтов и ищет в нем слова, начинающиеся с «crypto» (например, «cryptocurrency», «crypto-bot», «crypto-crash»,…) ,

Ваша первая попытка - следующий фрагмент кода:

import urllib.request

search_phrase = 'crypto'

with urllib.request.urlopen('https://www.wired.com/') as response:
   html = response.read().decode("utf8") # convert to string
   first_pos = html.find(search_phrase)
   print(html[first_pos-10:first_pos+10])

Метод urlopen (из модуля urllib.request) извлекает исходный код HTML из указанного URL-адреса. В результате получается байтовый массив, сначала вы конвертируете его в строку с помощью метода decode(). Затем вы используете строковый метод find(), который возвращает позицию первого вхождения искомой строки. С помощью нарезки (см. Главу 2) вы вырезаете подстроку, которая возвращает непосредственное окружение позиции. В результате получается следующая строка:

#, r = window.crypto || wi

Оо. Это выглядит плохо. Как оказалось, поисковая фраза неоднозначна - большинство слов, содержащих «крипто», семантически не связаны с криптовалютами. Ваш веб-парсер генерирует ложные срабатывания (он находит результаты строк, которые вы изначально не хотели находить). Так как же это исправить?

К счастью, вы только что прочитали эту книгу по Python , поэтому ответ очевиден: регулярные выражения! Ваша идея удалить ложные срабатывания состоит в том, чтобы искать случаи, когда за словом «crypto» следуют до 30 произвольных символов, за которыми следует слово «coin». Грубо говоря, поисковый запрос: «crypto» + <до 30 произвольных символов> + «coin». Рассмотрим следующие два примера:

  1. «crypto-bot that is trading Bitcoin» - ДА
  2. «cryptographic encryption methods that can be cracked easily with quantum computers» - НЕТ

Регулярное выражение похоже на язык мини-программирования внутри Python, который позволяет вам искать в строке вхождения шаблона запроса. Регулярные выражения намного более эффективны, чем функции текстового поиска по умолчанию, как показано выше. Например, набор строк запроса может даже иметь бесконечный размер!

Наша цель состоит в том, чтобы решить следующую проблему: «По заданной строке найдите случаи, в которых за строкой «crypto» следует до 30 произвольных символов, за которыми следует строка «coin». Давайте сначала посмотрим на результат, прежде чем обсудим - пошагово - как код решает проблему.

## Dependencies
import re


## Data
text_1 = "crypto-bot that is trading Bitcoin and other currencies"
text_2 = "cryptographic encryption methods that can be cracked easily with quantum computers"


## One-Liner
pattern = re.compile("crypto(.{1,30})coin")


## Result
print(pattern.match(text_1))
print(pattern.match(text_2))

Однострочное решение для поиска отрывков текста в виде криптографии.

Код ищет в двух разных строках text_1 и text_2. Соответствует ли им поисковый запрос (pattern)?

Сначала мы импортируем стандартный пакет для регулярных выражений в Python, который называется re. Важная вещь происходит в одной строке, где вы компилируете поисковый запрос «crypto (. {1,30}) coin» (так называемый шаблон в терминологии регулярных выражений). Это запрос, который мы можем затем искать в различных строках. Мы используем следующие специальные символы регулярных выражений. Прочитайте их сверху вниз, и вы поймете значение шаблона в приведенном выше фрагменте кода.

  1. ()    соответствует любому регулярному выражению,
  2. .    соответствует произвольному символу,
  3. {1,30}    соответствует от 1 до 30 вхождений предыдущего регулярного выражения,
  4. (.{1,30})    соответствует от 1 до 30 произвольных символов
  5. crypto(.{1,30})coin    соответствует регулярному выражению, состоящему из трех частей: слово «crypto», произвольная последовательность от 1 до 30 символов, за которой следует слово «coin».

Мы говорим, что шаблон скомпилирован, потому что Python создает объект шаблона, который может быть повторно использован в нескольких местах - так же, как скомпилированная программа может выполняться несколько раз. Теперь мы вызываем функцию match() для нашего скомпилированного шаблона и текста для поиска. Это приводит к следующему результату:

## Result
print(pattern.match(text_1))
# 

print(pattern.match(text_2))
# None

Строка text_1 соответствует шаблону (указанному в результате объекта сопоставления), строка text_2 - нет (указанному результату None). Хотя текстовое представление первого соответствующего объекта выглядит не очень красиво, оно дает четкий намек на то, что данная строка «crypto-bot that is trading Bitcoin» соответствует регулярному выражению.

Нахождение основных текстовых шаблонов в строках

Регулярное выражение (короче: regex) формально описывает шаблон поиска, используя комбинацию некоторых основных команд. Изучите эти основные команды, и вы легко поймете сложные регулярные выражения. В этом однострочном разделе мы сосредоточимся на трех наиболее важных командах регулярных выражений.

Точка Regex(.)

Во-первых, вам нужно знать, как сопоставить произвольный символ с помощью регулярного выражения, точки (.). Данное регулярного выражения соответствует любому символу. Вы можете использовать его, чтобы указать, что вам действительно все равно, какой символ соответствует выражению и если он только один (одна точка один символ).

import re

text = '''A blockchain, originally block chain,
is a growing list of records, called blocks,
which are linked using cryptography.
'''

print(re.findall('b...k', text))
# ['block', 'block', 'block']

В примере используется метод findall() пакета re. Первый параметр - это само регулярное выражение: мы ищем любой строковый шаблон, начинающийся с символа «b», за которым следуют три произвольных символа (точки ), за которыми следует символ «k». Обратите внимание, что соответствует не только строка «block», но и «boook», «b erk» и «bloek». Второй параметр - это текст для поиска. Текст строки содержит три таких шаблона.

Звездочка Regex(*)

Во-вторых, вам нужно знать, как сопоставить произвольное количество определенных символов с помощью регулярного выражения звездочка (*).

print(re.findall('y.*y', text))
# ['yptography']

Оператор звездочки применяется к регулярному выражению непосредственно перед ним. В этом примере шаблон регулярного выражения начинается с символа «y», за которым следует произвольное количество символов (. *), За которым следует символ «y». Слово «криптография» содержит один такой случай.

Если вы внимательно читаете это, вы можете удивиться, почему он не находит длинную подстроку между «originally» и «cryptography», которая также должна соответствовать шаблону регулярных выражений «y.*y». Причина в том, что оператор звездочки соответствует произвольному количеству символов, но не включает переводы строки. Семантически конец строки сбрасывает состояние поиска регулярного выражения. На следующей строке начинается новый поиск. Строка, хранящаяся в переменной text, представляет собой многострочную строку с тремя новыми строками.

Вопросительный знак Regex(?)

В-третьих, вам нужно знать, как сопоставить ноль или один символ с помощью регулярного выражения (?).

print(re.findall('blocks?', text))
# ['block', 'block', 'blocks']

Знак вопроса применяется к регулярному выражению непосредственно перед ним. В нашем случае это символ 's'. Смысл регулярного выражения «ноль или один» заключается в том, что искомый символ является необязательным.

Важной деталью является то, что знак вопроса может сочетаться с оператором звездочки '*?' чтобы разрешить не жадное сопоставление с образцом. Напротив, если вы используете оператор звездочки '*' без знака вопроса, он жадно сопоставляет максимально возможное количество символов. Например, при поиске в HTML:

hello world

Использование регулярного выражения <. *> совпадает со всей строкой. Если вы хотите достичь последнего, вы можете использовать не жадное регулярное выражение <. *?>:

txt = '
hello world
' print(re.findall('<.*>', txt)) # ['
hello world
'] print(re.findall('<.*?>', txt)) # ['
', '
']

Наша цель - решить следующую проблему: «Задана строка. Используйте не жадный подход, чтобы найти все шаблоны, которые начинаются с символа «p», заканчиваются символом «r» и имеют одно вхождение символа «e» (и произвольное количество других символов) между ними! » Эти типы текстовых запросов встречаются довольно часто, особенно в компаниях, которые занимаются обработкой текста, распознаванием речи или машинным обучением (например, поисковыми системами, социальными сетями или видеоплатформами).

## Dependencies
import re


## Data
text = 'peter piper picked a peck of pickled peppers'


## One-Liner
result = re.findall('p.*?e.*?r', text)


## Result
print(result)

Поисковый запрос регулярного выражения - p.*?e?.*?r. Мы ищем фразу, которая начинается с символа «p» и заканчивается символом «r». Между этими двумя символами нам требуется одно вхождение символа «е». Кроме того, мы допускаем произвольное количество символов (пробел или нет). Тем не менее, мы сопоставляем не жадным образом, используя регулярное выражение .*? так что Python ищет минимальное количество произвольных символов (а не максимальное количество произвольных символов).

## Result
print(result)
# ['peter', 'piper', 'picked a peck of pickled pepper']

Чтобы полностью понять, что такое не жадное сопоставление, сравните это решение с тем, которое было бы получено, если бы вы использовали жадное регулярное выражение p.*e.*r.

result = re.findall('p.*e.*r', text)
print(result)
# ['peter piper picked a peck of pickled pepper']

Первый жадный оператор звездочки .* соответствует почти всей строке до ее завершения.

Анализ гиперссылок в HTML

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

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

  1. . соответствует произвольному символу.
  2. A* соответствует произвольному числу экземпляров А.
  3. A? соответствует нулю или одному экземпляру А.
  4. .? соответствует как можно меньшему количеству произвольных символов, так что общее регулярное выражение, если возможно, соответствует.
  5. A{m} соответствует ровно m копий А.
  6. A{m,n} совпадает между m и n копиями A.
  7. A|B соответствует регулярному выражению A или B (но не обоим).
  8. AB соответствует сначала регулярному выражению A, а затем B.
  9. (A) соответствует регулярному выражению А. Скобки группам регулярных выражений, так что вы можете контролировать порядок выполнения (например, регулярное выражение (AB)|C отличается от A(B|C).

Давайте рассмотрим короткий пример. Скажем, вы создаете регулярное выражение b?(.a). Какие шаблоны будут соответствовать регулярному выражению? Регулярное выражение соответствует всем шаблонам, начинающимся с нуля или одного символа «b», и произвольным числом двухсимвольных последовательностей, заканчивающихся символом «a». Следовательно, строки "bcacaca", и "aaaaaa" будут соответствовать регулярному выражению.

Прежде чем мы углубимся в следующую статью, давайте быстро обсудим другую тему, интересующую любого практикующего: когда какую функцию регулярного выражения использовать? Три наиболее важные функции регулярных выражений: re.match(), re.search() и re.findall(). Вы уже видели две из них, но давайте изучим их более подробно (на примере).

import re

text = '''
"One can never have enough socks", said Dumbledore.
"Another Christmas has come and gone and I didn’t
get a single pair. People will insist on giving me books."
Christmas Quote
'''

regex = 'Christ.*'

print(re.match(regex, text))
# None

print(re.search(regex, text))
# 

print(re.findall(regex, text))
# ['Christmas has come and gone and I didn’t', 'Christmas Quote']

Все три функции принимают в качестве входных данных регулярное выражение и строку для поиска. Функции match() и search() возвращают объект соответствия (или None, если регулярное выражение ничего не соответствует). Объект соответствия хранит положение соответствия и более сложную метаинформацию. Функция match() не находит регулярное выражение в строке (возвращает None). Почему? Потому что функция ищет шаблон только в начале строки. Функция search() ищет первое вхождение регулярного выражения в любом месте строки. Поэтому он находит совпадение «Christmas has come and gone and I didn’t».

Полагаю, вам больше всего нравится функция findall()? Вывод интуитивно понятен (но также менее полезен для дальнейшей обработки: например, объект сопоставления содержит интересную информацию о точном месте сопоставления). Результатом является не соответствующий объект, а последовательность строк. В отличие от функций match() и search(), функция findall() извлекает все сопоставленные шаблоны.

Скажем, ваша компания просит вас создать небольшого веб-бота, который сканирует веб-страницы и проверяет, содержат ли они ссылки на домен goole.com. Дополнительным требованием является то, что описания гиперссылок также должны содержать строки «test» или «puzzle». Точнее, цель состоит в том, чтобы решить следующую проблему: «По заданной строке найдите все гиперссылки, которые указывают на домен google.com и содержат строки «test» или «puzzle» в описании ссылки».

## Dependencies
import re

## Data
page = '''



My Programming Links

test your Python skill level Learn recursion Great books from NoStarchPress Solve more Python puzzles ''' ## One-Liner practice_tests = re.findall("()", page) ## Result print(practice_tests)

Код находит два вхождения регулярного выражения. Какие?

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

После открывающего тега сопоставляется произвольное количество символов (без жадности), за которым следует строка 'google'. Затем мы сопоставляем произвольное количество символов (жадных), за которым следует одно вхождение строки «test», либо «puzzle». Опять же, мы сопоставляем произвольное количество символов (жадно) с последующим закрывающим тегом. Таким образом, мы находим все теги гиперссылки, которые содержат соответствующие строки. Обратите внимание, что это регулярное выражение также совпадает с тегами, где в самой ссылке присутствуют строки «test» или «puzzle».

Результат:

## Result
print(practice_tests)
# [('test your Python skill level', 'test'),
#  ('Solve more Python puzzles', 'puzzle')]

Две гиперссылки соответствуют нашему регулярному выражению: результатом является список из двух элементов. Однако каждый элемент представляет собой набор строк, а не одну строку. Это отличается от результатов функции findall(), которые мы обсуждали в предыдущих фрагментах кода. В чем причина такого поведения? Тип возвращаемого значения представляет собой список кортежей - с одним значением кортежа для каждой соответствующей группы, заключенным в скобки (). Например, регулярное выражение '(test | puzzle)' использует обозначение в скобках для создания соответствующей группы. Теперь правило следующее: если вы используете соответствующие группы в своем регулярном выражении, функция re.findall() добавит одно значение кортежа для каждой соответствующей группы. Значением кортежа является подстрока, которая соответствует этой конкретной группе (а не строка, которая соответствует всему регулярному выражению, содержащему несколько совпадающих групп).

Источник:

#Python
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу