Введение в веб-скрейпинг с помощью lxml и Python

1656595700 vvedenie v veb skrejping s pomoshhyu lxml i python

от Timber.io

7CyqSROgBxukbETQ2rfx4ysEs2V0NSjwngok
Фото Фабиана Гроса на Unsplash

Почему вам стоит учиться веб-скрейпингу? Если ваша работа не требует от вас ее учиться, позвольте мне немного мотивировать вас.

Что делать, если вы хотите создать веб-сайт, на котором будут представлены самые дешевые продукты от Amazon, Walmart и нескольких других интернет-магазинов? Многие из этих интернет-магазинов не дают вам простого способа получить доступ к их информации с помощью API. При отсутствии API, ваш единственный выбор – создать веб-скрейпер. Это позволяет автоматически получать информацию из этих веб-сайтов и делает эту информацию легкой в ​​использовании.

Вот пример типичного ответа API в JSON. Вот ответ от Reddit:

3AAHh7eokTALm4tw82sQT-Kl3zzeqZlo6CYy

Существует множество библиотек Python, которые могут помочь вам в веб-скрейпинге. Есть lxml, BeautifulSoup и полноценный фреймворк под названием Scrapy.

Большинство учебников обсуждают BeautifulSoup и Scrapy, поэтому я решил использовать lxml в этой публикации. Я научу вас основам XPaths и тому, как вы можете использовать их для извлечения данных из HTML-документа. Я проведу вас через несколько разных примеров, чтобы вы могли быстро настроиться на скорость с помощью lxml и XPaths.

Получение данных

Если вы геймер, вы уже знаете (и, вероятно, любите) этот веб-сайт. Мы попытаемся получить данные из Steam. Вернее, мы будем выбирать информацию из «популярных новых выпусков».

Я превращаю этот процесс в серию из двух частей. В этой части мы создадим скрипт Python, который сможет вытаскивать названия игр, цены на игры, разные теги, связанные с каждой игрой и целевыми платформами. Во второй части мы превратим этот скрипт в API на основе Flask, а затем разместим его на Heroku.

o-MbyodJMiNMhSvwE7HLEBpgDx2pbx0Fmekd

Прежде всего, откройте страницу «Новые популярные выпуски» в Steam и прокрутите вниз, пока не увидите вкладку «Популярные новые релизы». На этом этапе я обычно открываю инструменты разработчика Chrome и смотрю, какие HTML-теги содержат необходимые данные. Я активно использую инструмент инспектора элементов (кнопка в левом верхнем углу инструментов разработчика). Это позволяет только одним щелчком мыши увидеть HTML-разметку по определенному элементу на странице.

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

UB6gYciEusxfGnCix3bcaXkHTCHKkyr-J5KY

Сами теги привязки инкапсулированы в div с идентификатором tab_newreleases_content. Я упоминаю идентификатор, поскольку на этой странице есть две вкладки. Вторая вкладка – это стандартная вкладка «Новые выпуски», и мы не хотим извлекать информацию из этой вкладки. Поэтому сначала вытащим вкладку «Новые популярные релизы», а затем вытащим из этого тега необходимую информацию.

Это идеальное время, чтобы создать новый Python файл и начать записывать наш скрипт. Я собираюсь создать a scrape.py файл. Теперь давайте продолжим импортировать необходимые библиотеки. Первый из них requests библиотека, а вторая – это lxml.html библиотека.

import requests import lxml.html

Если у вас нет requests установлен, вы можете легко установить его, выполнив эту команду в терминале:

$ pip install requests

Библиотека запросов поможет нам открыть веб-страницу на Python. Мы могли бы использовать lxml чтобы открыть страницу HTML, но это не работает хорошо со всеми веб-страницами. Чтобы быть в безопасности, я собираюсь использовать requests.

Удаление и обработка информации

Теперь давайте откроем веб-страницу с помощью запросов и передаем ответ lxml.html.fromstring.

html = requests.get(' doc = lxml.html.fromstring(html.content)

Это дает нам объект HtmlElement типа. Этот объект имеет xpath метод, который можно использовать для запроса HTML-документа. Это дает нам структурированный способ извлечения информации из HTML-документа.

Теперь сохраните этот файл и откройте терминал. Скопируйте код с scrape.py файл и вставьте его в сеанс интерпретатора Python.

StkkSYH4Gr4mbmXuPj8mtkDQtuIpT8nA4WmO

Мы делаем это, чтобы мы могли быстро протестировать наши 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 и проверим, какой тэг содержит названия выпусков.

L9fPrMkTIwhdbwklG9-0X9gZl3e0ko3OuuBF

Заголовок содержится в div с классом tab_item_name. Теперь, когда мы извлекли вкладку Новые популярные релизы, мы можем выполнять дополнительные запросы XPath на этой вкладке. Запишите следующий код на той же консоли Python, на которой мы раньше запускали наш код:

titles = new_releases.xpath('.//div[@class="tab_item_name"]/text()')

Это дает нам названия всех игр на вкладке Популярные новые релизы. Вот ожидаемый результат:

Xhf8xeRmP1HcnQylCKcRICVcP3wS49jhKyHt

Давайте немного разберем этот 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()')

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

2fnisL0yYetblYk1s8JTw8KMVDq5IyiRBITW

Теперь нам нужно извлечь теги, связанные с заголовками. Вот HTML-разметка:

Z88XEIzl3gg9Y94h2j9hNiQviV8N22ttfQnL

Запишите следующий код в терминале 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"]')]
hcRXlOSBvpuEiek51XbZeBtrqtN2ubKimKmt

Давайте также разделим теги в списке, чтобы каждый тэг был отдельным элементом:

tags = [tag.split(', ') for tag in tags]

Теперь остается только извлечь платформы, связанные с каждым названием. Вот HTML-разметка:

SckW1McK61WTzv0v0aaDUJb2eVAcQejKMvn-

Основное отличие здесь состоит в том, что платформы не содержатся как тексты в определенном тэге. Они указаны как название класса. Некоторые названия имеют только одну платформу, связанную с ними, например:

<span class="platform_img win">&lt;/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 мы используем понимание списка, чтобы уменьшить размер кода. The .get() метод позволяет нам извлечь атрибут тэга. Здесь мы используем его для извлечения class атрибут а span. Мы получаем строку назад из .get() метод. В случае первой игры возвращается строка platform_img win поэтому мы разделили эту строку на основе запятой и пробела. Затем мы сохраняем последнюю часть (которая является фактическим названием платформы) разделенной строки в списке.

в строки 7–8 мы удаляем hmd_separator из списка, если он существует. Это происходит потому hmd_separator не является платформой. Это просто вертикальная разделительная полоса, которая используется для отделения фактических платформ от оборудования VR/AR.

Вот код, который мы имеем:

import requestsimport lxml.htmlhtml = requests.get('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 Tips. Я регулярно пишу в этом блоге советы, подсказки и учебники из Python. И если вам интересно изучение Python среднего уровня, посмотрите мою книгу с открытым исходным кодом здесь.

Просто оговорка: мы лесозаготовительная компания @ Timber. Нам было бы очень приятно, если бы вы испытали наш продукт (это действительно здорово!), но вы здесь, чтобы узнать о веб-скрейпинге в Python, и мы не хотели от этого отказываться.

M2arEU7akD9w2V4YapbwHr9u0axtZQfxy9q5

Первоначально опубликовано на timber.io.

Добавить комментарий

Ваш адрес email не будет опубликован.