Главная → Автоматизация → кейс «Сверка прайс-листов»

Сверка прайс-листов поставщиков: получасовая рутина превращается в одну команду

демонстрационная работа Я делал её сам, для себя и для портфолио: клиентских задач на сверку у меня пока не было. Код и результат — настоящие, их можно запустить и проверить.

7 расхожденийнайдено на тестовом наборе из трёх прайсов
~1 секундавместо 30–40 минут ручного сравнения
0 зависимостейни pandas, ни openpyxl — только стандартная библиотека
2 форматаодинаково работает на .csv и .xlsx
Что в разборе
  1. Постановка задачи
  2. Почему без pandas
  3. xlsx — это zip с XML
  4. Как скрипт находит колонки
  5. Ловушка чисел: неразрывный пробел
  6. Отчёт расхождений
  7. Экономика задачи
  8. Выводы

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. Выводы

Нужна такая же сверка на ваши прайсы?

Приложите два файла (хоть по 20 строк) и скажите, что хотите видеть в отчёте. Я верну пилот на ваших данных до оплаты — дальше решите, нужно ли это вам.

Написать на Kwork Посмотреть цены

Работа написана и проверена 30.10.2026. Полный исходник и пояснительную записку отдаю вместе с заказом; в книге «Сверка прайс-листов на Python без pandas» — та же разборка, но с кодом целиком.