Як я створив чат-бот, аби порівняти ціни в магазинах

💡 Усі статті, обговорення, новини про AI — в одному місці. Приєднуйтесь до AI спільноти!

Хочу тут поділитися результатами дослідження, який супермаркет для щодижневих закупок продуктів найкращий для мене. Відразу скажу, що це моя суб’єктивна думка (або дружини, потрібно в неї уточнити 🙃). А досліджували:

  • ціни на продукти;
  • зручність.

Врахововуючи ці параметри, було вибрано 4 супермаркета, які знаходилися найближче до нас (м. Вінниця). Забігаючи на перед скажу, що це важливо, тому що ціни різних магазинів однієї мережі можуть відрізнятися, навіть одного міста. А саме:

  • Грош;
  • Сільпо;
  • АТБ;
  • Метро.

Після того, як було вибрано магазини, потрібно було дістати дані з цих магазинів.

Як я діставав дані з потрібного магазину

Так, як я вже описував, що мені потрібні ціни конкретного магазину (який знаходиться ближче до мене). Тут я почав аналізувати запити і знайшов, що для Сільпо і Метро кожен магазин має свою ідішку, яка передається в запиті (я тут не буду показувати запити на ці сайти, тому що я намагаюся цінувати їхні ресурси і не хотів би, щоб їх спамили).

А із АТБ виявилося цікавіше, я не знайшов ніяку ідішку магазину, але знайшов що в куках є привязка до магазину, тому я просто взяв сесію з браузера і використав її. І ще додав як на мене важливу деталь, а саме я діставав дані з доставок, тобто якщо ви подивитися то є сайт магазину а є сайт магазину на якому ви можете зробити доставку (якщо на прикладі то для Метро, я діставав дані з Zakaz). І орієнтуючись на те що я побачив то є різниця між ціною в онлайн/інтернет магазині і в офлайн магазині.

Ідея спарсити всі ціни на товар

Я надіявся, що це дасть мені відповідь, де купивати і я щасливий скажу дружині, що от тут найкраще і не потрібно мені дякувати за це 😊. Але реальність була трохи іншою.

Отже для того, щоб дістати всі дані я використав Python/Scrapy.

Порівнявши ціни для Сільпо і Метро я зрозумів що не все так просто як здавалося на перший погляд. Ось що я побачив — www.kaggle.com/...​makyn/compare-food-prices.

І ще я збираю дані напочатку місяця щоб потім подивитися на тенденцію, але це можливо тема наступної статті — www.kaggle.com/...​tasets/dimakyn/food-price

З якими проблемами я зіткнувся:

  • кожен магазин має багато власних товарів (товари, які продаються тільки в цій мережі);
  • різні упаковки товару (наприклад в одному магазині консерва 200г а в іншому 230г);
  • акції.

І тут виникло питання: а як же порівнювати ціни? Потрібна нормалізація.

Спроби нормалізації

Оцінивши те, що я отримав, я вирішив мерджети товари (тобто порівнювати тільки ті товари, які є в різних магазинах).

Що я спробував (детально не буду розповідати, тому що це не є темою моєї розповіді):

  • мерджети по фото — для мене то не спрацювало;
  • мерджети по назві, категорії, виробнику і фото — для мене не спрацювало;
  • мерджети по назві, категорії, виробнику, ціна (різниця не більше 20%) — це дало результат, але потрібно була ручна перевірка, а на це потрібно було багато часу (~13 тисяч товарів і на перевірку 1000 потрібно було ~1 день). Тому це також не зовсім те що можна було використати.
  • створити «basket», тобто порівнювати ті товари, які зазвичай купуєш в мазазині — було вибрано 15 товарів і на основі них продовжилося дослідження.

Порівняння цін

Одноразовий скан не дуже інформативний, особливо на 15 товарів. Тому почався пошук щоденого скану (уточнення, щоденного безкоштовного скану). Якщо хтось запитає про проксі, то я не ставив собі за мету зробити постійний скан, мета тільки дослідити, тому проксі не використовувалося (чи це вплинуло на дослідження, однозначно я сказати не можу, але те що я спостерігав то малоймовірно).

Щоб мати можливість сканувати кожен день я вирішив використати kaggle notebook із налаштуванням scheduler, там я можливість щоденого запуску скріпта, але обмеження в тому що час запуску вибираєш не ти, а система (для мене це було десь в районі 10 години вечора за Києвом), але як на мене і мого дослідження те що це безкоштовно була явною перевагою, і відносно просто для налаштування також є перевагою.

Ніби все добре скан біжить дані є але кожен раз заходити і дивитися на таблицю не дуже зручно. Тому це потрібно було вирішити. Зупинився я на телеграмі, тобто була створена група і в цій групі було повідомлення у вигляді таблиці, з цінами (яку відправляв тей ж kaggle notebook).

Тобто matplotlib i pandas створювали картинку таблиці, а бот відправляв через API картинку на канал і дані я вже міг візуально оцінити:

Цей код підготовлює дані, також створює картинку і зберігає її:

df_plot = df.pivot(index='names', columns='provider', values='price').reset_index()
df_mean = df_plot[["atb", "grosh", "metro", "silpo"]]
df_mean["mean"] = df_mean.mean(axis=1)
df_plot.loc[len(df_plot.index)] = [
    "Saving", 
    round((1 - df_mean["atb"]/df_mean["mean"]).mean(), 3)*(-1),
    round((1 - df_mean["grosh"]/df_mean["mean"]).mean(), 3)*(-1),
    round((1 - df_mean["metro"]/df_mean["mean"]).mean(), 3)*(-1),
    round((1 - df_mean["silpo"]/df_mean["mean"]).mean(), 3)*(-1)
]
fig, ax = plt.subplots(figsize=(12, int(0.4*df_plot.shape[0])))
ax.axis('off')  # Turn off axis labels
values = df_plot.values
df_shape = values.shape
colors = np.full(df_shape, "#ffffff", dtype=np.dtype(object))
for i in range(df_shape[0]):
    if i%2 == 0:
        colors[i][0] = "#dbd8d7"
    try:
        colors[i][np.nanargmax(values[i][1:])+1] = "#edb2a4"
        colors[i][np.nanargmin(values[i][1:])+1] = "#96e596"
    except Exception as err:
        print(f"[ERROR] => {err}") 
        
# Iterate through the array and replace NaN with empty string
for i in range(values.shape[0]):
    for j in range(values.shape[1]):
        if isinstance(values[i, j], float) and np.isnan(values[i, j]):
            values[i, j] = ''
pd.merge(df_plot, df[['unit', 'image', 'quantity', 'producer', 'categories', "names"]], how="left", on="names").drop_duplicates("names").to_csv("save_plot_df.csv", index=False)
table = plt.table(cellText=values, colLabels=df_plot.columns, cellLoc='center', cellColours=colors, loc='center')
table.auto_set_font_size(True)
table.set_fontsize(12)
table.scale(1, 1.5)# Adjust the table size if needed
table.auto_set_column_width([0, 1, 2, 3, 4])
# Get the current date and time
current_datetime = datetime.now()
# Add 3 hours to the current datetime
new_datetime = current_datetime + timedelta(hours=3)
# Format the new datetime as a string
new_datetime_str = new_datetime.strftime('%Y-%m-%d %H:%M:%S')
# Add the updated datetime as a title
plt.title(f'Date of scanning - {new_datetime_str}', fontsize=14)
plt.savefig('table.png')

Після цього відправляється повідомлення в телеграм:

def send_photo(chat_id, file_opened):
    method = "sendPhoto"
    params = {'chat_id': chat_id}
    files = {'photo': file_opened}
    resp = requests.post("https://api.telegram.org/bot<telegram_bot_token>:<chat_id>>/  sendPhoto", params, files=files)
    return resp


send_photo(<chat_id>, open("/kaggle/working/table.png", 'rb'))

І приклад повідомлення в телеграмі:

Висновок

Провівши моніторинг три місяці з 14 листопада до 14 лютого, ми з дружиною створили для себе рейтинг магазинів (це дуже суб’єктивний рейтинг, в якому ми не дивилися на акції, а на основне було наявність і ціна ну і сервіс):

  1. Сільпо
  2. Метро
  3. АТБ
  4. Грош
👍ПодобаєтьсяСподобалось5
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Я трохи часу зекономлю — можна без проблем їхати у метро за товарами, бо вони майже завжди у наявності, у більшому асортименті, інколи зі знижками на базову ціну або знижками за придбання у великій кількості. Або, наприклад, придбання кілограмових пачок кисломолочного сиру (творог який), ціна на який суттево нижче ніж у пачках по 350 грам.

Плюс, якщо, наприклад, купляти 5 літрів гелю для душу (270) замість мого, раніше, улюбленого палмолів (750мл за 260 гривень), дебіт на переплату за пекинську з бананами буде кілограмм на 100.

PS трохи дивує відсутність філе стегон у метро, може це особливість вашого метро, бо я таке бачив лише перед святами, коли вигрібали м’ясний відділ на 80% загального об’єму та до 100% по окремим позиціям, що бувало, не тільки стегон немає, а і філе з крильцями та цілих туш курей та качок, лише поодинокі шматки бідолашних індичок та австралійська телятина по 1000 за кілограм.

Відсутність деяких позиціій повязана із мерджом товарів (як було написано кожна нова поставка може створювати новий товар в базі і це постійно потрібно моніторити).
З приводу того що ціни дешевші в метро, я не погоджуюся (навіть із закупкою великої кількості). Хоча з досвіду скажу, що якість фруктів і овочів в Метро краща

Для мене комфорт більш важливий, як і витрати часу на це. Жоден з супермаркетів, окрім Novus біля мене не може запропонувати такий самий рівень комфорту під час продуктового шоппінгу, як Metro, але навіть і він поза конкуренцією через деякі особливості. Власне, єдине, що мені там подобається — це випічка (яку я перестав купляти, бо купив хлібопіч) та піцца-напівфабрикат. Я перестав відвідувати сільпо, еко, в атб я лише кілька разів за життя був.

Асортимент м’яса, молочки (молоко, наприклад я купляю ящиками, і це буквально, от у кавоварці молоко закінчилось, буду сьогодні-завтра брати 3-4 ящика по 12 пачок) та зелені — не залишає іншого вибору. Окрім того, придбання їжи та іншого — це не завжди «пройтись за списком і купити все по ньому», це і постійне оновлення бази даних у мізках у пошуках чьогось новенького та цікавого. Наприклад, знижки на фісташки, або відкриття для себе нових товарних позицій. Або, наприклад, вичепити ящик апельсинів зі знижкою, або відкрити для себе, що поруч є магазин, у якому продають за копійки гель для душу у 5 літрових баклажках...

PS metro.zakaz.ua/...​ukrayina—04823065729909 3 кілограми за 449 гривень, або 149 гривень за кіло, у вашому списку яготинський — 193 за кілограм. Це 44.9 гривень різниця, або 897 кілограмів бананів, якщо взяти 5 копійок різниці на кілограмі. Я його купляю і кидаю у морозилку з −20 всередині.

На мою думку, потрібно взяти до уваги кількість продуктів та мінімальний threshhold у %% та сумі, бо 5 копійок різниця — взагалі не варта уваги, наприклад. Плюс, якщо відвідувати додатковий магазин — це коштує якоїсь суми грошей, наприклад, якщо є авто — потрібно додати вартість бензину або кіловат плюс якусь вартість грошей на додатковий час, скажемо 50 гривень. А після чого порахувати, чи варто заради економії у кілька гривень (якщо вона взагалі буде) відвідувати додатковий магазин, аби зекономити 5 копійок на бананах, можливо, що є сенс раз на тиждень закупитись свіжими овочами і раз на місяць робити великі закупки у одному магазину.

PS2 вибачте, ви зробили чудову роботу, звичайно. Все описане вами я вже проходив коли був молодше, з часом, особистий комфорт та, непомітні та незначні, на той момент речі, почали брати гору. Так що без образ, поділився власними спостереженням та «пройденим шляхом», можливо щось з цього стане вам у нагоді, хоча це все персональне.

Я пару тижнів тому придбав ящик вершкового масла у метро, цього мені вистачить на рік (ладно, меньше, бо я брав для мами, а вона пече різні речі).

Ось скріншот bdfss.blob.core.windows.net/...​530-8808-950ce10dfc52.jpg

Ціна у атб 90 гривень за пачку яготинського, тільки що перевірив через zakaz.ua.
Вибачте, що не одинакові товарні позиції, але як є.
Різниця на пачці — 25.14 гривень.

Дякую за корисну річ!
Роки 4 тому була новина про застосунок з цінами всіх популярних мереж. Вже не памʼятаю, як він називався. На релізі були проблеми з мерджанням назв продуктів. Одних «помідорів» було штук 10 різних.

Мерчендайзінгові агенства за таку базу в чергу встануть )

Проблема такої бази, що вона досить динамічна

В великих торгових компанія куча народу займається збором даних конкурентів — цін, асортименту та інш.

Круто. Ще ідея — перевірка на ’реальну знижку’, але треба регулярно парсити ціни

Підписатись на коментарі