Фид + Python = идеальные быстрые ссылки в Яндекс Директ и рост CTR на 30%
Слушать
В прошлой статье мы рассказали, как с помощью Excel создали уникальные быстрые ссылки для 7000 объектов национального туроператора «Алеан», с которым Adgasm.io сотрудничает уже более 4 лет. Наше решение повысило CTR объявлений на 30%, а выручка генераторных кампаний, где мы, в том числе, использовали уникальные быстрые ссылки, выросла в несколько раз.
В этой части поделимся, как реализовать подобный функционал при помощи Python. Могут понадобиться базовые знания языка. Преимущество этого способа в том, что можно обновлять все быстрые ссылки в пару кликов, независимо от объёма фида и формировать десятки тысяч уникальных объявлений. Excel, даже на мощных компьютерах часто не способен переварить столько данных.
Я не эксперт в Python, и, как любой новичок, сначала формулирую задачу, а затем ищу способы реализации. Поэтому готов к критике и буду рад обсудить в комментариях оптимальные решения.
Шаг 1. Сначала нужно установить среду разработки Jupyter Notebook, где мы будем писать код. Процесс установки для популярных операционных систем легко найти в сети.
Открываем файл Python 3.
Импортируем библиотеки — файлы с шаблонами кода, которые упрощают работу, а также импортируем данные из фида в датафрейм pandas (проще сказать — в таблицу). Если говорить условно, строчки в фиде мы записываем в ячейки таблицы. Этот процесс подробно описан на канале «Товарищ Excel — Power Query, Power Pivot, Python».
Шаг 2. В отличие от гайда «Товарищ Excel» наш фид начинается с url, который относится не к объекту (отелю), а к url сайта, к которому относится фид. Это нужно исправить.
Считываем данные из фида и записываем их в переменные (после знака «#» идут комментарии, которые не считаются частью кода).
# считываем данные из фида по атрибутам и записываем в переменную url_object = feed.find_all('url') id_object = feed.find_all('id') region_1 = feed.find_all('region_1') region_2 = feed.find_all('region_2') distance_to_sea = feed.find_all('distance_to_sea')
Затем удаляем первую строчку из переменной, в которую мы поместили url, так как она отражает некоторые технические свойства фида, но не относится напрямую к объекту.
# Удаляем первый url из шапки фида, который относится к сайту, но не к объекту url_object.pop(0)
Шаг 3. Остальные шаги получились идентичными гайду от «Товарищ Excel». Датафрейм при парсинга фида получается точь-в-точь таким же, как мы импортировали при помощи Power Query в предыдущей в первой части статьи.
Далее нам нужно повторить шаги из первой части статьи, только при помощи Python. Преобразуем значения из фида в тексты, которые мы будем использовать в расширениях.
Шаг 4. При помощи метода replace мы заменяем значения true на тот текст, который хотим видеть в расширениях. Но в нашем случае есть исключения:
— рейтинг, где мы конкатенируем (объединяем) текст «Рейтинг» и числовое значение рейтинга, а также заменяем значения без рейтинга на false (аналогично и для расстояния до моря, например);
— бассейны (крытый/открытый), которые мы будем дополнительно преобразовывать.
# заменяем значения true на тот текст, который хотим отображать в заголовке быстрой ссылки в дальнейшем bs.services_open_pool = bs.services_open_pool.replace("true", "Открытый") bs.services_closed_pool = bs.services_closed_pool.replace("true", "крытый") bs.for_kids = bs.for_kids.replace("true", "Отдых с детьми") bs.services_spa = bs.services_spa.replace("true", "SPA") bs.services_treatment = bs.services_treatment.replace("true", "Лечение") bs.services_beach = bs.services_beach.replace("true", "Пляж рядом") bs.hotel_rating = "Рейтинг: " + bs.hotel_rating bs.hotel_rating = bs.hotel_rating.replace("Рейтинг: null", "false") bs.services_wifi = bs.services_wifi.replace("true", "Wi-Fi")
Также можно совместить несколько сходных преимуществ: например, в рамках термального комплекса.
# Соединяем некоторые схожие преимущества, например, наличие Сауны, Джакузи, Бани, Хаммама в одну колонку с разделителем "/" bs["thermal"] = bs.sauna + "/" + bs.jacuzzi + "/" + bs.bath + "/" + bs.hammam # На выходе получится Сауна/Джакузи/Баня/Хаммам или Сауна/Джакузи/false/Хаммам — если Бани нет. # Поэтому следующей строкой заменяем значения "false/" или ""/false" на пустое место bs.thermal = bs.thermal.replace(["false/", "/false"], ["", ""], regex=True)
Чтобы проверить, какие уникальные значения получились в результате обработки, применяем к столбцу метод «unique()»
Далее преобразовываем колонки, связанные с расстоянием: до моря, до подъёмника. Например, форматируем километры в метры, удаляем слишком отдаленные значения по типу «до моря: 1 000 000 км». Не будем углубляться в подробности, потому что это специфика объявлений «Алеан» — чтобы раскрыть все нюансы, пришлось бы написать еще одну часть статьи.
Шаг 5. Создаем в датафрейме колонки ОБС (описания быстрых ссылок) для соответствующих ЗБС (заголовки быстрых ссылок)
# Создаём колонки под ОБС и помещаем в них любое значения (в моём случае "1") для каждой из ячеек bs["for_kids_obs"] = 1 bs["services_spa_obs"] = 1 bs["services_treatment_obs"] = 1 bs["services_beach_obs"] = 1 bs["hotel_rating_obs"] = 1 bs["bar_restaurant_obs"] = 1 bs["services_wifi_obs"] = 1 bs["animal_friendly_obs"] = 1 bs["services_parking_obs"] = 1
Заполняем актуальными значениями колонки ОБС, созданные на предыдущем шаге.
# генерируем описания быстрых ссылок для каждого заголовка быстрой ссылки при помощи функции obs_generic(bs.for_kids, bs.for_kids_obs, 'Подходит для отдыха с детьми. Подробности на сайте') obs_generic(bs.services_spa, bs.services_spa_obs, 'Посетите SPA-комплекс') obs_generic(bs.services_treatment, bs.services_treatment_obs, 'Есть лечебная программа. Выбирайте процедуру') obs_generic(bs.services_beach, bs.services_beach_obs, 'Оборудованный пляж для комфортного отдыха')
Для заполнения значений используем функцию obs_generic. Это нужно для сокращения повторяющихся фрагментов кода, чтобы «облегчить» код и его было проще читать.
# Генерирует описания быстрых ссылок для заголовков. Если заголовок равен null или false, то описание будет false def obs_generic(sbs, obs, text): for i in range(0, len(sbs)): if "false" in sbs[i] or "null" in sbs[i]: obs[i] = "false" else: obs[i] = text
Наша функция принимает данные о колонке ЗБС (sbs), колонке ОБС (obs) и тексте (text, для каждого ОБС — свой), который мы хотим поместить в колонку ОБС. Функция преобразовывает колонки ОБС, принимая соответствующие значения.
Работа с функциями выглядит сложной, если вы только начинаете работу с Python. Мы описали процесс упрощенно, но если вы хотите разобраться глубже, рекомендуем поискать статьи или видео на темы «Как работать с функциями Python», «Как работать с циклом for в Python».
В нашем случае функция сработала корректно. Чтобы в этом убедиться, мы проверили колонки с ОБС на наличие уникальных значений. Получили уникальные значения false там, где ЗБС также имеет значение false, или текст быстрой ссылки, если ячейка ЗБС не содержит false.
Шаг 6. Далее нам нужно каждую колонку ЗБС добавить в общую колонку со всеми ЗБС. Этот шаг аналогичен шагу 4 в разделе «Обрабатываем значения фида для формата быстрых ссылок» первой части статьи.
Результат конкатенации для объекта под индексом 464 получился таким.
Конкатенируем адреса быстрых ссылок уже с другой, более простой механикой, чем в случае с Excel. Нам достаточно добавить 8 url объекта через разделитель ||
# Формируем общую колонку со всеми (8) адресами быстрых ссылок для каждого из объектов # При этом разделителей должно быть только 7, поэтому конструкцию "bs.url_object + """ домножаем на 7, а не 8. # И следом добавляем еще один адрес bs["url_bs_all"] = (bs.url_object + "") * 7 + bs.url_object
Шаг 7. Чтобы работать с объединенными ЗБС, ОБС и адресами БС, поместим их в новый датафрейм bs_all.
# в новый датафрейм отбираем нужные нам значениям bs_all = bs[["id_object", "url_object", "url_bs_all", "obs_all", "sbs_all"]]
В новом датафрейме получаем только необходимые для дальнейшей работы поля.
Удаляем все значения false и null.
# удаляем false и null в датафрейме bs_all = bs_all.replace("false", "", regex = True).replace("null", "", regex = True)
Получаем такие значения.
Шаг 8. Удаляем лишние разделители || из ЗБС и ОБС. Этот шаг аналогичен шагу 5 в разделе «Обрабатываем значения фида для формата быстрых ссылок» первой части статьи.
# удаляем повторения \\ при помощи replace и regex for i in range(0, 20): bs_all["obs_all"].replace("||||", "", regex=True, inplace=True) bs_all["sbs_all"].replace("||||", "", regex=True, inplace=True) # удаляем символы || слева и справа при помощи lstrip и rstrip for i in range(0, len(bs_all.obs_all)): a = "|" bs_all.obs_all[i] = bs_all.obs_all[i].lstrip(a).rstrip(a) bs_all.sbs_all[i] = bs_all.sbs_all[i].lstrip(a).rstrip(a)
Получаем более чистые и читаемые значения.
Шаг 9. Считаем количество ЗБС. Этот шаг аналогичен шагу 1 в разделе «Готовим быстрые ссылки для выгрузки в Директ Коммандер» первой части статьи.
Повторяем то же самое для ОБС.
# то же самое для описаний быстрых ссылок bs_all['count_obs_all'] = 0 bs_all['count_obs_all_2'] = bs_all.obs_all.str.count('|') for i in range(0, len(bs_all.obs_all)): a = 0 if bs_all.count_obs_all_2[i] > 0 or len(bs_all.obs_all[i]) > 0: a = bs_all.count_obs_all_2[i] // 2 + 1 bs_all.count_obs_all[i] = a
К числовой колонке мы можем применить метод describe, который покажет сводку по основным показателям. Если применим describe к колонке с количеством ЗБС, получим следующие данные:
Где,
- count — количество объектов в фиде
- mean — среднее число ЗБС для одного объекта
- min/max — минимальное/максимальное число ЗБС для одного объекта
- 25, 50, 75% — значения по процентилям. 50% — это медиана, то есть для половины объектов количество ЗБС больше 4, для половины — меньше.
Если мы все сделали правильно, то при применении describe к колонке с количеством ОБС получим идентичные значения. Так и получилось.
Шаг 10. Удаляем некоторое количество ЗБС и ОБС, если их больше 8. И наоборот, добавляем, если их меньше 8. Этот шаг аналогичен шагу 2 в разделе «Готовим быстрые ссылки для выгрузки в Директ Коммандер» первой части статьи.
Сформируем списки из значений с разделителем || и значением ЗБС или ОБС. Чем ближе ЗБС или ОБС к началу списка, тем чаще оно будет использоваться. Важно, чтобы порядок значений в ЗБС соответствовал порядку значений в ОБС.
sbs_app = ["||Рассрочка 0%", "||Отмена брони — 0₽", "||Отзывы", "||Оформление за 3 мин", "||Актуальные цены", "||Места в наличии", "||Контроль качества", "||Алеан: один из лидеров туризма"] obs_app = ["||При бронировании предоставляем беспроцентную рассрочку", "||Об условиях читайте на сайте", "||Честные отзывы от клиентов Алеан", "||Удобная навигация и формы на сайте", "||Всегда поддерживаем актуальность", "||Бронируйте на удобную дату", "||1 место по результатам мониторинга лояльности туроператоров", "||1 место по итогам голосования турагентов (лето 2022)"]
Далее напишем код для добавления необходимого количества ЗБС и ОБС.
# В переменную x для каждого объекта мы помещаем следюущий расчёт: из 8 вычитаем количество ЗБС. # или равно Если результат получится со знаком меньше 0 или равно 0 — это будет значить, # что количество ЗБС для этого объекта больше 8. И дополнительные ЗБС добавлять не надо. # Если же получится больше 0, значит, количество ЗБС меньше 8 и дополнительные ЗБС нужно добавить. for i in range(0, len(bs_all.sbs_all)): x = 8 — bs_all.count_sbs_all[i] c = 0 # Для каждой ячейки в ЗБС и ОБС мы делаем проверку: если x > 0 , то мы добавим дополнительные ЗБС. # При этом количество ЗБС, которые нужно добавить, будет определяться как раз переменной "x" if x > 0: bs_all.sbs_all[i] = bs_all.sbs_all[i] + "".join(sbs_app[c:x]) bs_all.obs_all[i] = bs_all.obs_all[i] + "".join(obs_app[c:x])
Из-за того, что мы присоединяли первые значения в ЗБС и ОБС с разделителем || в начале, могли образоваться лишние разделители слева. Это происходит в случае, если количество уникальных ЗБС или ОБС равно 0. Поэтому нужно удалить первые значения || слева при помощи str.lstrip()
# Удаляем лишние разделители "|" слева a = "|" bs_all["sbs_all"] = bs_all["sbs_all"].str.lstrip(a) bs_all["obs_all"] = bs_all["obs_all"].str.lstrip(a)
Недостающие ЗБС и ОБС мы добавили. Осталось удалить те, где ЗБС и ОБС больше 8.
Этот процесс можно описать в такой последовательности.
1) Создаём новый датафрейм. ОБС разбиваем по разделителю ||. Задаем название каждой из полученных колонок.
Новый датафрейм будет выглядеть так.
2) Конкатенируем колонки с ОБС из нового датафрейма с разделителем || в одну колонку в старом датафрейме obs all при помощи методов .agg и .join
# Соединяем 8 колонок, а не 9, т.к. максимальное количество быстрых ссылок — 8. Разделитель — "\" bs_all["obs_all"] = new_df_obs[['obs1','obs2','obs3','obs4','obs5','obs6','obs7','obs8']].agg(''.join , axis=1)
Ячейки колонки для каждого из объектов сформированы правильно — проверили на конкректном объекте под индексом 464.
Тот же порядок действий повторяем для ЗБС.
# то же самое для ЗБС new_df_sbs = bs_all["sbs_all"].astype(str).str.split('||', n=8, expand=True) new_df_sbs.columns=['sbs1','sbs2','sbs3','sbs4','sbs5','sbs6','sbs7','sbs8','sbs9'] bs_all["sbs_all"] = new_df_sbs[['sbs1','sbs2','sbs3','sbs4','sbs5','sbs6','sbs7','sbs8']].agg('||'.join , axis=1)
На этом преобразования закончены. Осталось убедиться, что количество ЗБС и ОБС везде равняется 8. Эту работу для ЗБС мы уже проделывали, поэтому повторяем ее для новых колонок.
bs_all['count_sbs_all'] = 0 bs_all['count_sbs_all_2'] = bs_all.sbs_all.str.count('|') for i in range(0, len(bs_all.sbs_all)): a = 0 if bs_all.count_sbs_all_2[i] > 0 or len(bs_all.sbs_all[i]) > 0: a = bs_all.count_sbs_all_2[i] // 2 + 1 bs_all.count_sbs_all[i] = a # то же самое для описаний быстрых ссылок bs_all['count_obs_all'] = 0 bs_all['count_obs_all_2'] = bs_all.obs_all.str.count('|') for i in range(0, len(bs_all.obs_all)): a = 0 if bs_all.count_obs_all_2[i] > 0 or len(bs_all.obs_all[i]) > 0: a = bs_all.count_obs_all_2[i] // 2 + 1 bs_all.count_obs_all[i] = a
С помощью колонок count_obs_all и count_sbs_all проверяем, что количество ОБС у всех объектов равно 8, применив метод describe:
Шаг 11. Подготавливаем данные и выгружаем датафрейм в Excel.
# создаём финальный датафрейм — в него записываем все колонки для выгрузки bs_final = bs_all[["id_object", "url_object", "sbs_all", "url_bs_all", "obs_all"]] #переименовываем колонки в формат, читаемый Direct Commander bs_final.rename(columns={"url_object" : "Ссылка", "sbs_all" : "Заголовки быстрых ссылок", "obs_all" : "Описания быстрых ссылок", "url_bs_all" : "Адреса быстрых ссылок"}, inplace=True) # выгружаем датафрейм в xlsx файл "bs_final" bs_final.to_excel("bs_final.xlsx", index=False)
Элементы быстрых ссылок уже готовы для выгрузки.
Остается только применить функцию ВПР. Помните, что в результате могут получиться значения « /Д», которые нельзя загружать в Директ Коммандер. Чтобы их убрать, воспользуйтесь способом, описанным в последнем разделе первой части статьи.
Больше полезной информации в нашем телеграм-канале:
Заметки Adgasm.io. Контекстная реклама Канал агентства Adgasm.io. Пишем об опыте, наблюдениях и новинках в контекстной рекламе. По… t.me
Автор: Иван Кикоть, Senior performance manager
Источник: vc.ru