Шаг 76.
Однострочники Python. Регулярные выражения. Создание первого веб-скрапера с помощью регулярных выражений

    На этом шаге мы рассмотрим создание однострочника, решающего указанную задачу.

    Допустим, вы разработчик-фрилансер. Вашему заказчику - финансовотехнологическому стартапу - постоянно нужны последние новости в сфере криптовалют. Они наняли вас для написания веб-скрапера, который бы регулярно извлекал исходный 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, а затем обсудим, как этот код решает поставленную задачу.


Пример 5.2. Однострочное решение для поиска фрагментов текста вида crypto(какой-то текст)coin
## Зависимости
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-документов.




Предыдущий шаг Содержание Следующий шаг