← Все статьи
Riders AgencyТексты и контент-редакторы

Похожие туры без ручного подбора: рекомендательная система Adventure Guide

Для Adventure Guide автоматизировали подбор похожих туров: подготовили признаки каталога, рассчитали сходство в Python и связали сервис FastAPI с 1С-Битрикс. В статье — задача, данные, код и границы полученного результата.

Содержание 8

Зачем туристическому сайту рекомендации

Adventure Guide предлагает активные туры по России: вертолётные маршруты над вулканами, сплавы, глэмпинг на Алтае и другие путешествия с сервисом, едой и новыми знакомствами.

Миссию компании автор связывает со словами Гилберта Кита Честертона из эссе «Загадка плюща»: «Высшая цель путешествия не в том, чтобы увидеть чужую страну, а в том, чтобы увидеть свою страну как чужую».

На момент написания статьи в каталоге было около тысячи туров. На каждой странице блок похожих предложений помогал выбрать альтернативу. Контент-менеджер подбирал их вручную и поддерживал свойство «Похожие туры» более чем на тысяче страниц 1С-Битрикс. Этот повторяющийся процесс мы и решили автоматизировать.

Задача: найти похожесть по совокупности признаков

На вход система получает идентификатор тура, на выходе — список похожих предложений. Одного признака для этого мало.

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

Для такого подбора мы использовали методы машинного обучения. В исходной статье для масштаба задачи приведён Netflix Prize — конкурс улучшения прогнозов пользовательских оценок с призом в миллион долларов. Это пример сложности рекомендаций, а не результат или клиентский проект Riders.

Какие данные вошли в модель

Основа решения — таблица признаков туров. Данные уже были структурированы в базе, поэтому подготовку выполняли на Python без отдельной ручной разметки каталога.

Автор оценивает подготовку датасета примерно в 80% работы специалистов при неструктурированных данных; это общая оценка из исходной статьи, а не измеренная трудоёмкость данного проекта.

Использованные параметры:

  • Цена, длительность в днях и сложность.
  • Месяц проведения.
  • Тип проживания: турбаза или гостевой дом, гостиница, корабль или каюта, палатка.
  • Раздел каталога, регион и виды активности.
  • Конкретные турбазы, отели и корабли.
  • Оператор тура, широта и долгота, количество людей.
  • Идентификатор похожего тура, ранее выбранного менеджером.

У одного тура может быть несколько активностей и регионов. Например, «Камчатский калейдоскоп» сочетает разные сценарии. Такие признаки разнесли по отдельным столбцам с помощью pandas: условно, «есть в Адыгее» и «есть на Кавказе». В результате получилась широкая таблица, пригодная для расчёта сходства.

Python
# Основной датасет
dataset = []

# Колонки
columns = ['id', 'price', 'duration', 'complexity']

# Месяца
for item in range(0, 12):
    columns.append('month_' + str(item+1))

# Места проживания
for item in range(0, 4):
    columns.append('place_' + str(item+1))

# Разделы
cursor.execute(
    'select ID from b_iblock_section where IBLOCK_ID = 1')
for result in cursor.fetchall():
    columns.append('section_' + str(result['ID']))

# Заполняем привязанные свойства из Битрикса
bitrix_fillment_columns = {'region': 3, 'type': 4, 'place_id': 11, 'ship': 9, 'complexity': 15, 'operator': 35}

Как рассчитали сходство

Команда проверяла варианты несколько дней. Для этого набора данных подошёл расчёт косинусного сходства cosine_similarity из sklearn.metrics.pairwise.

В примере ниже данные загружаются из CSV, пропуски заполняются нулями, выбранные признаки масштабируются через StandardScaler, затем вычисляется матрица сходства. Функция возвращает идентификаторы близких туров, исключая исходный тур.

  • По словам автора, обратить внимание на этот подход помог ChatGPT. Это сохранённая часть истории разработки, а не добавленная задним числом связь с ИИ.
Python
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

# load the dataset
df = pd.read_csv('dataset_all.csv')
df.fillna(0, inplace=True)

# select the relevant features for the content-based algorithm
#X = df[['price', 'region', 'style']]
X = df.drop(["same_tour_id","children_terms","group_count"], axis=1) # Features


# apply feature scaling if necessary
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# calculate the cosine similarity matrix
cosine_sim = cosine_similarity(X_scaled)

# define a function to get similar tour IDs
def get_similar_tours(tour_id, cosine_sim, df, top_n=10):
    idx = df.loc[df['id'] == tour_id].index[0]
    sim_scores = list(enumerate(cosine_sim[idx]))
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
    sim_scores = sim_scores[1:(top_n+1)]
    tour_indices = [i[0] for i in sim_scores]
    unique_arr = list(set(df['id'].iloc[tour_indices].tolist()))
    unique_arr = [elem for elem in unique_arr if elem != tour_id]
    return unique_arr

# get the similar tour IDs for a given tour ID
similar_tours = get_similar_tours(8227591, cosine_sim, df, top_n=5)
print('Similar tour IDs:', similar_tours)

Python и 1С-Битрикс: связь через FastAPI

Сайт работал на PHP и 1С-Битрикс, а алгоритм был написан на Python. Мы вынесли расчёт в отдельный процесс на FastAPI: он принимает идентификатор тура и возвращает список похожих предложений в JSON. Так сайт обращается к алгоритму через понятный интерфейс, не смешивая два окружения.

Python
@app.get("/same/")
def same(id: int = 0):
"""
Получает id похожих туров
"""

import pandas as pd

# load the dataset
df = pd.read_csv('/var/www/adventure_admin/data/ai/dataset_all.csv')
df.fillna(0, inplace=True)

# select the relevant features for the content-based algorithm
# X = df[['price', 'region', 'style']]
X = df.drop(["same_tour_id", "children_terms", "group_count"], axis=1)  # Features

# apply feature scaling if necessary
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

Обновление рекомендаций при сохранении тура

В 1С-Битрикс добавили обработчик сохранения тура: он запрашивает похожие предложения у сервиса и записывает их идентификаторы в свойство каталога. Ниже — исходный фрагмент PHP.

PHP
# Получаем похожие туры через сервис
$same_tours = file_get_contents ('http://127.0.0.1:7700/same/?id=' . $tour_id);
if (si_is_json($same_tours)) {
	
	$same_tours = json_decode ($same_tours, true);

	if (!empty($same_tours)) {
		$prop = [];
		foreach ($same_tours as $same_id) {
			$prop[] = ['VALUE' => $same_id];
		}
		CModule::IncludeModule('iblock');
		CIBlockElement::SetPropertyValuesEx($tour_id, 1, ['SAME_TOUR_AI_RECOMMENDED' => $prop]);
	}

}

Что автоматизировали и что оставили на развитие

Система заменила ручной подбор похожих предложений и упростила поддержку каталога. По данным автора, это экономит сотрудникам сотни часов в год; методика расчёта времени в исходной статье не приведена.

Дальнейшее направление — ранжирование туров с учётом предпочтений и поведения разных пользователей. В исходном материале это описано как потенциал развития и первые эксперименты, а не как завершённая персонализация для каждого посетителя.

Автоматизация начинается с данных

В этом проекте связка структурированного каталога, расчёта сходства и автоматического обновления в CMS позволила убрать повторяющуюся ручную работу. Такой подход стоит обсуждать там, где рекомендации уже нужны бизнесу и есть данные для их построения.

Источники

Обсуждение

Задайте вопрос об этой работе, обсудите решение или поделитесь своим опытом.

Обсуждение открыто для всех участников.

Загружаем комментарии…