Для Adventure Guide автоматизировали подбор похожих туров: подготовили признаки каталога, рассчитали сходство в Python и связали сервис FastAPI с 1С-Битрикс. В статье — задача, данные, код и границы полученного результата.
Содержание 8
Зачем туристическому сайту рекомендации
Adventure Guide предлагает активные туры по России: вертолётные маршруты над вулканами, сплавы, глэмпинг на Алтае и другие путешествия с сервисом, едой и новыми знакомствами.
Миссию компании автор связывает со словами Гилберта Кита Честертона из эссе «Загадка плюща»: «Высшая цель путешествия не в том, чтобы увидеть чужую страну, а в том, чтобы увидеть свою страну как чужую».
На момент написания статьи в каталоге было около тысячи туров. На каждой странице блок похожих предложений помогал выбрать альтернативу. Контент-менеджер подбирал их вручную и поддерживал свойство «Похожие туры» более чем на тысяче страниц 1С-Битрикс. Этот повторяющийся процесс мы и решили автоматизировать.
Задача: найти похожесть по совокупности признаков
На вход система получает идентификатор тура, на выходе — список похожих предложений. Одного признака для этого мало.
Тур на Сахалине может быть похож на тур на Камчатке, а экскурсия и веломаршрут в одном регионе — различаться. Конная прогулка на выходных в Галиче не равна недельному маршруту на Кавказе. Совпадение цены также не делает сплав похожим на экскурсию. Нужно учитывать признаки вместе.
Для такого подбора мы использовали методы машинного обучения. В исходной статье для масштаба задачи приведён Netflix Prize — конкурс улучшения прогнозов пользовательских оценок с призом в миллион долларов. Это пример сложности рекомендаций, а не результат или клиентский проект Riders.
Какие данные вошли в модель
Основа решения — таблица признаков туров. Данные уже были структурированы в базе, поэтому подготовку выполняли на Python без отдельной ручной разметки каталога.
Автор оценивает подготовку датасета примерно в 80% работы специалистов при неструктурированных данных; это общая оценка из исходной статьи, а не измеренная трудоёмкость данного проекта.
Использованные параметры:
- Цена, длительность в днях и сложность.
- Месяц проведения.
- Тип проживания: турбаза или гостевой дом, гостиница, корабль или каюта, палатка.
- Раздел каталога, регион и виды активности.
- Конкретные турбазы, отели и корабли.
- Оператор тура, широта и долгота, количество людей.
- Идентификатор похожего тура, ранее выбранного менеджером.
У одного тура может быть несколько активностей и регионов. Например, «Камчатский калейдоскоп» сочетает разные сценарии. Такие признаки разнесли по отдельным столбцам с помощью pandas: условно, «есть в Адыгее» и «есть на Кавказе». В результате получилась широкая таблица, пригодная для расчёта сходства.
# Основной датасет
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. Это сохранённая часть истории разработки, а не добавленная задним числом связь с ИИ.
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. Так сайт обращается к алгоритму через понятный интерфейс, не смешивая два окружения.
@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.
# Получаем похожие туры через сервис
$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 позволила убрать повторяющуюся ручную работу. Такой подход стоит обсуждать там, где рекомендации уже нужны бизнесу и есть данные для их построения.



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