На этом шаге мы рассмотрим создание однострочника, решающего указанную задачу.
Допустим, вы разработчик-фрилансер. Вашему заказчику - финансовотехнологическому стартапу - постоянно нужны последние новости в сфере криптовалют. Они наняли вас для написания веб-скрапера, который бы регулярно извлекал исходный 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() для поиска позиции первого вхождения искомой строки. С помощью среза мы извлекаем подстроку, содержащую непосредственное окружение искомого места. В результате получаем следующее строковое значение:
# ,r=window.crypto || wi
Ой, выглядит не очень. Как оказалось, поисковая фраза двусмысленна - большинство слов, содержащих 'crypto', с криптовалютами никак не связаны. Наш веб-скрапер генерирует ложноположительные результаты (находит строковые значения, которые мы вовсе не хотели находить). Как же исправить эту ситуацию?
К счастью, ответ очевиден: регулярные выражения! Возникает идея: исключить ложноположительные результаты за счет поиска только тех вхождений, в которых за словом 'crypto' следует до 30 произвольных символов, за которыми следует слово 'coin'. Грубо говоря, поисковый запрос выглядит так: crypto + <до 30 произвольных символов> + coin. Рассмотрим следующие два примера:
Итак, проблема состоит в том, что регулярное выражение должно допускать до 30 произвольных символов между двумя строками символов. Как решить эту выходящую за пределы простого поиска строк задачу? Перебрать все комбинации символов не получится - их количество практически бесконечно. Например, нашему поисковому шаблону должны соответствовать все следующие строковые значения: 'cryptoxxxcoin', 'crypto coin', 'crypto bitcoin', 'crypto is a currency. Bitcoin', а также остальные сочетания до 30 символов между двумя строками. Даже если в алфавите всего 26 символов, количество теоретически удовлетворяющих нашему требованию строк символов превышает 2630 = 2 813 198 901 284 745 919 258 621 029 615 971 520 741 376.
Далее мы расскажем вам, как искать в тексте задаваемый регулярным выражением шаблон, которому соответствует большое количество возможных комбинаций символов.
В этом коде мы ищем в заданном строковом значении вхождения, в которых за строкой символов 'crypto' следует до 30 произвольных символов, за которыми следует слово 'coin'. Посмотрим сначала на пример 5.2, а затем обсудим, как этот код решает поставленную задачу.
## Зависимости import re ## Данные text_1 = "crypto-bot that is trading Bitcoin and other currencies" text_2 = ("cryptographic encryption methods that can be cracked easily " "with quantum computers") ## Однострочник pattern = re.compile("crypto(.{1,30})coin") ## Результат print(pattern.match(text_1)) print(pattern.match(text_2))
Данный код производит поиск в двух строковых переменных, text_1 и text_2. Соответствуют ли они поисковому запросу (шаблону)?
Во-первых, мы импортируем стандартный модуль для работы с регулярными выражениями в Python, re. Самое интересное происходит в однострочнике, где компилируется поисковый запрос crypto(.{1,30})coin. С помощью этого запроса мы и будем производить поиск в различных строковых значениях. В нем используются специальные символы регулярных выражений. Прочитайте их список внимательно, от начала до конца, и вы поймете смысл шаблона из примера 5.2:
Мы упомянули, что шаблон скомпилирован, поскольку Python создает объект шаблона, который можно повторно применять в разных местах - подобно тому, как скомпилированную программу можно использовать многократно. Теперь можно вызвать функцию match() нашего скомпилированного шаблона, и будет произведен поиск по тексту. В результате получим следующее:
## Результат print(pattern.match(text_1)) # <re.Match object; span=(0, 34), match='crypto-bot that is trading Bitcoin'> print(pattern.match(text_2)) # None
Строковая переменная text_1 соответствует шаблону (что видно из полученного объекта Match), а text_2 - нет (что видно из результата None). И хотя текстовое представление первого объекта выглядит не слишком изящно, но ясно указывает, что заданная строка 'crypto-bot that is trading Bitcoin' соответствует регулярному выражению.
На следующем шаге мы рассмотрим анализ гиперссылок HTML-документов.