Статьи

Современная веб-очистка с BeautifulSoup и Selenium

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

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

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

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

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

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

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

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

Исходный текст страницы

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

HTML со страницы

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

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

Могучий встроенный каркас

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

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

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

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

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

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

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

Извлечение страницы с запросами — это одна строка: r = requests.get(url)

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
>>> r = requests.get(‘http://www.c2.com/no-such-page’)
>>> r.ok
False
>>> print(r.content.decode(‘utf-8’))
<!DOCTYPE HTML PUBLIC «-//IETF//DTD HTML 2.0//EN»>
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /ggg was not found on this server.</p>
<hr>
<address>
Apache/2.0.52 (CentOS) Server at www.c2.com Port 80
</address>
</body></html>

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

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

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

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

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

1
2
3
4
def get_page_articles(page):
   elements = page.findAll(‘article’)
   articles = [eaattrs[‘href’] for e in elements]
   return articles

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
last_comment = {}
   if comment_count > 0:
       e = driver.find_elements_by_class_name(‘author’)[-1]
       last_author = e.find_element_by_tag_name(‘a’)
       last_author = e.get_attribute(‘data-username’)
       if last_author != ‘the_gigi’:
           e = driver.find_elements_by_class_name(‘post-meta’)
           meta = e[-1].find_element_by_tag_name(‘a’)
           last_comment = dict(
               author=last_author,
               title=meta.get_attribute(‘title’),
               when=meta.text)
   return name, comment_count, last_comment

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

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