Главная → Автоматизация → кейс «Сверка прайс-листов»
Сверка прайс-листов поставщиков: получасовая рутина превращается в одну команду
демонстрационная работа Я делал её сам, для себя и для портфолио: клиентских задач на сверку у меня пока не было. Код и результат — настоящие, их можно запустить и проверить.
1. Постановка задачи
Небольшая торговая компания получает от трёх-пяти поставщиков файлы с прайсами. Каждый поставщик присылает свою таблицу: у одного «Артикул / Цена / Остаток», у второго «код товара / цена с НДС / кол-во на складе», у третьего вообще всё вперемешку с двумя шапками сверху. Менеджер открывает две вкладки Excel и сравнивает глазами: что подорожало, чего не хватает, где поставщик молча убрал позицию из продажи.
На 400 позиций это полчаса, и всё равно пропускаешь. Задача формулируется в одну строку: на входе два (или больше) прайса, на выходе — список расхождений, который можно показать поставщику.
2. Почему без pandas и openpyxl
Первая мысль — pip install pandas openpyxl. На своей машине это так и работает. Но заказчик —
бухгалтерия или кладовщик на рабочем компьютере, куда админ разрешение на установку не даст, а корпоративная
политика на «голом» Python иногда запрещает что-либо ставить. Значит решение должно запускаться там, где стоит
только интерпретатор.
Отказ от pandas оказался даже полезен: код читает файл сам, поэтому видно, где именно ломается чтение, и не нужно гадать, что сделала чужая библиотека.
3. .xlsx — это обычный zip с XML внутри
Формат xlsx пугает только на вид. Внутри — архив, который можно открыть стандартным zipfile,
а данные лежат в двух файлах: xl/sharedStrings.xml (текстовые значения, чтобы не повторять одно и то же
в каждой ячейке) и xl/worksheets/sheet1.xml (ячейки со ссылками на строки и числами).
# читаем лист и подставляем общие строки — без единой внешней библиотеки
with zipfile.ZipFile(path) as z:
shared = read_shared_strings(z.read("xl/sharedStrings.xml"))
rows = parse_sheet(z.read("xl/worksheets/sheet1.xml"), shared)
# у ячейки есть адрес вида "C7" — по нему расставляем значения по колонкам
Дальше две вещи, о которых обычно забывают: адрес ячейки (C7 — это 3-я колонка 7-й строки, а не
«следующая идущая»), и пропуски: если в строке пустая ячейка, её в XML просто нет, и наивный код сдвигает все
значения влево. Из-за этого цена попадает в колонку «наличие», и сверка врёт.
4. Как скрипт сам находит нужные колонки
Жёстко зашивать «колонка B — это артикул» нельзя: у каждого поставщика свой шаблон, и он меняется. Решение — подсказки в шапке и в значениях:
| Признак | Что ищем | Примеры названий колонок |
|---|---|---|
| Артикул | текст с цифрами, высокая повторяемость уникальных значений | артикул, код товара, sku, item, штрихкод |
| Цена | числа, в шапке слово про деньги | цена, стоимость, цена с ндс, руб, price |
| Наличие | целые числа или слова «нет в наличии» | остаток, кол-во, количество, в наличии, stock |
| Название | самая длинная текстовая колонка | наименование, товар, название, product |
Если колонка не опознана уверенно, скрипт не угадывает молча, а пишет об этом в отчёт: лучше один раз поправить шаблон, чем получить красивый, но враный результат.
5. Ловушка чисел: неразрывный пробел, из-за которого цена перестаёт быть ценой
Это главная причина, по которой «простая сверка» на реальных файлах падает. Поставщики выгружают прайсы из
своих систем, и числа приходят текстом: 1 250,50 — с пробелом в разряде тысяч. Причём это не обычный
пробел U+0020, а неразрывный U+00A0 или тонкий пробел U+202F. Обычный
float() на таком значении бросает исключение, и колонка «цена» молча превращается в мусор.
# нормализация: любые пробелы-разделители -> обычно, запятая -> точка
def to_number(v):
s = str(v).replace("\u00a0", "").replace("\u202f", "").replace(" ", "")
s = s.replace(",", ".").strip("₽. ")
try:
return float(s)
except ValueError:
return None # не число — честно говорим об этом в отчёте
Второй слой той же проблемы: цена «1250.5» и «1250.5000001» — это одна и та же цена. Сравнивать числа напрямую нельзя, сравниваю с допуском в полкопейки и округляю для вывода.
6. Отчёт расхождений: пять типов, а не «красным подсветить»
Менеджеру нужен не дифф двух таблиц, а список, с которым можно идти к поставщику. Поэтому каждая строка получает тип:
| Тип | Что значит | Что с этим делать |
|---|---|---|
PRICE_DIFF | артикул есть в обоих прайсах, цена отличается | спросить, почему подорожало; посчитать дельту |
MISSING | позиции пропали из нового прайса | искать замену или пересогласовывать |
OUT_OF_STOCK | остаток обнулился | снять с витрины, чтобы не принимать заказы |
DUPLICATE | один артикул встречается дважды с разными ценами | вернуть поставщику на исправление — это их ошибка в выгрузке |
EXTRA | новая позиция, которой не было | посмотреть: возможно, стоит взять в ассортимент |
На выходе — .xlsx с зафиксированной шапкой и цветовой маркировкой типа, а если Excel на машине
заказчика не открывается (такое тоже бывает), то же самое уезжает в .csv с BOM, который Excel понимает
без танцев с кодировкой. Проверено на обоих форматах: 7 расхождений, список идентичен.
7. Экономика: за что тут платят
Сверка двух прайсов по 400 позиций — это 30–40 минут работы менеджера. Если таких поставщиков пять и прайсы обновляются раз в неделю, набегает рабочий день в неделю, то есть примерно 4 000–6 000 ₽ в месяц только на переносе цифр глазами. Скрипт стоит один раз 5 000–8 000 ₽ и окупается за месяц. Дальше он полезен не деньгами, а тем, что расхождение замечают в тот же день, а не тогда, когда клиент ушёл к тому, кто дешевле.
8. Выводы
- Хорошая автоматизация — та, которая запускается у заказчика без «а поставьте мне ещё вот это». Стандартной библиотеки хватает на чтение xlsx, сравнение и внятный отчёт.
- Половина времени в таких задачах уходит не на логику, а на грязь данных: неразрывные пробелы, две шапки, пропущенные ячейки, дубликаты артикулов.
- Отчёт должен отвечать на вопрос «что делать», а не «чем отличаются ячейки». Типы PRICE_DIFF / MISSING / OUT_OF_STOCK — это уже решение, а не дифф.
- Если колонку не удалось определить уверенно, честнее сказать об этом, чем угадать.
Нужна такая же сверка на ваши прайсы?
Приложите два файла (хоть по 20 строк) и скажите, что хотите видеть в отчёте. Я верну пилот на ваших данных до оплаты — дальше решите, нужно ли это вам.
Работа написана и проверена 30.10.2026. Полный исходник и пояснительную записку отдаю вместе с заказом; в книге «Сверка прайс-листов на Python без pandas» — та же разборка, но с кодом целиком.