L o a d i n g
Подробное руководство по созданию системы парсинга книг в Django с категоризацией и фильтрацией Django

В этом руководстве я опишу, как создать систему парсинга книг на основе Django, поддерживающую различные категории и гибкую фильтрацию. Мы разберем структуру проекта, создание моделей, настройку административной панели и реализацию самого парсера. Это решение предназначено для автоматизации сбора данных с сайта, например, онлайн-магазина, и будет полезно для разработчиков, стремящихся улучшить свои навыки работы с Django.

Шаг 1: Создание моделей для категорий и книг

Первым шагом является создание двух моделей: category_book для категорий и book_pages для книг. Эти модели определяют структуру данных, которую мы будем собирать и хранить в базе данных.

Модель для категорий книг

from django.db import models

# Словарь для хранения типов фильтрации книг
dict_book = {
    'Все книги': 'all_book',
    'По популярности': 'popular',
    'По новизне': 'news',
    'Больше отзывов': 'reviews',
    'Бестселлеры': 'bestseller',
    'Рейтинг (от 4 звезд)': 'reiting'
}

class category_book(models.Model):
    # Название категории
    titleCategory = models.CharField(max_length=250, verbose_name='Название категории')

    # Слаг для URL (генерируется автоматически)
    slugCategory = models.CharField(max_length=250, verbose_name='Слаг заполняется автоматически', blank=True, null=True)

    # URL категории на внешнем сайте
    urlCategory = models.CharField(max_length=250, verbose_name='Ссылка на категорию из сайта')

    # Флаг для выбора: только первая страница или вся категория
    allCategoryBook = models.BooleanField(default=True, verbose_name='Только первая страница? Или вся категория.')

    # Количество выгружаемых книг (опционально)
    calcCategoryBook = models.IntegerField(verbose_name='Введите количество выгружаемых книг', blank=True, null=True, default=0)

    # Тип сортировки книг
    filterCategoryBook = models.CharField(
        max_length=10,
        choices=[(value, name) for name, value in dict_book.items()],
        verbose_name='Тип сортировки книг'
    )

    def save(self, *args, **kwargs):
        # Автоматическая генерация слага, если он не был задан
        if not self.slugCategory:
            self.slugCategory = custom_slugify(self.titleCategory)
        super().save(*args, **kwargs)
    
    def __str__(self):
        return self.titleCategory
    
    class Meta:
        verbose_name = 'Категория'
        verbose_name_plural = 'Категории'

Эта модель включает основные поля для работы с категориями книг. Поле titleCategory хранит название категории, а slugCategory автоматически генерируется на основе названия с помощью функции custom_slugify. Поле urlCategory содержит ссылку на соответствующую категорию на внешнем сайте, откуда будут парситься данные. Флаг allCategoryBook позволяет выбрать, нужно ли загружать только первую страницу категории или все страницы. calcCategoryBook указывает количество книг, которые следует выгрузить, а filterCategoryBook — тип сортировки (например, по популярности или новизне).

Модель для книг

class book_pages(models.Model):
    # Изображение книги
    imgBook = models.CharField(max_length=250, verbose_name='Изображение книги', null=True, blank=True)

    # Название книги
    titleBook = models.CharField(max_length=250, verbose_name='Название книги')

    # Автор книги
    autorBook = models.CharField(max_length=500, verbose_name='Автор книги', null=True, blank=True)

    # Ссылка на книгу на внешнем сайте
    linkBook = models.URLField(verbose_name='Ссылка')

    # Старая цена
    oldPricesBook = models.CharField(max_length=250, verbose_name='Старая цена', null=True, blank=True)

    # Новая цена или просто цена
    newPricesBook = models.CharField(max_length=250, verbose_name='Новая цена, или просто цена', null=True, blank=True)

    # Категория книги
    categoryBook = models.ForeignKey(category_book, on_delete=models.CASCADE, blank=True, null=True, verbose_name='Выберите категорию книги')
    
    def __str__(self):
        return self.titleBook
    
    class Meta:
        verbose_name = 'Книга'
        verbose_name_plural = 'Книги'

Эта модель описывает данные, которые мы будем собирать для каждой книги. Она включает поля для хранения изображений, названий, авторов, ссылок, цен и связи с категорией. Поля oldPricesBook и newPricesBook предназначены для отображения изменения цен, что полезно для отслеживания скидок.

Шаг 2: Реализация функции транслитерации и генерации слага

Для автоматической генерации слага на основе названия категории нам понадобится функция транслитерации. Она преобразует кириллицу в латиницу, удаляет ненужные символы и форматирует текст для использования в URL.

TRANSLIT_DICT = {
    'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
    'е': 'e', 'ё': 'e', 'ж': 'zh', 'з': 'z', 'и': 'i',
    'й': 'i', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
    'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
    'у': 'u', 'ф': 'f', 'х': 'h', 'ц': 'ts', 'ч': 'ch',
    'ш': 'sh', 'щ': 'sch', 'ъ': '', 'ы': 'y', 'ь': '',
    'э': 'e', 'ю': 'yu', 'я': 'ya'
    # Добавлены и заглавные буквы
}

def custom_slugify(text):
    transliterated_text = ''.join(TRANSLIT_DICT.get(c, c) for c in text)
    sanitized_text = transliterated_text.replace("'", "").replace('"', "").replace(',', '').replace(';', '').replace(':', '')
    return sanitized_text.replace(' ', '-').lower()

Эта функция сначала транслитерирует текст на основе словаря TRANSLIT_DICT, затем удаляет символы, которые могут вызвать проблемы в URL, и заменяет пробелы на дефисы.

Шаг 3: Реализация парсера книг с поддержкой категорий и фильтров

Основная логика парсера реализована в классе views_pages. Этот класс позволяет настроить парсер для работы с конкретной категорией, определяя URL, количество книг и тип сортировки.

import requests

from bs4 import BeautifulSoup

from book.models import book_pages, category_book


 

class views_pages:

   

    def __init__(self, category):

        self.category_book_item = category

        self.base_url = category.urlCategory

        self.all_book = category.allCategoryBook

        self.nums_book = category.calcCategoryBook

        self.sort = category.filterCategoryBook

 

       

   

   

    def number_str(self):

        print(self.category_book_item, self.base_url)

        if self.all_book != True:

            html = requests.get(self.base_url).text

            soup = BeautifulSoup(html, 'html.parser')

            nums = soup.find_all('span', class_='pagination__text')

            last_nums_pages = None

           

            for nums_links in nums:

                last_nums_pages = nums_links

                nums_pages = last_nums_pages.get_text(strip=True)

            print('Выбрано несколько страниц', nums_pages)

        else:

            nums_pages = 1

            print('Выбрана одна страница')

           

        return nums_pages

   

   

    def parser_boock(self, nums_pages):

        sort_options = {

            'all_book': '',

            'popular': '&sortPreset=popularity',

            'news': '&sortPreset=newness',

            'reviews': '&sortPreset=reviewsDesc',

            'bestseller': '&filters%5BonlyBestseller%5D=1',

            'reiting': '&filters%5BratingStars%5D=4&filters%5BratingStars%5D=5',

        }

 

        sort_param = sort_options.get(self.sort, '')

       

        for pages in range(1, int(nums_pages)+1):

           

            base_url_patterns = self.base_url + '?page=' + str(pages) + sort_param

           

               

            html = requests.get(base_url_patterns, timeout=10).text

            soup = BeautifulSoup(html, 'html.parser')

            articls = soup.find_all('article', class_='product-card product-card product')

            count = 0

            print(base_url_patterns)

               

            for i, items in enumerate(articls):

                if self.nums_book != 0:

                    if count < int(self.nums_book):

                        url = items.find('a', class_='product-card__picture')

                        url_print = url.get('href')

                        url_final = str('https://www.chitai-gorod.ru' + str(url_print))

                       

                       

                       

                        title = soup.find_all('div', class_='product-title__head')[i]

                        title_book = title.get_text(strip=True)

                       

                        img = soup.find_all('img', class_='product-picture__img')[i]

                        img_book = img.get('data-srcset')

                        if img.get('data-srcset'):

                            img_str = img_book.split('?width=400&height=560&fit=bounds 2x')

                            img_str_link = img_str[0]

                        else:

                            img_str = img.get('src')

                            img_str_link = img_str

                       

                       

                       

                        old_prices = items.find('div', class_='product-price__old')

                       

                        if old_prices:

                            old_prices_text = old_prices.get_text(strip=True)

                        else:

                            old_prices_text = ''

                           

                        sale_prices = items.find('div', class_='product-price__value')

                       

                        if sale_prices:

                            sale_prices_text = sale_prices.get_text(strip=True)

                        else:

                            sale_prices_text = ''

                       

                       

                       

                        autor = items.find('div', class_='product-title__author')

                        if autor:

                            autor_text = autor.get_text(strip=True)

                        else:

                            autor_text = 'Автор не указан'

                       

                        if title_book:

                            bookSite = book_pages.objects.filter(titleBook=title_book).first()

                           

                            if bookSite:

                                if (bookSite.autorBook != autor_text or

                                    bookSite.linkBook != url_final or

                                    bookSite.oldPricesBook != old_prices_text or

                                    bookSite.newPricesBook != sale_prices_text or

                                    bookSite.categoryBook != self.category_book_item):

                                   

                                    bookSite.imgBook = img_str_link

                                    bookSite.autorBook = autor_text

                                    bookSite.linkBook = url_final

                                    bookSite.oldPricesBook = old_prices_text

                                    bookSite.newPricesBook = sale_prices_text

                                    bookSite.categoryBook = self.category_book_item

                                    bookSite.save()

                                else:

                                    print("Запись уже содержит актуальные данные.")

                            else:

                                book_pages.objects.create(

                                    imgBook=img_str_link,

                                    titleBook=title_book,

                                    autorBook=autor_text,

                                    linkBook=url_final,

                                    oldPricesBook=old_prices_text,

                                    newPricesBook=sale_prices_text,

                                    categoryBook = self.category_book_item,

                                )

 

                        else:

                            print('Одно из полей не имеет значений')

                       

                        count += 1

                    else:

                        break

                   

               

                elif self.nums_book == 0:

                    url = items.find('a', class_='product-card__picture')

                    url_print = url.get('href')

                    url_final = str('https://www.chitai-gorod.ru' + str(url_print))

                   

                   

                   

                    title = soup.find_all('div', class_='product-title__head')[i]

                    title_book = title.get_text(strip=True)

                   

                    img = soup.find_all('img', class_='product-picture__img')[i]

                    img_book = img.get('data-srcset')

                    if img.get('data-srcset'):

                        img_str = img_book.split('?width=400&height=560&fit=bounds 2x')

                        img_str_link = img_str[0]

                    else:

                        img_str = img.get('src')

                        img_str_link = img_str

                   

                   

                   

                    old_prices = items.find('div', class_='product-price__old')

                   

                    if old_prices:

                        old_prices_text = old_prices.get_text(strip=True)

                    else:

                        old_prices_text = ''

                       

                    sale_prices = items.find('div', class_='product-price__value')

                   

                    if sale_prices:

                        sale_prices_text = sale_prices.get_text(strip=True)

                    else:

                        sale_prices_text = ''

                   

                   

                   

                    autor = items.find('div', class_='product-title__author')

                    if autor:

                        autor_text = autor.get_text(strip=True)

                    else:

                        autor_text = 'Автор не указан'

                   

                    if title_book:

                        bookSite = book_pages.objects.filter(titleBook=title_book).first()

                       

                        if bookSite:

                            if (bookSite.autorBook != autor_text or

                                bookSite.linkBook != url_final or

                                bookSite.oldPricesBook != old_prices_text or

                                bookSite.newPricesBook != sale_prices_text or

                                bookSite.categoryBook != self.category_book_item):

                               

                                bookSite.imgBook = img_str_link

                                bookSite.autorBook = autor_text

                                bookSite.linkBook = url_final

                                bookSite.oldPricesBook = old_prices_text

                                bookSite.newPricesBook = sale_prices_text

                                bookSite.categoryBook = self.category_book_item

                            else:

                                print("Запись уже содержит актуальные данные.")

                        else:

                            book_pages.objects.create(

                                imgBook=img_str_link,

                                titleBook=title_book,

                                autorBook=autor_text,

                                linkBook=url_final,

                                oldPricesBook=old_prices_text,

                                newPricesBook=sale_prices_text,

                                categoryBook = self.category_book_item,

                            )

 

                    else:

                        print('Одно из полей не имеет значений')

                else:

                    print('Значение не корректное')

        return

 

    def run(self):

        nums_pages = self.number_str()

        data = self.parser_boock(nums_pages)

 

def import_books(category):

    parser = views_pages(category)

    parser.run()

Шаг 5: Настройка URL маршрутизации

Для того чтобы пользователи могли взаимодействовать с вашим приложением, необходимо настроить URL маршрутизацию. Мы добавим URL-шаблоны для управления категориями и книгами.

Настройка URL для управления книгами

Создайте файл urls.py в вашем приложении, если его еще нет, и добавьте следующие маршруты:

from django.urls import path
from .views import CategoryListView, BookListView, CategoryDetailView, BookDetailView

urlpatterns = [
    path('', CategoryListView.as_view(), name='category-list'),
    path('category/<slug:slug>/', CategoryDetailView.as_view(), name='category-detail'),
    path('book/<int:pk>/', BookDetailView.as_view(), name='book-detail'),
]

 

Этот код настраивает следующие маршруты:

  • category-list — для отображения списка всех категорий.
  • category-detail — для отображения книг в конкретной категории.
  • book-detail — для просмотра подробностей о книге.
Настройка URL для административной панели

В файле urls.py проекта добавьте маршрут для административной панели, если он еще не добавлен:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', include('book.urls')),  # Подключение маршрутов для приложения books
]

 

Шаг 6: Создание представлений (Views)

Создайте представления для обработки запросов пользователей. Мы будем использовать класс-based views (CBV) для упрощения работы.

Представления для категорий и книг

Создайте файл views.py в вашем приложении и добавьте следующие классы:

from django.views.generic import ListView, DetailView
from .models import category_book, book_pages

class CategoryListView(ListView):
    model = category_book
    template_name = 'category_list.html'
    context_object_name = 'categories'

class CategoryDetailView(ListView):
    model = book_pages
    template_name = 'category_detail.html'
    context_object_name = 'books'

    def get_queryset(self):
        return book_pages.objects.filter(categoryBook__slugCategory=self.kwargs['slug'])

class BookDetailView(DetailView):
    model = book_pages
    template_name = 'book_detail.html'
    context_object_name = 'book'

Шаблоны для представлений

Создайте папку templates внутри вашего приложения и добавьте следующие шаблоны:

  1. category_list.html

{% extends "base.html" %}

{% block content %}
    <h1>Категории книг</h1>
    <ul>
        {% for category in categories %}
            <li><a href="{% url 'category-detail' slug=category.slugCategory %}">{{ category.titleCategory }}</a></li>
        {% endfor %}
    </ul>
{% endblock %}

2. category_detail.html

{% extends "base.html" %}

{% block content %}
    <h1>Книги в категории "{{ category.titleCategory }}"</h1>
    <ul>
        {% for book in books %}
            <li>
                <a href="{% url 'book-detail' pk=book.pk %}">
                    <img src="{{ book.imgBook }}" alt="{{ book.titleBook }}">
                    {{ book.titleBook }} - {{ book.newPricesBook }}
                </a>
            </li>
        {% endfor %}
    </ul>
{% endblock %}

3. book_detail.html

{% extends "base.html" %}

{% block content %}
    <h1>{{ book.titleBook }}</h1>
    <img src="{{ book.imgBook }}" alt="{{ book.titleBook }}">
    <p>Автор: {{ book.autorBook }}</p>
    <p>Старая цена: {{ book.oldPricesBook }}</p>
    <p>Новая цена: {{ book.newPricesBook }}</p>
    <a href="{{ book.linkBook }}" target="_blank">Посмотреть книгу</a>
{% endblock %}

Шаг 7: Тестирование и отладка

Теперь, когда основная структура готова, вам нужно протестировать систему:

  1. Проверьте модели: Убедитесь, что модели правильно сохраняют и обновляют данные. Попробуйте создать несколько записей через административную панель и убедитесь, что они отображаются в вашем приложении.

  2. Проверьте парсер: Запустите парсер и проверьте, что он правильно собирает данные. Убедитесь, что данные отображаются корректно на веб-страницах.

  3. Проверьте представления и шаблоны: Убедитесь, что представления правильно обрабатывают запросы, и шаблоны корректно отображают данные.

  4. Тестирование в разных браузерах: Проверьте отображение страниц в различных браузерах, чтобы убедиться, что все работает корректно.

Шаг 8: Улучшения и расширения

Когда основная система готова, вы можете подумать о следующих улучшениях:

  • Поддержка дополнительных фильтров: Расширьте систему фильтрации книг, добавив больше опций для сортировки и фильтрации.

  • Оптимизация производительности: Оптимизируйте парсер для повышения производительности и минимизации времени обработки.

  • Интерфейс пользователя: Улучшите интерфейс пользователя, добавив больше стилей и интерактивных элементов.

  • Аналитика и отчеты: Добавьте функциональность для сбора и отображения аналитики, такой как количество книг в каждой категории, популярность книг и т.д.

  • Автоматическое обновление данных: Настройте регулярные задачи для автоматического обновления данных о книгах.

Эти улучшения помогут сделать вашу систему более гибкой и мощной, предоставляя пользователям лучшие возможности для работы с книгами и категориями.

Написать комментарий

Вы можете оставить комментарий автору статьи Обязательные поля помечены *