В данном проекте я осуществляю парсинг сайта с информацией по калориям продуктов https://health-diet.ru/table_calorie/ .
Парсинг – это автоматический сбор данных по конкретным параметрам или под какие-то задачи. Соответственно, парсеры – специальные сервисы для автоматического сбора данных. Собирать информацию можно практически из любых источников. Там где вы можете вычленить данные вручную, там можно использовать и парсинг, главное подобрать правильный инструмент для этого
Решение данной задачи было в качестве учебного проекта, чтобы еще больше отточить свои навыки в парсинге сайтов.
На каждый пунктик и скрин на каждое действие не делал, поскольку данный проект представлен как презентация, а не учебное пошаговое пособие.
Язык программирования - Python;
Библиотеки ЯП - requests, BeautifulSoup
Модули ЯП - random, time
Сохраняем файлы в формат - Json, CSV, html
-
Сначала забираем страницу с сайта и после оперируем ей
import random from time import sleep import requests from bs4 import BeautifulSoup import json import csv # **Сохраняем копию страницы сайта на которой расположены все ссылки на категории и после работаем с ней** # url = "https://health-diet.ru/table_calorie/" # headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36" } # # # req = requests.get(url, headers=headers) src = req.text print(src) with open("index.html", "w", encoding="utf-8") as file: file.write(src)
-
Проверяем ссылки сохраненной страницы, собираем их по общему классу, добавляем доменное имя
with open("index.html", encoding="utf-8") as file: src = file.read() soup = BeautifulSoup(src, "lxml") all_products_hrefs = soup.find_all(class_="mzr-tc-group-item-href") for item in all_products_hrefs: # print(item) item_text = item.text item_href = "https://health-diet.ru" + item.get("href") # print(f"{item_text}: {item_href}")
-
Сохраняем в словарь
сохраняем данные в словарь, создаем переменную
all_categories_dict = {}
и на каждой итерации цикла будем наполнять наш словарикall_categories_dict[item_text] = item_href
в котором ключи, это имена категорий, а значения это ссылки[item_text] = item_href
all_categories_dict = {} for item in all_products_hrefs: # print(item) item_text = item.text item_href = "https://health-diet.ru" + item.get("href") # print(f"{item_text}: {item_href}") all_categories_dict[item_text] = item_href
-
Сохраним наш словарь в json файл
💡 очень часто бывает необходимо сохранить данный в json файл и сохранение таких файлов съкономит вам поиск информации в интернетеwith open("index.html", encoding="utf-8") as file: src = file.read() soup = BeautifulSoup(src, "lxml") all_products_hrefs = soup.find_all(class_="mzr-tc-group-item-href") all_categories_dict = {} for item in all_products_hrefs: # print(item) item_text = item.text item_href = "https://health-diet.ru" + item.get("href") # print(f"{item_text}: {item_href}") all_categories_dict[item_text] = item_href with open("all_categories_dict.json", "w", encoding="utf-8") as file: json.dump(all_categories_dict, file, indent=4, ensure_ascii=False)
-
Закомментируем код выше и загрузим наш файл в переменную
all_categories
with open("all_categories_dict.json") as file: all_categories = json.load(file)
-
Создаем цикл на каждой итерации которого мы будем заходить на новую страницу категории, собирать с нее данные о продуктах и хим. составе и записывать все в файл. Также пробелы из знаки препинания заменим на нижний слэш, для более лаконичного составления имен
iteration_count = int(len(all_categories)) - 1 count = 0 print(f"Всего итераций: {iteration_count}") for category_name, category_href in all_categories.items():
# метод replace для замены нескольких символов сразу к нескольким элементам rep = [",", " ", "-", "'"] for item in rep: if item in category_name: category_name = category_name.replace(item, "_") # если символ есть в имени, то меняем его на нижний слэш
-
Переходим к запросам на странице, расскоментируем заголовки (headers), чтобы мы могли снова их использовать. Создадим папку data, чтобы сохранять все страницы туда и сделаем счетчик для категорий (смотри выше в п.6 где count = 0)
req = requests.get(url=category_href, headers=headers) src = req.text # сохраним в переменную src with open(f"data/{count}_{category_name}.html", "w") as file: file.write(src)
Но прежде чем бомбить сайт запросами, протестируем наш код на одной странице с помощью if
count = 0 for category_name, category_href in all_categories.items(): if count == 0: rep = [",", " ", "-", "'"] for item in rep: if item in category_name: category_name = category_name.replace(item, "_") # print(category_name) # req = requests.get(url=category_href, headers=headers) src = req.text # with open(f"data/{count}_{category_name}.html", "w") as file: file.write(src) count += 1 # count += 1 после всех операций увеличиваем имя файла на 1
# откроем и сохраним код страницы в переменную src with open(f"data/{count}_{category_name}.html") as file: src = file.read() # Создаем объект супа soup = BeautifulSoup(src, "lxml") # смотрим на сайте заголовки для таблицы калорий
# собираем заголовки таблицы # поскольку мы получили список, мы можем обращаться к нему по индексам table_head = soup.find(class_="mzr-tc-group-table").find("tr").find_all("th") product = table_head[0].text calories = table_head[1].text proteins = table_head[2].text fats = table_head[3].text carbohydrates = table_head[4].text # Запишем в файлы csv категории продукты, с помощью метода writer.writerow передаем 5 объектов with open(f"data/{count}_{category_name}.csv", "w", encoding="utf-8") as file: writer = csv.writer(file) writer.writerow( ( product, calories, proteins, fats, carbohydrates ) ) count += 1 ) )
-
Соберем данные продуктов в теге tbody из тегов tr
и далее в цикле собререм все td теги
products_data = soup.find(class_="mzr-tc-group-table").find("tbody").find_all("tr") product_info = [] for item in products_data: product_tds = item.find_all("td") title = product_tds[0].find("a").text calories = product_tds[1].text proteins = product_tds[2].text fats = product_tds[3].text carbohydrates = product_tds[4].text
-
Запишем снова в наши csv скопируем код с одним отличием, вместо “w” write (записать), поставим “a” , что значит добавить append
with open(f"data/{count}_{category_name}.csv", "a", encoding="utf-8") as file: writer = csv.writer(file) writer.writerow( ( product, calories, proteins, fats, carbohydrates ) )
-
При запуске кода, нам выпадет ошибка, поскольку по одной из ссылок, код не найдет таблицы, поэтому мы допишем условие
# проверка страницы на наличие таблицы с продуктами alert_block = soup.find(class_="uk-alert-danger") if alert_block is not None: continue
-
Cоздадим переменную, в которую положим количество страниц категории. Так как наш count начинается с нуля , отнимем единицу от функции len предварительно обернув в строку, поскольку len возвращает объект строки
iteration_count = int(len(all_categories)) - 1 count = 0 print(f"Всего итераций: {iteration_count}")
-
Для наглядности выведем операции на принт
print(f"# Итерация {count}. {category_name} записан...") # Отнимаем единицу от количества итераций iteration_count = iteration_count - 1 # также допишем условие, когда итераций остается 0, печатаем Работа завершена и выходим из цикла if iteration_count == 0: print("Работа завершена") break print(f"Осталось итераций: {iteration_count}") # Поскольку сбор данных идет быстро, для наглядности добавим рандомную задержку sleep(random.randrange(2, 4))
-
Также создадим json файл и в него мы будем собирать все данные из в виде словаря
#добавляем csv и json product_info.append( { "Title": title, "Calories": calories, "Proteins": proteins, "Fats": fats, "Carbohydrates": carbohydrates } ) with open(f"data/{count}_{category_name}.csv", "a", encoding="utf-8") as file: writer = csv.writer(file) writer.writerow( ( title, calories, proteins, fats, carbohydrates ) ) with open(f"data/{count}_{category_name}.json", "a", encoding="utf-8") as file: json.dump(product_info, file, indent=4, ensure_ascii=False)