Практикум. Интернет-магазин продажи штор.
Основы Python и Django. -> Создание интернет-магазина. Часть 1.
Создание интернет-магазина. Часть 1.
В начале давайте подумаем из чего состоит типовой интернет-магазин? Я выделил 3 основных функциональных блока:
Каталог товаров. Корзина пользователя. Оформление заказа.
Эти три части составляют основу и без них невозможно представить работу магазина. Начнем с первой части т.к. она наиболее трудоемка.
Каталог товаров.
Если вы решили создать свой интернет-магазин, то очевидно знаете примеры готовых решений, которые вам подходят больше всего и отражают вашу идею и ассортимент продукции. Глупо этим не воспользоваться и не скопировать информацию из других сайтов. Я говорю именно про информацию о товарах, ценах, картинках, описании и т.д. Наличие инструмента для автоматического сбора подобных данных может сэкономить колоссальное количество времени, требующееся для наполнения вашего будущего интернет магазина. Самым простым будет создание некоторого скрипта, который бы ходил по сайту-примеру и собирал все необходимое, складывая в виде определенной структуры папок и файлов. Далее эту структуру мы передадим другой программе, которая наполнит нашу базу данных и сам сайт.
Теперь обсудим структуру каталога данных, назовем его data.
Предположим, мы продаем шторы и хотим создать интернет магазин штор. Возьмем какой то пример. В выдачи по “магазин штор” у меня первым вышел https://pangardin.com.ua Думаю, было бы логичным подкаталоги назвать по имени интернет-ресурса. Но помимо каталога продукции, на всех сайтах присутствуют и статические страницы с информацией о владельце, доставке, обратной связи и пр. Думаю что подобную информацию можно оформить в специальных текстовых файла в корне каталога data.
Примерная структура каталога данных:
. data
..pages
...main.txt
...about.txt
...contact.txt
..pangardin.com.ua
...tul
...shtory
...furnitura
Как видно, мы разбиваем товары по каталогам, будем называть их каталог-категориями, при этом внутри таких категорий необходим какой-то мета файл с информацией о данной категории.
Можно воспользоваться форматом yml, как по мне, это наиболее читабельный вариант оформления структурированных данных.
Так и назовем этот файл - meta.yml. С учетом многоязычности этот файл может выглядеть так:
content_type: category
is_published: true
order: 1
title_ru: Тюль.
title_ua: Тюль.
desc_ru: |
Самая лучшая <strong>тюль</strong>!
desc_ua: |
Дуже гарна тюль!
meta_title_ru: Купить тюль. Интернет-магазин штор.
meta_title_ua: Створення iнтернет магазина штор
meta_keywords_ru: тюль шторы купить интернет-магазин
meta_keywords_ua: ….
meta_description_ru: ….
meta_description_ua: ….
Я заложил минимум информации о каталоге. Первым делом я определяю тип информации, для того чтобы отличать описание категории и товара и определять в каком каталоге находится программа. Либо это каталог-категория, либо каталог-товар. Далее идет признак опубликованности на сайте, сортировка и информация для страницы категории.
Подобным образом можно оформить и статичные страницы в файлах *.yml в каталоге pages. Например так:
content_type: page
is_published: true
title_ru: Разработка интернет-магазина штор.
desc_ru: |
Как разработать интернет магазин на <strong>python</strong>!
Примерная структура каталога товарной позиции:
.tul-metis
..images
...1.png
...2.png
meta.yml
description.md
Для описания товара я определил файл description.md, использующий формат markdown для разметки текста, но позволяющий использовать и нативный html код.
Файл описания товарной позиции.
name_slug: shtory-raduga
name_ru: Шторы радуга.
meta_title_ru: Первые шаги Python.
meta_keywords_ru: Первые шаги Python.
meta_description_ru: Первые шаги Python.
is_published: false
Для простоты, пока будем оперировать одной, русской локализацией.
Итак, наша следующая задача - опросить сайт-источник скриптом и создать требуемую структуру каталогов. Для этого я буду использовать библиотеки Python requests и BeatifulSoup.
Библиотекой requests мы будем генерировать HTTP запросы на сервер и получать HTML код страниц.
Библиотекой BeatifulSoup мы этот HTML парсим и находим в нем интересующие элементы (теги) и забираем их содержимое.
В начале, нас интересует сама структура каталога. Ее можно найти на главной странице во всплывающем блоке.
HTML код этого списка выглядит так:
<ul class="sub-menu">
<li class="menu-item ..."><h4 class='mega_menu_title'>Категории</h4>
<ul class="sub-menu">
<li class="menu-item ..."><a href="..."><span class="avia-bullet"></span>Ткани-Компаньоны</a></li>
...
Я убрал лишние css классы для наглядности. Что мы имеем - это два вложенных списка ul с категориями и подкатегориями.
Подкатегории, кроме названия имеют и ссылку на страницу с товарами, ее мы тоже будем использовать для получения информации о продуктах каждой взятой категории.
Все, приведенные ниже примеры, даны для операционной сисnемы на базе Debian.
Создание виртуального окружения.
virtualenv -p python3 venv3
После создания виртуального окружения необходимо его активировать набрав такую команду в консоле.
. ./venv3/bin/activate
Установить в него наши библиотеки можно такими командами.
pip install requests
pip install beatifulsoup4
Если у вас не установлена программа pip, то поставить ее можно так:
sudo apt install python-pip
Ниже приведен Python код с подробными комментариями, который разбирает cтруктуру категорий и выводит ее на экран терминала.
#!/usr/bin/env python
# импортируем библиотеки
import requests
from bs4 import BeautifulSoup
print("Getting catalog")
# делаем запрос на получение главной страницы
url = 'https://pangardin.com.ua'
r = requests.get(url)
# распознаем HTML, создавая специальный объект soup
soup = BeautifulSoup(r.text, 'html.parser')
# находим главный (родительский) элемент списка категорий
ulsout = soup.find('ul',{'class': 'sub-menu'})
# в цикле проходим по всем его элементам
for ulout in ulsout:
liout = ulout.find('li')
if liout != -1:
print('.'+liout.text)
# для каждой родительской категории находим дочерний список подкатегорий
ulins = ulout.find('ul')
# в цикле проходим по подкатегориям
for liin in ulins.findAll('li'):
print('..'+liin.text)
Вывод программы:
.Ткани-Компаньоны
..Ткани-Компаньоны
..Тюль
..Портьерные ткани
..Готовые шторы
..Шторы нити
..Постельное белье
..Фурнитура
….
Все что тут происходит - это перебираются ul - списки в двух вложенных циклах.
Теперь осталось перевести названия в транслит и создать нужные каталоги.
Транслитирировать можно библиотекой python-slugify и делается это передачей нужной строки в функцию slugify.
name_slug = slugify(name)
Для удобства, программу всегда разделяют на более мелкие подпрограммы, например функциии.
Функцию сохранения категории на диск можно описать так:
def save_category(cat_name,parent_name):
''' сохранение каталогов-категорий '''
# транслитерируем русское название категории
cat_name_slug = slugify(cat_name)
# транслитерируем русское название подкатегории
parent_name_slug = slugify(parent_name)
# выводим отладочное сообщение в консоль
print("Creating category %s with parent %s" % (cat_name,parent_name))
# формируем путь для для создания каталога
dir_name = os.path.join(DATA_DIR,cat_name_slug)
# создаем каталог, если его не существует
if not os.path.isdir(dir_name):
os.mkdir(dir_name)
Для того, чтобы использовать такую функцию ее необходимо вызвать, передав два параметра - название категории и подкатегории.
Это будем делать во внутреннем цикле.
for liin in ulins.findAll('li'):
save_category(liin.text, liout.text)
Полный код программы и результат его работы приведен ниже.
#!/usr/bin/env python
import requests
from bs4 import BeautifulSoup
import os
from slugify import slugify
### создаю каталоги с данными
if not os.path.isdir('data'):
os.mkdir('data')
if not os.path.isdir(os.path.join('data','pangardin.com.ua')):
os.mkdir(os.path.join('data','pangardin.com.ua'))
###
DATA_DIR = os.path.join('data','pangardin.com.ua')
def save_category(cat_name,parent_name):
''' сохранение каталогов-категорий '''
cat_name_slug = slugify(cat_name)
parent_name_slug = slugify(parent_name)
print("Creating category %s with parent %s" % (cat_name,parent_name))
dir_name = os.path.join(DATA_DIR,cat_name_slug)
if not os.path.isdir(dir_name):
os.mkdir(dir_name)
print("Getting catalog")
url = 'https://pangardin.com.ua'
r = requests.get(url)
soup = BeautifulSoup(r.text, 'html.parser')
ulsout = soup.find('ul',{'class': 'sub-menu'})
for ulout in ulsout:
liout = ulout.find('li')
if liout != -1:
ulins = ulout.find('ul')
for liin in ulins.findAll('li'):
save_category(liin.text, liout.text)
Дополним функцию save_category созданием meta.yml файлов, минимально описывающих категории.
В эти файлы будем писать имена и псевдонимы текущей и родительской категории.
def save_category(cat_name,parent_name):
''' сохранение каталогов-категорий '''
# транслитерируем русское название категории
cat_name_slug = slugify(cat_name)
# транслитерируем русское название подкатегории
parent_name_slug = slugify(parent_name)
# выводим отладочное сообщение в консоль
print("Creating category %s with parent %s" % (cat_name,parent_name))
# формируем путь для для создания каталога
dir_name = os.path.join(DATA_DIR,cat_name_slug)
# создаем каталог, если его не существует
if not os.path.isdir(dir_name):
os.mkdir(dir_name)
# создаем строку в которую вставляем названия категорий
meta_info = '''
content_type: category
is_published: true
name_slug: %s
name_ru: %s
parent_slug: %s
parent_name_ru: %s
order: 1
descr: |
''' % (cat_name_slug,cat_name,parent_name_slug,parent_name)
with open(os.path.join(dir_name,'meta.yml'),'w') as f:
f.write(meta_info)
Функция прохода по странице каталога
def save_goods(url,category):
# Получаем станицу категории
r = requests.get(url)
soup = BeautifulSoup(r.text, 'html.parser')
# находим список товаров
products = soup.find('ul',{'class': 'products'}).findAll('li')
for product in products:
# заголовок
title = product.find('div',{'class': 'inner_product_header'}).find('h3').text
title_slug = slugify(title)
# фоормируем путь
path = os.path.join(DATA_DIR,category,title_slug)
# создаем каталог товара
print("Saving ... %s" % title_slug)
if not os.path.isdir(path):
os.mkdir(path)
# находим ссылку на страницу товара
link = product.find('div',{"class": "inner_cart_button"}).find('a').get('href')
# сохраняем позицию
save_position(link,path)
Взглянем на то как выглядит HTML код позиции товара на странице категории
https://pangardin.com.ua/category/complect/artdeco/
<li class="...">
<div class="inner_product wrapped_style ">
<div class="avia_cart_buttons single_button">
<div class="inner_cart_button">
<a href="https://pangardin.com.ua/magazin/ballades/" >Просмотр товара</a>
</div>
</div>
<a href="https://pangardin.com.ua/magazin/ballades/">
<div class="thumbnail_container">
<img src="..." class="attachment-shop_catalog wp-post-image" alt="Ballade">
</div>
<div class="inner_product_header">
<h3>Ballades</h3>
<span class="price"><span class="amount">208грн.</span>
<span class="amount">366грн.</span>
</span>
</div>
</a>
</div>
</li>
После получения элемента li
products = soup.find(‘ul’,{‘class’: ‘products’}).findAll(‘li’)
мы выбрали блок с классом inner_product_header и использовали заголовок для нахождения названия товара.
title = product.find(‘div’,{‘class’: ‘inner_product_header’}).find(‘h3’).text
Теперь необходимо получить url адрес страницы с описанием товара.
link = product.find(‘div’,{“class”: “inner_cart_button”}).find(‘a’).get(‘href’)
Опишем функцию сохранения позиции.
def save_position(link,path):
r = requests.get(link)
with open('log.html','w') as f:
f.write(r.text)
soup = BeautifulSoup(r.text, 'html.parser')
print(link)
#sys.exit('s')
# находим ссылку на изображение
img_link = soup.find('a',{"class": "MagicZoomPlus"}).get('href')
print("Downloading pic %s" % img_link)
# забираем картинку
r = requests.get(img_link, stream=True)
# создаем каталог images
path_to_img = os.path.join(path,'images')
if not os.path.isdir(path_to_img):
os.mkdir(path_to_img)
# сохраняем картинку
image_full_path = os.path.join(path_to_img,'1.png')
if r.status_code == 200:
with open(image_full_path, 'wb') as f:
r.raw.decode_content = True
shutil.copyfileobj(r.raw, f)
# находим название и описание
title = soup.find('h1',{"class": "product_title"}).text
description = soup.find('div',{"itemprop": "description"}).text
# формируем содержимое meta.yml
meta_info = '''
name_slug: %s
name_ru: %s
meta_title_ru: %s
meta_keywords_ru: %s
meta_description_ru: %s
is_published: true
description_ru: |
%s
''' % (slugify(title),title,title,title,title,description)
# запись в файл
with open(os.path.join(path,'meta.yml'),'w') as f:
f.write(meta_info)
При поиске картинки, мы вначале находим элемент div с классом inner_cart_button, а потом в нем ссылку, из которой атрибут href. Загрузив страницу описания товара найдем элемент с изображением в исходном коде.
<a class="MagicZoomPlus" href="https://pangardin.com.ua/wp-content/uploads/2013/04/DSC00047.jpg" >
<img itemprop="image" src="..." >
<div class="MagicZoomPup">
</div>
...
</a>
Получить на него ссылку можно такой строчкой кода, где мы ищем по классу MagicZoomPlus.
img_link = soup.find('a',{"class": "MagicZoomPlus"}).get('href')
Для того, чтобы получить более подробную и “чистую” информацию о товаре понадобиться некоторая сноровка в работе со структурой HTML страницы.
Скорее всего, надо будет отсеивать лишнюю информацию и выбирать то что вам нужно для будущего сайта.
Пример нашего сайта-источника написан на wordpress и для каждого нового сайта понадобится написать свой собственный парсер контента.
Основы Python и Django. -> Создание интернет-магазина. Часть 2.
Создание интернет-магазина. Часть 2.
Создание Django приложения.
django-admin.py startproject prj
cd prj
./manage.py migrate
Создание приложения shop
./manage.py startapp shop
Добавим приложение в проект.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'shop'
]
Создадим модель (shop/models.py).
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=250)
name_slug = models.CharField(max_length=250)
class Subcategory(models.Model):
name = models.CharField(max_length=250)
name_slug = models.CharField(max_length=250)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
class Good(models.Model):
name = models.CharField(max_length=250)
name_slug = models.CharField(max_length=250)
desc = models.TextField()
subcategory = models.ForeignKey(Subcategory, on_delete=models.SET_NULL, null=True)
class Image(models.Model):
good = models.ForeignKey(Good, on_delete=models.SET_NULL, null=True)
image = models.ImageField()
Опишем классы админ интерфейса и привяжем их к модели (shop/admin.py).
from django.contrib import admin
from .models import *
class CategoryAdmin(admin.ModelAdmin):
pass
class SubcategoryAdmin(admin.ModelAdmin):
pass
class GoodAdmin(admin.ModelAdmin):
pass
class ImageAdmin(admin.ModelAdmin):
pass
admin.site.register(Category, CategoryAdmin)
admin.site.register(Subcategory,SubcategoryAdmin)
admin.site.register(Good, GoodAdmin)
admin.site.register(Image, ImageAdmin)
Создадим новую команду импорта данных (shop/management/commands/import.py).
from django.core.management.base import BaseCommand, CommandError
from shop.models import *
class Command(BaseCommand):
def handle(self, *args, **options):
print('Importing data')
** Не забываем создавать файлы ini.py в новых каталогах management и commands **