Практикум. Інтернет магазин продажу штор.
Основи 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_ua: |
Як розробити інтернет магазин на <strong>python</strong>!
Орієнтовна структура каталогу товарної позиції:
.tul-metis
..images
...1.png
...2.png
meta.yml
description.md
Для опису товару я визначив файл description.md, який використовує формат markdown для розмітки тексту, але дозволяє використовувати і html нативний html.
Файл опису товарної позиції.
name_slug: shtory-raduga
name_ru: Веселка штори.
meta_title_ua: Перші кроки Python.
meta_keywords_ua: Перші кроки Python.
meta_description_ua: Перші кроки 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 з категоріями та підкатегоріями.
Підкатегорії, крім назви, мають і посилання на сторінку з товарами, її ми також використовуватимемо для отримання інформації про продукти кожної взятої категорії.
Всі наведені нижче приклади наведено для операційної системи на базі Debian.
Створення віртуального оточення.
virtualenv -p python3 venv3
Після створення віртуального оточення необхідно його активувати, набравши таку команду в консолі.
. ./venv3/bin/activate
Встановити наші бібліотеки можна такими командами.
pip install requests
pip install beatifulsoup4
Якщо у вас не встановлена програма pip, то можна поставити її так:
sudo apt install python-pip
Нижче наведено Python код із докладними коментарями, який розбирає структуру категорій та виводить її на екран терміналу.
#!/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 **