Парсинг
если в этом блоке вы плохо видите некоторые изображения, откройте их в новой вкладе
Введение
Все данные, которые мы видим на различных страницах интернета отдаются нам в виде html документов и данные в этих документах структурированы html тегами, которым зачастую присвоен какой-то уникальный идентификатор. Такое устройство распространения данных и дало нам возможность собирать эту информацию и, конечно, python имеет для этих целей свои библиотеки. Говорят, что нет интернет ресурса, с которого не возможно было бы спарсить данные, не возьмусь утверждать то же самое, но как минимум спарсить список вакансий с соответствующих сайтов или список актуальных скидок с какого-нибудь маркетплейса большого труда не составить. В этом блоке будем разбираться как это делается.
Библиотека requests
Установка библиотеки requests:
sudo apt install python3-requests
- для терминала linux.
pip install requests
- для виртуального окружения.
Библиотека requests позволяет нам отправлять запросы на сервера и получать на них ответ. requests поддерживает методы get и post, метод get используется, когда нам нужно просто получить данные со страницы, метод post используется, когда мы хотим передать какие-то данные обрабатываемому сайту, например авторизационные данные.
Давайте воспользуемся метод get для получения информации с сайта github.

Сначала импортируем библиотеку requests. В переменную link поместим ссылку на страницу, к которой хотим обратиться. GitHub имеет открытую api документацию, поэтому обратимся сразу к api сайта. Принято при работе с этой библиотекой называть переменную, которая будет хранить запрошенные данные, response, но это конечно не принципиально. Воспользоваться get запросом можно несколькими способна, конструкция типа:
response = requests.get('ссылка на ресурс')
- возвратит статус запроса, в нашем случае мы получили ответ - response [200], код ответа 200 означает, что запрос отработал без ошибок.
response = requests.get('ссылка на ресурс').text
- использование метода text позволяет декодировать запрос в читаемый текст, не всегда метод text может правильно декодировать запрос, тогда можно воспользоваться бинарным ответом.
response = requests.get('ссылка на ресурс').content
- метод content отобразит бинарное содержимое ответа.
Поскольку в нашем запросе проблем с декодированием не возникло методы .text и .content возвратили нам одинаковый результат.
Что же мы получили в ответ на наш запрос? Мы получили разветвления, по которым мы можем продолжить делать запросы для получения нужной нам информации. Например, в этом списке есть отдельная ссылка, которая возвратит все emoji, используемые на github.

В ответ на этот запрос мы получаем огромный список ссылок, при переходе по которыми мы увидим какой-то emoji.
Это пример простых api запросов, с api сайтов работать достаточно просто, ведь разработчики сайта сами обернули в удобный вид информацию, которая может пригодиться пользователям. Но мы можем отправлять запросы не только к api, но и просто на любой сайт в интернете.
Процесс анализа интернет страниц для сбора информации и называется парсинг. Но библиотека requests это, конечно, не единственный инструмент для парсинга.
Библиотека fake-useragent
Установка библиотеки fake-useragent:
pip install fake-useragent
Многие сайты защищены от парсинга проверкой HTTP заголовка User-Agent, когда этого заголовка нет сайт воспринимает запрос, как запрос от робота и запрос не выполняется. Библиотека fake_useragent создает имитацию реального пользователя.

UserAgent каждый раз генерирует случайного пользователя, с разной ОС, браузером и прочими данными.
Библиотека BeautifulSoup и первый парсер
BeautifulSoup - наш основной инструмент, мощная и простая библиотека для парсинга.
Установка библиотеки BeautifulSoup:sudo apt install python3-bs4
- для терминала linux.pip install bs4
- для виртуального окружения.
Для работы библиотеки BeautifulSoup нужен модуль lxml. Объект BeautifulSoup принимает два аргумента - разметку страницы, к которой мы обращаемся и анализатор этой разметки. lxml как раз является этим анализатором, на ряду с ним используются html.parser и, например, html5lib, но если два последних позволяют обрабатывать исключительно HTML-файлы, то lxml обрабатывает и XML- и HTML-файлы.
Установка библиотеки lxml:
sudo apt install python3-lxml
- для терминала linux.
pip install lxml
- для виртуального окружения.
Начнем сразу с практики. Давайте опять вернемся на сайт github и посмотрим на страницу маркетплейса.

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

Посмотрим код этой страницы, нас интересует несколько данных. А именно заголовки запроса, в самом простом варианте хватит только user-agent. Для того чтобы его узнать зайдем во вкладку Network и если там пусто обновим страницу для того, чтобы увидеть список наших запросов на сайт. Выберем любой из этих запросов, например самый верхний и перейдем во вкладку headers. Возьмем оттуда наш user-agent. Необходимые данные есть, теперь перейдем в pycharm.
import requests
from bs4 import BeautifulSoup
url = 'https://github.com/marketplace?query=sort%3Apopularity-desc&type=apps'
headers = {
'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0'
}
def get_apps():
response = requests.get(url=url, headers=headers)
html_file = response.text
with open('index.html') as file:
file.write(html_file)
bs = BeautifulSoup(response.text, "lxml")
apps_cards = bs.find_all("div", class_="d-md-flex flex-wrap mb-4")
for app_card in apps_cards:
print(app_card)
def main():
get_apps()
if __name__ == '__main__':
main()
Сначала импортируем библиотеки requests и BeautifulSoup. Далее создадим переменные url и headers, где url - адрес страницы откуда мы хотим забрать данные, а headers - заголовки запроса, которые нужны, чтобы показать сайту, что мы реальный человек, а не робот. В headers можно было бы добавить еще заголовок accept, найти его можно там же, где и user-agent. Первым делом сделаем get запрос к странице, используя атрибуты url и headers. Полученные данные представим в текстовом виде методом .text. Для наглядности сохраним спаршеную страницу в файл index.html. Если теперь открыть этот файл, то там будет весь html код исследуемой страницы. Следующим шагом создадим объект BeautifulSoup, с названием bs, куда передадим в качестве атрибутов наш response с примененным к нему методом text и lxml. Основные методы bs4, которыми мы будем пользоваться это find() и find_all().
Метод find() находит первое совпадение по имени тега или по его классу, либо, как на примере, одновременно по тегу и классу для большей точности.
Метод find_all() работает также, только находит все совпадения по заданным параметрам во всем файле. Обратите внимание, что class_ пишется с нижним подчеркиванием в конце, это необходимо из-за того, что class зарезервированное слово.
По какой логике мы выбираем какие данные передать в find() и find_all()? Вернемся на github. Я перешел с главной страницы на страницу посмотреть все.

Воспользуемся инструментом выбора элемента, выделен красным квадратиком на скриншоте, и найдем область, в которой находится список наших приложений. Все наши приложения лежат в теге div с классом "d-md-flex flex-wrap mb-4", передадим эти данные в переменную apps_cards. И как сразу видно в инспекторе внутри лежат все данные, которые нас могут заинтересовать. Пробежимся циклом по нашей переменной apps_cards и выведем результат.

Вот какие данные нам удалось забрать, информация о всех карточках, чего и следовало ожидать. Наша программа выполняет то, что от нее требуется. Соберем более детальные данные, например заберем название приложения и количество его установок. Как видно название лежит в теге h3 с классом - h4, а количество скачиваний в теге span с классом - text-small color-fg-muted text-bold. Воспользуемся этими данными.

Переменная app_name - для имени и переменная app_count_of_installs - для количества установок. Метод .text обрежет теги и оставит голый текст, а метод .strip уберет образовавшиеся после удаления тегов пробелы по краям. Выведем результат и получим то, что хотели - имя приложения и количество его установок. Не забывайте про конструкцию if __name__ == '__main__'.
Вот такая простенькая программа выводит нам в консоль интересующую нас информацию. Теперь эту программу можно открыть при закрытом браузере и увидеть самое популярное на данный момент приложение на github. Конечно, это не самое эффективное применение и не самые необходимые данные, но надеюсь данный кейс сформировал у вас представление о том, что такое парсинг и общую идею его работы.
Углубляемся в парсинг
Просто взять название со страницы вряд ли будет полезно, ткм более написав для этого достаточно нагроможденную программу. Это можно сделать гораздо лаконичнее. Всем названиям приложений присвоен один класс, достаточно их распечатать.
import requests
from bs4 import BeautifulSoup
url = 'https://github.com/marketplace?query=sort%3Apopularity-desc&type=apps'
headers = {
'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0'
}
def get_apps():
response = requests.get(url=url, headers=headers)
bs = BeautifulSoup(response.text, 'lxml')
titles = bs.find_all(class_='h4')
n = 0
for title in titles:
app_title = title.text.strip()
n += 1
print(f"{n} - {app_title}")
def main():
get_apps()
if __name__ == '__main__':
main()
Таким образом получим все названия с первой страницы и за одно узнаем сколько их на странице. Объяснений по коду тут не требуется мы уже все это обсудили. Но допустим нам хочется не только получить названия приложений, но и ссылку в маркетплейс на страницу этого приложения. Вернемся еще раз к коду страницы.

Как мы видим каждому приложению выделен отдельный блок. И у каждого есть атрибут "href=" где лежит ссылка на проект. Давайте пробежимся по всем приложениям и заберем помимо названий ссылки.
Тут уже есть что обсудить. Во-первых, как и в самом первом парсере, заберем весь html код страницы и прочитаем его в переменную item, и в дальнейшем будем обращаться к ней. Зачем нам мучить сайт запросами, если мы можем обращаться к его сохраненной копии. Конечно, если мы хотим получать актуальную информацию с сайта, то такой вариант нам не подойдет, но когда мы только пишем программу и экспериментируем с ее кодом, каждый раз обращаться напрямую к сайту нет нужды. Далее пробежимся по каждому тегу a с интересующим нас классом. Названия приложений заберем уже знакомым способом. А ссылки заберем методом get, которым обратимся к атрибуту href. Но если просто забрать содержимое href, то мы получим ссылку без доменного имени, поэтому на каждой итерации будем плюсовать доменное имя к нашей ссылке. Теперь наш парсер кажется более полезным, мы получили список приложений по популярности со ссылками на них. Правда остался еще один момент, мы забираем приложения только с первой страницы, но конечно хотелось бы забрать данные со всех страниц.

Спустимся вниз страницы и посмотрим сколько страницы на странице, перейдем на вторую и обратим внимание на ссылку, меняется параметр page. Воспользуемся этим.

Вот таким нехитрым способом можно это реализовать. Поместим url внутрь функции и поместим все тело функции в цикл for, количество итераций которого равно количеству страниц + 1. С помощью f строки будем поочередно вставлять номер страницы в ссылку. Программа вернула нам все приложения со всех страниц и ссылки на них списком, достаточно удобно. Последним приложением в списке является - Indeema Tambo, перейдем на 27 страницу и посмотрим все ли верно.

Чего и следовало ожидать. И напоследок давайте сохраним полученную информацию в json файл.
import requests
from bs4 import BeautifulSoup
import json
headers = {
'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) '
'Gecko/20100101 Firefox/96.0'
}
def get_apps():
apps_github_dict = {}
for x in range(28):
url = f'https://github.com/marketplace?page={x}' \
f'&q=sort%3Apopularity-desc&query=sort%3Apopularity-desc&type=apps'
response = requests.get(url=url, headers=headers)
with open('index.html', 'w') as file:
file.write(response.text)
with open('index.html') as file:
item = file.read()
bs = BeautifulSoup(item, 'lxml')
titles = bs.find_all(class_='col-md-6 mb-4 d-flex no-underline')
for title in titles:
app_title = title.find('h3', class_='h4').text.strip()
app_href = 'https://github.com/' + title.get("href")
apps_github_dict[app_title] = app_href
with open('apps_info_dict.json', 'w') as file:
json.dump(apps_github_dict, file, indent=4)
def main():
get_apps()
if __name__ == '__main__':
main()
Импортируем библиотеку json. Создадим перед главным циклом пустой словарь куда будем в качестве ключа передавать название проекта, а в качестве значения ссылку на этот проект. И с помощью метода dump библиотеки json сохраним этот словарь в json формат.
Парсинг и телеграм бот
Давайте сымитируем ситуацию. Какое-то время я работал на сайте g2g, на этом сайте люди продают игровые услуги за деньги, конкретно я в основном продавал золото в игре World of Warcraft.

Рассмотрим сайт. Я продавал золото на сервере Dragon's Call - фракция Альянс. Перейдем на страницу продавцов этого сервера и включим тумблер lower price, чтобы отсортировать продавцов по возрастанию цены. Перед нами карточки продавцов, у них есть: имя, метод доставки, время доставки, количество золота в наличии, минимальное количество золота, которое можно приобрести и цена за одну монету плюс валюта. Я хочу получать актуальную информацию с этой страницы, чтобы мониторить как меняется активность на этой вкладке. Мне хочется получать имя, количество золота и цену за единицу. Мы уже знаем как это сделать, но возникает одна трудность и как с ней поступать мы еще не знаем. Вся нужная нам информация находится в одном блоке с классом 'other_offer-desk-main-box other_offer-div-box', для имени и цены за единицу тоже есть уникальные классы, а вот у количества, класс точно такой же как и у времени доставки и такой же, как у минимальной суммы сделки. Для того чтобы взять нужную информацию из одинаковых тегов с одинаковыми классами напишем такую программу.
import requests
from bs4 import BeautifulSoup
headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) '
'Gecko/20100101 Firefox/96.0'
}
def get_sellers():
url = 'https://www.g2g.com/offer/Dragon-s-Call-TBC--DE----' \
'Alliance?service_id=lgc_service_1&brand_id=lgc_game_29076®ion' \
'_id=ac3f85c1-7562-437e-b125-e89576b9a38e&fa=lgc_29076_server%3Algc_' \
'29076_server_40988&q=dra&sort=lowest_price&include_offline=1'
response = requests.get(url=url, headers=headers)
with open('index_g2g.html', 'w') as file:
file.write(response.text)
with open('index_g2g.html') as file:
item = file.read()
bs = BeautifulSoup(item, 'lxml')
sellers = bs.find_all(class_='other_offer-desk-main-box other_offer-div-box')
for seller in sellers:
seller_name = seller.find("div", class_='seller__name-detail').text.strip()
seller_stock = seller.find("div", class_='offers-bottom-attributes '
'offer__content-lower-items').find_next().find_next().find("span").text.strip()
seller_price = seller.find("span", class_='offer-price-amount').text
print(f"{seller_name} - {seller_stock} - {seller_price}")
def main():
get_sellers()
Ничего нового, кроме find_next(). Метод find_next() позволяет обращаться к следующему тегу, это более мощный метод, чем next_element(). next_element() в отличие от find_next() ищет следующий элемент html дерева. Например, если после тега, к которому вы применили метод next_element идет перенос строки, то вы заберете его и получите тип None.
Для того чтобы взять предыдущий элемент есть метод previous_element().
Помимо этих методов есть find_next_sibling() и find_previous_siblings() - они вернут вам теги находящиеся после и до соответственно, на одном уровне с тегом, к которому были применены эти методы. Это не все методы, которые существуют для перемещения по html дереву, все методы можно найти например в официальной документации. Возможно далее мы воспользуемся какими-нибудь еще, но на данный момент я не знаю на сколько парсеров затянется этот раздел. Просто знайте, если на странице есть какая-то информация BeautifulSoup сможет ее забрать. Мы забрали нужную нам информацию, нам хватит только ее.
Теперь хотелось бы обернуть эту информацию в телеграм бота, который будет по запросу проверят сайт и возвращать нам эту информацию. Это быстрее, чем смотреть вручную, плюс в переписке с ботом будет храниться история обращений, благодаря которой можно будет посмотреть как поменялась информация по каждому продавцу, а также видеть какие из них пропали из списка, а какие появились. Благодаря этой информации можно эффективно анализировать активность покупателей.
Для отправки комментария необходимо авторизоваться
Комментарии
Здесь пока ничего нет...