Зачем вам нужно учиться парсить веб-страницы? Если ваша работа не требует от вас изучения, позвольте мне дать вам некоторую мотивацию.
Что, если вы хотите создать веб-сайт, на котором будут продаваться самые дешевые товары с Amazon, Walmart и нескольких других интернет-магазинов? Многие из этих интернет-магазинов не предоставляют простой способ доступа к своей информации с помощью API. В отсутствие API ваш единственный выбор - создать парсер. Это позволяет автоматически извлекать информацию с этих веб-сайтов и упрощает использование этой информации.
Вот пример типичного ответа API в формате JSON. Это ответ Reddit:
Существует множество библиотек Python, которые могут помочь вам в парсинге веб-страниц. Есть lxml, BeautifulSoup и полноценный фреймворк под названием Scrapy.
В большинстве руководств обсуждаются BeautifulSoup и Scrapy, поэтому в этом посте я решил использовать lxml. Я научу вас основам XPath и тому, как вы можете использовать их для извлечения данных из HTML-документа. Я проведу вас через несколько разных примеров, чтобы вы могли быстро освоить работу с lxml и XPaths.
Получение данных
Если вы геймер, вы уже знаете (и, вероятно, полюбите) этот веб-сайт. Мы будем пытаться извлечь данные из Steam. В частности, мы будем выбирать из популярных новинок информацию.
Я превращаю этот процесс в серию из двух частей. В этой части мы будем создавать скрипт Python, который может извлекать названия игр, цены на игры, различные теги, связанные с каждой игрой и целевыми платформами. Во второй части мы превратим этот скрипт в API на основе Flask, а затем разместим его на Heroku.
Прежде всего, откройте страницу Популярные новые выпуски в Steam и прокрутите вниз, пока не увидите вкладку Популярные новые выпуски. На этом этапе я обычно открываю инструменты разработчика Chrome и смотрю, какие HTML-теги содержат необходимые данные. Я широко использую инструмент инспектора элементов (кнопка в верхнем левом углу инструментов разработчика). Он позволяет одним щелчком мыши увидеть разметку HTML за определенным элементом на странице.
Как общий обзор, все на веб-странице инкапсулируется в теги HTML, а теги обычно являются вложенными. Вам нужно выяснить, из каких тегов нужно извлекать данные, и все будет готово. В нашем случае, если мы посмотрим, мы увидим, что каждый отдельный элемент списка инкапсулирован в тег привязки (a
).
Сами теги привязки инкапсулируются в div
с идентификатором tab_newreleases_content
. Я упоминаю идентификатор, потому что на этой странице есть две вкладки. Вторая вкладка - это стандартная вкладка «Новые выпуски», и мы не хотим извлекать информацию из этой вкладки. Итак, сначала мы извлечем вкладку «Популярные новые выпуски», а затем извлечем необходимую информацию из этого тега.
Это идеальное время для создания нового файла Python и начала записи нашего скрипта. Я собираюсь создать scrape.py
файл. Теперь давайте продолжим и импортируем необходимые библиотеки. Первая - это библиотека requests
, вторая - это библиотека lxml.html
.
import requests import lxml.html
Если у вас не установлен requests
, вы можете легко установить его, выполнив следующую команду в терминале:
$ pip install requests
Библиотека запросов поможет нам открыть веб-страницу на Python. Мы могли бы использовать lxml
и для открытия HTML-страницы, но это не работает со всеми веб-страницами. На всякий случай я собираюсь использовать requests
.
Извлечение и обработка информации
Теперь давайте откроем веб-страницу с помощью запросов и передадим этот ответ lxml.html.fromstring
.
html = requests.get('https://store.steampowered.com/explore/new/') doc = lxml.html.fromstring(html.content)
Это дает нам объект типа HtmlElement
. Этот объект имеет xpath
метод, который мы можем использовать для запроса HTML-документа. Это дает нам структурированный способ извлечения информации из HTML-документа.
Теперь сохраните этот файл и откройте терминал. Скопируйте код из файла scrape.py
и вставьте его в сеанс интерпретатора Python.
Мы делаем это для того, чтобы мы могли быстро протестировать наши XPath без постоянного редактирования, сохранения и выполнения нашего scrape.py
файла.
Давайте попробуем написать XPath для извлечения div, который содержит вкладку «Популярные новые выпуски». Я объясню код по ходу дела:
new_releases = doc.xpath('//div[@id="tab_newreleases_content"]')[0]
Этот оператор вернет список всех divs
на HTML-странице с идентификатором tab_newreleases_content
. Теперь, поскольку мы знаем, что только один div на странице имеет этот идентификатор, мы можем извлечь первый элемент из списка ([0]
), и это будет наш необходимый div
. Давайте разберем xpath
и попробуем понять его:
//
эта двойная косая черта указываетlxml
, что мы хотим искать все теги в документе HTML, которые соответствуют нашим требованиям / фильтрам. Другой вариант - использовать/
(одинарная косая черта). Одиночная косая черта возвращает только непосредственные дочерние теги / узлы, которые соответствуют нашим требованиям / фильтрам.div
сообщаетlxml
, что мы ищемdivs
на странице HTML.[@id="tab_newreleases_content"]
сообщаетlxml
, что нас интересуют только теdivs
, которые имеют идентификаторtab_newreleases_content
Прохладный! У нас есть необходимый div
. Теперь вернемся в Chrome и проверим, какой тег содержит названия релизов.
Заголовок содержится в div с классом tab_item_name
. Теперь, когда у нас выделена вкладка «Популярные новые версии», мы можем выполнять дальнейшие запросы XPath на этой вкладке. Запишите следующий код в той же консоли Python, в которой мы ранее запускали наш код:
titles = new_releases.xpath('.//div[@class="tab_item_name"]/text()')
Это дает нам названия всех игр на вкладке «Популярные новые выпуски». Вот ожидаемый результат:
Давайте немного разберем этот XPath, потому что он немного отличается от предыдущего.
.
сообщает lxml, что нас интересуют только теги, которые являются дочерними для тегаnew_releases
[@class="tab_item_name"]
очень похож на то, как мы отфильтровывалиdivs
на основеid
. Единственная разница в том, что здесь мы выполняем фильтрацию по имени класса./text()
сообщает lxml, что мы хотим, чтобы текст содержался в только что извлеченном теге. В этом случае он возвращает заголовок, содержащийся в div с именем классаtab_item_name
.
Теперь нам нужно извлечь цены на игры. Это легко сделать, запустив следующий код:
prices = new_releases.xpath('.//div[@class="discount_final_price"]/text()')
Я не думаю, что мне нужно объяснять этот код, поскольку он очень похож на код извлечения заголовка. Единственное изменение, которое мы внесли, - это изменение имени класса.
Теперь нам нужно извлечь теги, связанные с заголовками. Вот разметка HTML:
Запишите следующий код в терминале Python для извлечения тегов:
tags = new_releases.xpath('.//div[@class="tab_item_top_tags"]')
total_tags = []
for tag in tags:
total_tags.append(tag.text_content())
Итак, мы извлекаем divs
, содержащий теги для игр. Затем мы перебираем список извлеченных тегов, а затем извлекаем текст из этих тегов с помощью метода text_content()
. text_content()
возвращает текст, содержащийся в теге HTML, без разметки HTML.
Примечание. Мы также могли бы использовать составление списков, чтобы сделать этот код короче. Я записал это таким образом, чтобы даже те, кто не разбирается в составлении списков, могли понять код. В любом случае это альтернативный код:
tags = [tag.text_content() for tag in new_releases.xpath('.//div[@class="tab_item_top_tags"]')]
Давайте также разделим теги в списке, чтобы каждый тег был отдельным элементом:
tags = [tag.split(', ') for tag in tags]
Теперь осталось только извлечь платформы, связанные с каждым тайтлом. Вот разметка HTML:
Основное отличие здесь в том, что платформы не содержатся в виде текстов в определенном теге. Они указаны как имя класса. С некоторыми играми связана только одна платформа, например:
<span class="platform_img win"></span>
Хотя с некоторыми играми связано 5 платформ, например:
<span class="platform_img win"></span>
<span class="platform_img mac"></span>
<span class="platform_img linux"></span>
<span class="platform_img hmd_separator"></span>
<span title="HTC Vive" class="platform_img htcvive"></span>
<span title="Oculus Rift" class="platform_img oculusrift"></span>
Как мы видим, эти spans
содержат тип платформы в качестве имени класса. Единственное, что объединяет эти spans
, - это то, что все они содержат класс platform_img
. Прежде всего, мы извлечем divs
с классом tab_item_details
. Затем мы извлечем spans
, содержащий класс platform_img
. Наконец, мы извлечем имя второго класса из этих spans
. Вот код:
platforms_div = new_releases.xpath('.//div[@class="tab_item_details"]')
total_platforms = []
for game in platforms_div:
temp = game.xpath('.//span[contains(@class, "platform_img")]')
platforms = [t.get('class').split(' ')[-1] for t in temp]
if 'hmd_separator' in platforms:
platforms.remove('hmd_separator')
total_platforms.append(platforms)
В строке 1 мы начинаем с извлечения файла tab_item_details
div
. XPath в строке 5 немного отличается. Здесь у нас [contains(@class, "platform_img")]
вместо простого [@class="platform_img"]
. Причина в том, что [@class="platform_img"]
возвращает те spans
, с которыми связан только класс platform_img
. Если у spans
есть дополнительный класс, они не будут возвращены. В то время как [contains(@class, "platform_img")]
фильтрует все spans
, которые имеют класс platform_img
. Не имеет значения, единственный ли это класс или с этим тегом связано больше классов.
В строке 6 мы используем анализ списка, чтобы уменьшить размер кода. Метод .get()
позволяет нам извлекать атрибут тега. Здесь мы используем его для извлечения атрибута class
из span
. Получаем строку обратно из метода .get()
. В случае первой игры возвращается строка platform_img win
, поэтому мы разбиваем эту строку на основе запятой и пробела. Затем мы сохраняем последнюю часть (которое является фактическим названием платформы) разделенной строки в списке.
В строках 7–8 мы удаляем hmd_separator
из списка, если он существует. Это потому, что hmd_separator
не является платформой. Это просто вертикальная разделительная полоса, используемая для отделения реальных платформ от оборудования VR / AR.
Вот код, который у нас есть на данный момент:
import requests
import lxml.html
html = requests.get('https://store.steampowered.com/explore/new/')
doc = lxml.html.fromstring(html.content)
new_releases = doc.xpath('//div[@id="tab_newreleases_content"]')[0]
titles = new_releases.xpath('.//div[@class="tab_item_name"]/text()')
prices = new_releases.xpath('.//div[@class="discount_final_price"]/text()')
tags = [tag.text_content() for tag in new_releases.xpath('.//div[@class="tab_item_top_tags"]')]
tags = [tag.split(', ') for tag in tags]
platforms_div = new_releases.xpath('.//div[@class="tab_item_details"]')
total_platforms = []
for game in platforms_div:
temp = game.xpath('.//span[contains(@class, "platform_img")]')
platforms = [t.get('class').split(' ')[-1] for t in temp]
if 'hmd_separator' in platforms:
platforms.remove('hmd_separator')
total_platforms.append(platforms)
Теперь нам просто нужно вернуть ответ JSON, чтобы мы могли легко превратить его в API на основе Flask. Вот код:
output = []
for info in zip(titles,prices, tags, total_platforms):
resp = {}
resp['title'] = info[0]
resp['price'] = info[1]
resp['tags'] = info[2]
resp['platforms'] = info[3]
output.append(resp)
Этот код не требует пояснений. Мы используем функцию zip
для параллельного перебора всех этих списков. Затем мы создаем словарь для каждой игры и назначаем название, цену, теги и платформы в качестве отдельного ключа в этом словаре. Наконец, мы добавляем этот словарь в список вывода.
Подведение итогов
В следующем посте мы рассмотрим, как мы можем преобразовать это в API на основе Flask и разместить его на Heroku.
Я Ясуб из Python Tips. Надеюсь, вам понравился этот урок. Если вы хотите прочитать больше руководств аналогичного характера, перейдите в Советы по Python. Я регулярно пишу в этом блоге советы, рекомендации и учебные пособия по Python. А если вы заинтересованы в изучении Python среднего уровня, то, пожалуйста, ознакомьтесь с моей книгой с открытым исходным кодом здесь.
Просто оговорка: мы - лесозаготовительная компания here @ Timber. Мы были бы рады, если бы вы опробовали наш продукт (это действительно здорово!), Но вы здесь, чтобы узнать о парсинге веб-страниц на Python, и мы не хотели отказываться от этого.
Изначально опубликовано на timber.io.