1. Code
  2. Python

Современный веб-скрапинг с BeautifulSoup и Selenium

HTML почти интуитивно понятен. CSS - это большое продвижение, которое чисто отделяет структуру страницы от ее внешнего вида. JavaScript добавляет некоторые pizazz. Это теория. Реальный мир немного отличается.
Scroll to top

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Обзор

HTML почти интуитивно понятен. CSS - это большое продвижение, которое чисто отделяет структуру страницы от ее внешнего вида. JavaScript добавляет некоторые pizazz. Это теория. Реальный мир немного отличается.

В этом уроке вы узнаете, как нарисованный вами контент в браузере будет отображаться и как его парсить, когда это необходимо. В частности, вы узнаете, как считать комментарии Disqus. Наши инструменты будут Python и удивительные пакеты, такие как requests, BeautifulSoup и Selenium.

Когда следует использовать веб-скрапера?

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

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

Понимание реальных веб-страниц

Давайте поймем, с чем мы сталкиваемся, посмотрев на вывод какого-то общего кода веб-приложения. В статье «Введение в Vagrant» есть некоторые комментарии Disqus в нижней части страницы:

Understanding Real-World Web PagesUnderstanding Real-World Web PagesUnderstanding Real-World Web Pages

Чтобы получить эти комментарии, нам нужно сначала найти их на странице.

Просмотр источника страницы

Каждый браузер с самого начала (1990-е годы) поддерживал возможность просмотра HTML текущей страницы. Вот фрагмент из источника представления «Введение в Vagrant», который начинается с огромного фрагмента миниатюрного и укрреченного JavaScript, не связанного с самой статьей. Вот небольшая его часть:

Page SourcePage SourcePage Source

Вот некоторые фактические HTML со страницы:

HTML From the PageHTML From the PageHTML From the Page

Это выглядит довольно грязно, но удивительно то, что вы не найдете комментарии Disqus в источнике страницы.

Inline Frame

Оказывается, что страница представляет собой mashup, а комментарии Disqus встроены как элемент iframe (inline frame). Вы можете найти его, щелкнув правой кнопкой мыши по области комментариев, и вы увидите, что там есть информация о фреймах и источник:

The Mighty Inline FrameThe Mighty Inline FrameThe Mighty Inline Frame

В этом есть смысл. Встраивание стороннего контента в качестве iframe является одной из основных причин использования iframe. Давайте найдем тег <iframe>, затем в источнике главной страницы. Опровергнуто снова! В источнике главной страницы нет тега <iframe>.

Разметка с использованием JavaScript

Причиной этого упущения является то, что view page source показывает вам контент, который был получен с сервера. Но итоговый DOM (объектная модель документа), получаемый браузером, может быть совсем другой. JavaScript запускается и может манипулировать DOM по своему усмотрению. Невозможно найти iframe, потому что его не было, когда страница была получена с сервера.

Статический скрапинг против динамического

Static scraping игнорирует JavaScript. Он извлекает веб-страницы с сервера без помощи браузера. Вы получаете именно то, что видите в «источнике страницы», а затем вы разрезаете и парсите его. Если контент, который вы ищете, доступен, вам не нужно идти дальше. Однако, если контент является чем-то вроде комментариев iframe от Disqus, вам нужен динамический скрапинг.

Динамический скрапинг использует фактический браузер (или headless браузер) и позволяет JavaScript делать свое дело. Затем он запрашивает DOM для извлечения содержимого, которое он ищет. Иногда вам необходимо автоматизировать браузер, моделируя пользователя, чтобы получить необходимый контент.

Статические скраперы с запросами и BeautifulSoup

Посмотрим, как работает статический скрапинг с использованием двух удивительных пакетов Python: requests на выборку веб-страниц и BeautifulSoup для анализа HTML-страниц.

Установка Requests и BeautifulSoup

Сначала установите pipenv, а затем: pipenv install requests beautifulsoup4

Это также создаст для вас виртуальную среду. Если вы используете код из gitlab, вы можете просто выполнить pipenv install.

Получение страниц

Получение страницы с запросами - это одна строка r = requests.get(url)

Объект ответа имеет множество атрибутов. Самые важные из них ok и content. Если запрос терпит неудачу, то r.ok будет False, а r.content будет содержать ошибку. Содержимое представляет собой поток байтов. Обычно с текстом лучше декодировать его до utf-8 при работе:

1
>>> r = requests.get('http://www.c2.com/no-such-page')
2
>>> r.ok
3
False
4
>>> print(r.content.decode('utf-8'))
5
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
6
<html><head>
7
<title>404 Not Found</title>
8
</head><body>
9
<h1>Not Found</h1>
10
<p>The requested URL /ggg was not found on this server.</p>
11
<hr>
12
<address>
13
Apache/2.0.52 (CentOS) Server at www.c2.com Port 80
14
</address>
15
</body></html>

Если все в порядке, то r.content будет содержать запрошенную веб-страницу (такую же, как источник страницы).

Поиск элементов с помощью BeautifulSoup

Функция get_page() ниже извлекает веб-страницу по URL-адресам, расшифровывает ее в UTF-8 и анализирует ее в объект BeautifulSoup с помощью парсера HTML.

1
def get_page(url):
2
    r = requests.get(url)
3
    content = r.content.decode('utf-8')
4
    return BeautifulSoup(content, 'html.parser')

Когда у нас есть объект BeautifulSoup, мы можем начать извлекать информацию со страницы. BeautifulSoup предоставляет множество функций поиска, чтобы находить элементы внутри страницы и разворачивать глубокие вложенные элементы.

Страницы Tuts + author содержат несколько руководств. Вот моя страница автора. На каждой странице есть до 12 учебников. Если у вас более 12 руководств, вы можете перейти к следующей странице. HTML для каждой статьи заключен в тег <article>. Следующая функция находит все элементы статьи на странице, проходится по их ссылкам и извлекает атрибут href для получения URL-адреса учебника:

1
def get_page_articles(page):
2
    elements = page.findAll('article')
3
    articles = [e.a.attrs['href'] for e in elements]
4
    return articles

Следующий код получает все статьи с моей страницы и печатает их (без общего префикса):

1
page = get_page('https://tutsplus.com/authors/gigi-sayfan')
2
articles = get_page_articles(page)
3
prefix = 'https://code.tutsplus.com/tutorials'
4
for a in articles:
5
    print(a[len(prefix):])
6
  
7
Output:
8
9
building-games-with-python-3-and-pygame-part-5--cms-30085
10
building-games-with-python-3-and-pygame-part-4--cms-30084
11
building-games-with-python-3-and-pygame-part-3--cms-30083
12
building-games-with-python-3-and-pygame-part-2--cms-30082
13
building-games-with-python-3-and-pygame-part-1--cms-30081
14
mastering-the-react-lifecycle-methods--cms-29849
15
testing-data-intensive-code-with-go-part-5--cms-29852
16
testing-data-intensive-code-with-go-part-4--cms-29851
17
testing-data-intensive-code-with-go-part-3--cms-29850
18
testing-data-intensive-code-with-go-part-2--cms-29848
19
testing-data-intensive-code-with-go-part-1--cms-29847
20
make-your-go-programs-lightning-fast-with-profiling--cms-29809

Динамический скрапинг с Selenium

Статический скрапинг был достаточно хорош, чтобы получить список статей, но, как мы видели ранее, комментарии Disqus встроены в JavaScript как элемент iframe. Чтобы собрать комментарии, нам нужно будет автоматизировать браузер и взаимодействовать с DOM в интерактивном режиме. Одним из лучших инструментов для работы является Selenium.

Selenium в первую очередь ориентирован на автоматическое тестирование веб-приложений, но он отлично подходит для универсального средства автоматизации браузера.

Установка Selenium

Введите эту команду для установки Selenium: pipenv install selenium

Выберите свой веб-драйвер

Selenium нужен веб-драйвер (он автоматизирует браузер). Для веб-скрапинга обычно не имеет значения, какой драйвер вы выберете. Я предпочитаю драйвер Chrome. Следуйте инструкциям в этом руководстве Selenium.

Chrome против PhantomJS

В некоторых случаях вы можете использовать headless браузер, что означает отсутствие отображения пользовательского интерфейса. Теоретически, PhantomJS - это еще один веб-драйвер. Но на практике люди сообщали о проблемах несовместимости, когда Selenium работает правильно с Chrome или Firefox, а иногда и с PhantomJS. Я предпочитаю удалить эту переменную из уравнения и использовать фактический драйвер браузера.

Подсчет комментариев Disqus

Давайте сделаем некоторый динамический скрапинг и используем Selenium для подсчета комментариев Disqus в Tuts + tutorials. Вот необходимые импорты.

1
from selenium import webdriver
2
from selenium.webdriver.common.by import By
3
from selenium.webdriver.support.expected_conditions import (
4
    presence_of_element_located)
5
from selenium.webdriver.support.wait import WebDriverWait

Функция get_comment_count() принимает драйвер и URL-адрес. Он использует метод get() драйвера для извлечения URL-адреса. Это похоже на request.get(), но разница в том, что объект-драйвер управляет живым представлением DOM.

Затем он получает название учебника и находит iframe Disqus, используя его родительский id disqus_thread, а затем сам iframe:

1
def get_comment_count(driver, url):
2
    driver.get(url)
3
    class_name = 'content-banner__title'
4
    name = driver.find_element_by_class_name(class_name).text
5
    e = driver.find_element_by_id('disqus_thread')
6
    disqus_iframe = e.find_element_by_tag_name('iframe')
7
    iframe_url = disqus_iframe.get_attribute('src')

Следующим шагом будет выбор содержимого самого iframe. Обратите внимание, что мы ожидаем присутствия элемента comment-count, потому что комментарии загружаются динамически и необязательно будут сразу доступны.

1
    driver.get(iframe_url)
2
    wait = WebDriverWait(driver, 5)
3
    commentCountPresent = presence_of_element_located(
4
        (By.CLASS_NAME, 'comment-count'))
5
    wait.until(commentCountPresent)
6
7
    comment_count_span = driver.find_element_by_class_name(
8
        'comment-count')
9
    comment_count = int(comment_count_span.text.split()[0])

Последняя часть должна вернуть последний комментарий, если он не был сделан мной. Идея состоит в том, чтобы обнаружить комментарии, на которые я еще не ответил.

1
    last_comment = {}
2
    if comment_count > 0:
3
        e = driver.find_elements_by_class_name('author')[-1]
4
        last_author = e.find_element_by_tag_name('a')
5
        last_author = e.get_attribute('data-username')
6
        if last_author != 'the_gigi':
7
            e = driver.find_elements_by_class_name('post-meta')
8
            meta = e[-1].find_element_by_tag_name('a')
9
            last_comment = dict(
10
            	author=last_author,
11
                title=meta.get_attribute('title'),
12
                when=meta.text)
13
    return name, comment_count, last_comment

Заключение

Веб-скрапинг - полезная практика, когда доступная вам информация доступна через веб-приложение, которое не предоставляет соответствующий API. Для извлечения данных из современных веб-приложений требуется некоторая нетривиальная работа, но зрелые и хорошо продуманные инструменты, такие как requests, BeautifulSoup и Selenium решают эту задачу.

Кроме того, не стесняйтесь посмотреть, что у нас есть для продажи и для изучения на Envato Market, и не стесняйтесь задавать любые вопросы и предоставлять свою ценную обратную связь, используя приведенный ниже канал.