Практикум. Інтернет магазин продажу штор.

Основи 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 **