Як я створив чат-бот, аби порівняти ціни в магазинах
Хочу тут поділитися результатами дослідження, який супермаркет для щодижневих закупок продуктів найкращий для мене. Відразу скажу, що це моя суб’єктивна думка (або дружини, потрібно в неї уточнити 🙃). А досліджували:
- ціни на продукти;
- зручність.
Врахововуючи ці параметри, було вибрано 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 лютого, ми з дружиною створили для себе рейтинг магазинів (це дуже суб’єктивний рейтинг, в якому ми не дивилися на акції, а на основне було наявність і ціна ну і сервіс):
- Сільпо
- Метро
- АТБ
- Грош
10 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів