import scrapy
import pandas as pd
import os


class KentekSpider(scrapy.Spider):
    name = 'kentek'
    start_urls = ['https://kentek.ru/catalog/']

    def parse(self, response):
        self.logger.info('Parsing main catalog page')
        # Получаем ссылки на главные категории
        main_categories = response.css(
            'div.product--list a::attr(href)').getall()
        self.logger.info(f'Found main categories: {main_categories}')
        for main_category in main_categories:
            yield response.follow(main_category, callback=self.parse_subcategories)

    def parse_subcategories(self, response):
        self.logger.info('Parsing subcategories page')
        # Получаем ссылки на подкатегории
        subcategories = response.css('div.blocks a::attr(href)').getall()
        self.logger.info(f'Found subcategories: {subcategories}')
        for subcategory in subcategories:
            yield response.follow(subcategory, callback=self.parse_products)

    def parse_products(self, response):
        self.logger.info('Parsing products page')
        # Собираем данные о продуктах и ссылки на страницы продуктов
        products = response.css('div.product-item a::attr(href)').getall()
        self.logger.info(f'Found products: {products}')
        for product_link in products:
            yield response.follow(product_link, callback=self.parse_product_details)

        # Обработка пагинации
        next_page = response.css('a.next::attr(href)').get()
        if next_page is not None:
            yield response.follow(next_page, callback=self.parse_products)

    def parse_product_details(self, response):
        self.logger.info('Parsing product details page')
        # Получаем данные о товаре и аналогах
        category_name = response.css(
            'ul.breadcrumb li a span::text').getall()[-1]
        subcategory_name = response.css(
            'ul.breadcrumb li a span::text').getall()[-2]

        product_data = {
            'Название категории': category_name,
            'Подкатегория': subcategory_name,
            'Название товара': response.css('h1.npc__name::text').get(),
            'Бренд': response.css('.npc__value a::text').get(),
            'Артикул': response.css('span.npc__key + span::text').get(),
            'Цена': response.css('div.npc__price::text').get(),
        }

        # Сбор аналогов товаров
        analogs = response.css('tr.result-table__row')
        analog_data = []
        prices = []
        for analog in analogs:
            analog_info = {
                'Название товара': analog.css('.result-table__info-description::text').get(),
                'Бренд товара': analog.css('.result-table__info-brand span::text').get(),
                'Цена': analog.css('.result-table__part-cell.price::text').get(),
            }
            price = analog.css('.result-table__part-cell.price::text').get()
            if price:
                price = price.replace(' ₽', '').replace(
                    ' ', '').replace(',', '.')
                prices.append(float(price))
            analog_data.append(analog_info)

        # Сортировка цен от минимума к максимуму
        prices.sort()
        if prices:
            product_data['Цена'] = f"от {prices[0]} ₽ до {prices[-1]} ₽"

        # Сохранение данных в Excel
        self.save_to_excel(category_name, product_data, analog_data)

    import os
import pandas as pd

def save_to_excel(self, category_name, product_data, analog_data):
    # Создание директории, если она не существует
    result_dir = 'C:/Py/pars_store/kentek/result'
    if not os.path.exists(result_dir):
        os.makedirs(result_dir)
        self.logger.info(f"Created directory: {result_dir}")

    # Создание пути к файлу
    filename = os.path.join(result_dir, f"{category_name}.xlsx")
    self.logger.info(f"Saving data to file: {filename}")

    try:
        if os.path.exists(filename):
            self.logger.info(f"File {filename} already exists, appending data.")
            writer = pd.ExcelWriter(filename, engine='openpyxl', mode='a', if_sheet_exists='new')
        else:
            self.logger.info(f"File {filename} does not exist, creating new file.")
            writer = pd.ExcelWriter(filename, engine='openpyxl')

        # Сохранение информации о продукте
        df_product = pd.DataFrame([product_data])
        df_product.to_excel(writer, sheet_name='Products', index=False)
        self.logger.info(f"Saved product data to sheet 'Products' in file {filename}")

        # Сохранение информации об аналогах
        df_analogs = pd.DataFrame(analog_data)
        df_analogs.to_excel(writer, sheet_name='Analogs', index=False)
        self.logger.info(f"Saved analog data to sheet 'Analogs' in file {filename}")

        writer.save()
        writer.close()
        self.logger.info(f"Data successfully saved to {filename}")

    except Exception as e:
        self.logger.error(f"Error saving data to {filename}: {e}")

При выполнении моего паука по команде 'scrapy crawl kentek' файл не создаётся, данные не выгружаются.

В выводе получаю:
2024-08-22 14:27:35 [kentek] INFO: Found products: []
2024-08-22 14:27:35 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/oil-filters/oil-kentek/> (referer: https://kentek.ru/catalog/filters/oil-filters/)
2024-08-22 14:27:35 [kentek] INFO: Parsing products page
2024-08-22 14:27:35 [kentek] INFO: Found products: []
2024-08-22 14:27:35 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/oil-filters/oil-filtron/> (referer: https://kentek.ru/catalog/filters/oil-filters/)
2024-08-22 14:27:35 [kentek] INFO: Parsing products page
2024-08-22 14:27:35 [kentek] INFO: Found products: []
2024-08-22 14:27:36 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/fuel-filters/fuel-filtron/> (referer: https://kentek.ru/catalog/filters/fuel-filters/)
2024-08-22 14:27:36 [kentek] INFO: Parsing products page
2024-08-22 14:27:36 [kentek] INFO: Found products: []
2024-08-22 14:27:36 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/fuel-filters/fuel-parker/> (referer: https://kentek.ru/catalog/filters/fuel-filters/)
2024-08-22 14:27:36 [kentek] INFO: Parsing products page
2024-08-22 14:27:36 [kentek] INFO: Found products: []
2024-08-22 14:27:36 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/fuel-filters/toplivnye-filtry-mann-filter/> (referer: https://kentek.ru/catalog/filters/fuel-filters/)
2024-08-22 14:27:36 [kentek] INFO: Parsing products page
2024-08-22 14:27:36 [kentek] INFO: Found products: []
2024-08-22 14:27:37 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/fuel-filters/fuel-baldwin/> (referer: https://kentek.ru/catalog/filters/fuel-filters/)
2024-08-22 14:27:37 [kentek] INFO: Parsing products page
2024-08-22 14:27:37 [kentek] INFO: Found products: []
2024-08-22 14:27:37 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/fuel-filters/fuel-donaldson/> (referer: https://kentek.ru/catalog/filters/fuel-filters/)
2024-08-22 14:27:37 [kentek] INFO: Parsing products page
2024-08-22 14:27:37 [kentek] INFO: Found products: []
2024-08-22 14:27:37 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/fuel-filters/fuel-kentek/> (referer: https://kentek.ru/catalog/filters/fuel-filters/)
2024-08-22 14:27:37 [kentek] INFO: Parsing products page
2024-08-22 14:27:37 [kentek] INFO: Found products: []
2024-08-22 14:27:38 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://kentek.ru/catalog/filters/fuel-filters/fuel-hi-fi/> (referer: https://kentek.ru/catalog/filters/fuel-filters/)
2024-08-22 14:27:38 [kentek] INFO: Parsing products page
2024-08-22 14:27:38 [kentek] INFO: Found products: []
2024-08-22 14:27:38 [scrapy.core.engine] INFO: Closing spider (finished)
2024-08-22 14:27:38 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 75210,
 'downloader/request_count': 170,
 'downloader/request_method_count/GET': 170,
 'downloader/response_bytes': 15260595,
 'downloader/response_count': 170,
 'downloader/response_status_count/200': 170,
 'elapsed_time_seconds': 57.027823,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2024, 8, 22, 11, 27, 38, 128186, tzinfo=datetime.timezone.utc),
 'httpcompression/response_bytes': 8823866,
 'httpcompression/response_count': 50,
 'log_count/DEBUG': 173,
 'log_count/INFO': 350,
 'request_depth_max': 2,
 'response_received_count': 170,
 'scheduler/dequeued': 170,
 'scheduler/dequeued/memory': 170,
 'scheduler/enqueued': 170,
 'scheduler/enqueued/memory': 170,
 'start_time': datetime.datetime(2024, 8, 22, 11, 26, 41, 100363, tzinfo=datetime.timezone.utc)}
2024-08-22 14:27:38 [scrapy.core.engine] INFO: Spider closed (finished)