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 в нижней части страницы:



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



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



Это выглядит довольно грязно, но удивительно то, что вы не найдете комментарии Disqus в источнике страницы.
Inline Frame
Оказывается, что страница представляет собой mashup, а комментарии Disqus встроены как элемент iframe (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, и не стесняйтесь задавать любые вопросы и предоставлять свою ценную обратную связь, используя приведенный ниже канал.



