Парсинг XML прайс-литов поставщиков на Python

Извлекаем цены и остатки товаров из xml-файлов для интернет-магазина

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


Скрипт для парсинга xml будем писать для прайс-листа с сайта inpo.ru. В итоге должны получить документ в виде csv-файла с артикулами, ценами и остатками для дальнейшей загрузки в интернет-магазин на 1С-Bitrix.

Также в скрипте реализуем функционал наценки на товары.

Наглядное видео извлечения данных из xml:

Скрипт парсинга XML

В скрипте будем использовать уже готовые классы на Python загрузки прайс-листов, используя библиотеки requests и запись в csv. Их можно скачать на github.

Алгоритм парсинга xml реализуем следующий:
  1. загрузим XML прайс-лист поставщика в папку data и сформируем новое имя, в котором добавим дату и время скачивания;
  2. извлечем данные, сделаем наценку на товары и выгрузим в csv-файл;
  3. удалим старые прайсы поставщика из папки data.
Установку виртуального окружения и необходимых библиотек расписывать не будем, так как это стандартные действия.
Файл requrements.txt для установки всех библиотек, которые используем для парсинга:
async-generator==1.10
attrs==21.4.0
beautifulsoup4==4.11.1
blinker==1.4
Brotli==1.0.9
certifi==2022.6.15
cffi==1.15.0
charset-normalizer==2.0.12
cryptography==37.0.2
fake-useragent==0.1.11
h11==0.13.0
h2==4.1.0
hpack==4.0.0
hyperframe==6.0.1
idna==3.3
kaitaistruct==0.9
outcome==1.2.0
pyasn1==0.4.8
pycparser==2.21
pyOpenSSL==22.0.0
pyparsing==3.0.9
PySocks==1.7.1
requests==2.28.0
selenium==4.2.0
selenium-wire==4.6.4
sniffio==1.2.0
sortedcontainers==2.4.0
soupsieve==2.3.2.post1
trio==0.21.0
trio-websocket==0.9.2
urllib3==1.26.9
Werkzeug==2.0.3
wsproto==1.1.0
zstandard==0.17.0
Создадим файл нашего будущего скрипта и назовем его price-list-processor.py. Импортируем python библиотеки:
import glob
import os
import xml.etree.ElementTree as ET

from lib.get_data_from_website import GetData
from lib.csv_handler import CsvHandler
import datetime
Установим глобальные переменные:
# Наценка на товары
MARKUP = 1.3
# Максимальное количетво прайс-листов, которые будем хранить в папке
MAX_PRICES_FROM_DIR = 3
# Источник прайс-листа
PRICE_LIST_SOURCE = 'https://inpo.ru/documents/pricelists/pricelist.xml'
# Название выходного файла
FINAL_FILE_NAME = 'cnic_price.csv'
Функции загрузки прас-листа с сайта поставщика и его обработка:
def download_price(url):
    filename = 'cnic_price_' + datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + '.xml'
    GetData(url).download_file('data', filename)

def read_xml_file(file_path: str):
    tree = ET.parse(file_path)
    root = tree.getroot()

    for group1 in root.findall('group'):
        for group2 in group1.findall('group'):
            for item in group2.findall('item'):
                sku = item.find('no').text
                price = float(item.find('price').text)
                warehouse = int(item.find('free').text)
                payload = {
                    'sku': sku,
                    'price': round(price * MARKUP), # Произведем наценку на товар
                    'warehouse': warehouse,
                }
                write_to_csv_file(payload)
Структура xml-файла прайс-листа выглядит так:
Поэтому в функции read_xml_file используем 3 цикла for, чтобы добраться до item. В них вся информация по товарам, которые нам необходимо получить: артикул, цена и остатки. В цикле извлечем все данные и запишем в csv, используя метод write_to_csv_file.

Функция выбора самого актуального файла из папки data:
def get_latest_price(files: list) -> dict:
    latest_price = max(files, key=os.path.getctime)
    file = {
        'latest_price': latest_price,
        'index': files.index(latest_price)
    }
    return file
И функция записи результатов нашей обработки в csv:
def write_to_csv_file(data: dict):
    CsvHandler(FINAL_FILE_NAME).write_to_csv_semicolon(data)
Итоговый скрипт будет таким:
import glob
import os
import xml.etree.ElementTree as ET

from lib.get_data_from_website import GetData
from lib.csv_handler import CsvHandler
import datetime

MARKUP = 1.3
MAX_PRICES_FROM_DIR = 3
PRICE_LIST_SOURCE = 'https://inpo.ru/documents/pricelists/pricelist.xml'
FINAL_FILE_NAME = 'cnic_price.csv'

def download_price(url):
    filename = 'cnic_price_' + datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + '.xml'
    GetData(url).download_file('data', filename)

def read_xml_file(file_path: str):
    tree = ET.parse(file_path)
    root = tree.getroot()

    for group1 in root.findall('group'):
        for group2 in group1.findall('group'):
            for item in group2.findall('item'):
                sku = item.find('no').text
                price = float(item.find('price').text)
                warehouse = int(item.find('free').text)
                payload = {
                    'sku': sku,
                    'price': round(price * MARKUP),
                    'warehouse': warehouse,
                }
                write_to_csv_file(payload)

def get_latest_price(files: list) -> dict:
    latest_price = max(files, key=os.path.getctime)
    file = {
        'latest_price': latest_price,
        'index': files.index(latest_price)
    }
    return file

def write_to_csv_file(data: dict):
    CsvHandler(FINAL_FILE_NAME).write_to_csv_semicolon(data)

def main():
    download_price(PRICE_LIST_SOURCE)
    list_of_files = glob.glob('data/*.xml')
    latest_price = get_latest_price(list_of_files)
    read_xml_file(latest_price.get('latest_price'))


if __name__ == '__main__':
    main()
Готовый xml документ можно скачать на Яндекс Диске.

Необходимы услуги парсинга?