Beautiful Soup: веб-краулинг на Python

Beautiful Soup (буквально — «прекрасный суп») — это библиотека Python для извлечения данных из HTML и XML. То есть, библиотека для веб-скрапинга.

Давайте разберем задание для технического собеседования.

 # Путем веб-краулинга исследуйте веб-страницу и выведите слово,
 # которое встречается на ней чаще всего, а также
 # количество вхождений этого слова.

 # Страница для краулинга:
 # https://en.wikipedia.org/wiki/Apple_Inc.

 # Учитывайте только слова из раздела «History».

Итак, дана страница Apple в Википедии. Нам нужно найти слово, которое чаще всего встречается в разделе «History», а также посчитать, сколько раз оно там встречается. Приступим.

1. Первоначальная подготовка

Для начала нам нужно импортировать Beautiful Soup. Установим библиотеку через командную строку:

 pip3 install bs4

Если у вас возникли проблемы с установкой, обратитесь к документации.

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

from bs4 import BeautifulSoup, Tag
import requests

from collections import defaultdict

Теперь мы готовы определить нашу функцию.

def find_most_common():

2. Извлечение страницы

Давайте извлечем нашу страницу и распарсим ее при помощи Beautiful Soup. Для этого мы воспользуемся библиотекой requests:

page = requests.get("https://en.wikipedia.org/wiki/Apple_Inc.")

Теперь прогоним полученный документ через Beautiful Soup.

soup = BeautifulSoup(page.text, "html.parser")

Как нам извлечь только раздел «History»? Нужно изучить HTML-код страницы. Там много всякой тарабарщины, но если подчистить, он выглядит так:

<h2>
    <span class="mw-headline" id="History">History</span>
</h2>
<div ...>...</div>
<div ...>...</div>
<h3>
    <span ...></span>
    <span class="mw-headline" id="1976–1984:_Founding_and_incorporation">1976–1984: Founding and incorporation</span>
</h3>
.
.
.
<p>Apple Computer Company was founded on April 1, 1976, by <a href="/wiki/Steve_Jobs" title="Steve Jobs">Steve Jobs</a>...

На сайте Википедии, судя по всему, все содержимое страницы помещено в один блок div. Это значит, что раздел «History» не лежит в собственном div. Это всего лишь заголовок и какое-то содержимое внутри родительского div, в котором содержатся все разделы.

Лучшее, что мы можем сделать, чтобы извлечь только исторический раздел, это просто захватить заголовок и все, что идет после него. Мы захватываем тег <span> с ID «History», а затем идем к его родителю — <h2>. Чтобы захватить все идущее за заголовком, мы можем использовать нотацию Beautiful Soup: next_siblings. Все вместе:

history = soup.find(id="History").parent.next_siblings

3. Подсчет слов

Давайте инициализируем пару переменных. Нам нужна переменная для искомого слова и для количества его вхождений. Еще нам нужен словарь для подсчета всех слов. Что касается словаря, мы используем дефолтный словарь — defaultdict. Он позволяет задать целые числа в качестве типа по умолчанию. Благодаря этому при запросе несуществующего ключа будет создана пара из этого ключа и дефолтного значения — нуля. (Пример использования defaultdict можно посмотреть в статье «Определяем, все ли символы в строке уникальны. Разбор задачи», — прим. ред. Pythonist.ru).

max_count = 0
max_word = ""
dd = defaultdict(int)

Теперь мы готовы краулить. Давайте переберем в цикле history, проверяя каждый элемент — elem. Бывает, что Beautiful Soup возвращает вместо элемента то, что называется Navigable String. Мы отфильтруем все, что не является элементом, при помощи метода isinstance() из нашей библиотеки.

for elem in history:
    if isinstance(elem, Tag):

Давайте подумаем, что будет происходить дальше. Для каждого элемента в history нам нужно просмотреть текст и посчитать вхождения каждого слова. Но не забудьте, что нам нужно остановиться, когда раздел истории закончится. Следующий раздел находится в том же блоке div, но начинается с тега <h2>. В конце мы можем вывести слово, встречающееся чаще всего, и количество его вхождений. Возвращать будем max_count.

for elem in history:
    if isinstance(elem, Tag):
        if elem.name == "h2":
            print(max_word, "is the most common, appearing", max_count, "times.")
            return max_count

А что, если мы еще не дошли до конца раздела? Нам нужно извлечь текст из элемента, вызвав метод get_text() Beautiful Soup, а затем разбить его на слова (по пробелам) при помощи метода split().

words = elem.get_text().split()

Что дальше? Переберем в цикле каждое слово, обновляя его значение в словаре. Поскольку мы используем defaultdict, нам не нужно проверять, есть ли уже такое слово в словаре: мы можем просто добавлять единицу к значению этого слова. Но нужно не забывать обновлять max_word и max_count, если находим слово, встречающееся чаще, чем предыдущее.

for word in words:
    dd[word] += 1
    if dd[word] > max_count:
        max_count = dd[word]
        max_word = word 

Вот и все! Код должен работать… если только Википедия не сменит макет своего сайта. Давайте-ка добавим итоговую проверку на случай, если это все же произойдет. Все вместе выглядит следующим образом:

from bs4 import BeautifulSoup, Tag
import requests

from collections import defaultdict

def find_most_common():
    page = requests.get("https://en.wikipedia.org/wiki/Apple_Inc.")
    soup = BeautifulSoup(page.text, "html.parser")
    history = soup.find(id="History").parent.next_siblings
    max_count = 0
    max_word = ""
    dd = defaultdict(int)

    for elem in history:
        if isinstance(elem, Tag):
            if elem.name == "h2":
                print(max_word, "is the most common, appearing", max_count, "times.")
                return max_count
            words = elem.get_text().split()
            for word in words:
                dd[word] += 1
                if dd[word] > max_count:
                    max_count = dd[word]
                    max_word = word

    return "Error"

Проверяем

Эта функция выводит результат на экран, так что мы можем просто запустить ее при помощи find_most_common(). Мы получим следующее:

 the is the most common, appearing 328 times.

Это оно! Конечно, эта функция работает только для конкретной страницы (и на момент написания статьи). Основная проблема веб-краулинга в том, что если собственник сайта хоть немного изменит свой контент, функция может сломаться. Также мы не учитывали здесь регистры и пунктуацию: вы можете реализовать это самостоятельно.

Прокрутить вверх