Основи 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 нативний 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 і використовували заголовок для знаходження назви товару.

itle = 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 і для кожного нового сайту знадобиться написати свій власний парсер контенту.