Поиск ближайших любительских соревнований по бегу, плаванию, велосипедам и другим видам спорта

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

Например, я живу в Перми и хочу поучаствовать на следующих выходных в какой нибудь активности, например, побегать, прокатиться на велосипеде или поплавать. Ввожу в поиске «Пермь», но в результатах выдачи ничего нет. Хотя есть соревнование в посёлке Юг, который находится меньше часа езды от Перми. Но раз я ищу «Пермь», то «Юг» понятное дело в списке не выводится…

Вместе с Александром Ивановым, одним из авторов телеграм -канала «Таблицы Гугл», мы решили разобраться с этой проблемой техническим способом — написать Apps Script, который обойдёт общий список соревнований на каждом из сайтов, где публикуются анонсы и соберет информацию о названии соревнования, дате и и городе, где проводится соревнование. А потом соберёт сводную таблицу предстоящих соревнований и, в идеале, отобразит эти точке на карте — так любитель сразу может понять, где проводятся ближайшие соревнования, какая у них дата и куда можно поехать. 

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

К тому же любительские соревнования это в первую очередь не сам спорт, а укрепление здоровья и забота о себе — по крайней мере, для меня. Я не фокусируюсь на спортивных достижениях, а участвую ради удовольствия, физической активности и возможности побывать в новых местах.

Проблемы с поиском любительских соревнований

Проблема с поиском на самом деле существует — уже несколько лет в теме и последний год занимаюсь вместо живого тренера под руководством ChatGPT. Но когда я хочу найти какое-нибудь соревнование по бегу, велосипедам или плаванию, то информация об этом разбросана по нескольким сайтам, что очень неудобно. 

Например, у меня есть свободные выходные и приходится тратить много времени на изучение разных сайтов, например:

И всё потому, что нет какого-то централизованного источника по всем дисциплинам. Мало того, что сайтов достаточно много, так ещё и функции поиска на этих сайтах неэффективны. 

Например, я могу захотеть съездить в другой город, но больше 6 часов на машине я не поеду. Или даже могу слетать куда-нибудь, чтобы побывать в Санкт-Петербурге. То есть интерес затрагивает в целом все города России.

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

Маршрут из Перми до Юга

Маршрут из Перми до Юга

Предлагаемое решение

Ребята из телеграм -канала «Таблицы Гугл» создали простой Google Apps Script, который автоматизирует процесс сбора данных о событиях с определенных сайтов и обновляет Google Таблицу с этой информацией.

Решение доступно по ссылке: amateur_sports_competitions.

Как это работает?

Этот скрипт Google Apps автоматизирует процесс парсинга и сохраняет данные об соревнованиях с трех конкретных самых популярных веб-сайтов в соответствующие вкладки гугл таблицы. 

Функция run запускает функции сбора данных для iron-star.com, myrace.info и russiarunning.com и гарантирует, что документ Google Sheets будет заполнен актуальной информацией о соревнованиях из этих источников данных.

Этот код состоит из трех основных функций, каждая из которых посвящена конкретному сайту, и центральной функции для координации этих задач по автоматическому извлечению данных. 

Вот подробное описание того, что делает каждая часть кода:

1. Манифест (файл appsscript.json):

Этот файл устанавливает в проекте следующие настройки:

— Устанавливается часовой пояс Москва.

— Добавляет библиотеку Cheerio для анализа HTML.

— Включает регистрацию исключений через Stackdriver.

— Для сценария выполнения используется среда V8.

{
  "timeZone": "Europe/Moscow",
  "dependencies": {
    "libraries": [
      {
        "userSymbol": "Cheerio",
        "version": "14",
        "libraryId": "1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

2. Основная функция (файл 0.index.gs):

Основная функция run служит точкой входа, которая последовательно вызывает три другие функции для извлечение данных с сайтов:  

  1. userActionScrapeMyraceInfo

  2. userActionScrapeIronStarCom

  3. userActionScrapeRussiarunningCom.

function run() {
  userActionScrapeMyraceInfo();
  userActionScrapeIronStarCom();
  userActionScrapeRussiarunningCom();
}

3. Парсинг сайта iron-star.com (файл iron-star.com.gs):

Сайт IRONSTAR TRIATHLON, это крупнейшая в России серия соревнований по триатлону.

Автор на соревнованиях Ironstar Sprint Ekaterinburg

Автор на соревнованиях Ironstar Sprint Ekaterinburg

Эта функция парсит информацию о соревнованиях с сайта iron-star.com. Она извлекает содержимое веб-страницы, анализирует его с помощью Cheerio, извлекает URL-адреса событий, даты, имена и города, а затем записывает данные в документ Google Sheets на вкладку с именем iron-star.com.

function userActionScrapeIronStarCom() {
  console.log('userActionScrapeIronStarCom', 'start');
  // Получаем новые ссылки
  const url = 'https://iron-star.com/event/';
  const contentList = UrlFetchApp.fetch(url).getContentText();
  const $ = Cheerio.load(contentList);
  const itemList = $('.event-item-wrap a.event-item');

  const values = [];

  itemList.map((_, item) => {
    const $item = Cheerio.load(item);
    const date = $item('.date').text().trim();
    const name = $item('.title').text().trim();
    const city = $item('.place').text().trim();
    const url = `https://iron-star.com${$(item).attr('href')}`;
    values.push([url, date, name, city]);
  });

  const book = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = book.getSheetByName('iron-star.com');

  sheet.getRange(3, 1, sheet.getMaxRows() - 1, sheet.getMaxColumns()).clearContent();

  if (values.length) {
    sheet.getRange(2, 1, values.length, values[0].length).setValues(values);
  } else {
    sheet.getRange('2:2').clearContent();
  }
  console.log('userActionScrapeIronStarCom', 'finish');
}

4. Парсинг сайта myrace.info (файл myrace.info.gs):

Сайт MyRace это площадка для регистрации на спортивные события в России. 

Автор на соревнованиях X-Waters Saint Petersburg. Заплыв вокруг Елагина

Автор на соревнованиях X-Waters Saint Petersburg. Заплыв вокруг Елагина

Аналогично предыдущей функции, эта функция собирает информацию о соревнованиях с myrace.info. Она извлекает содержимое веб-страницы, анализирует его с помощью Cheerio, извлекает URL-адреса событий, даты, имена и города, а затем записывает данные в документ Google Sheets на вкладку с именем myrace.info.

function userActionScrapeMyraceInfo() {
  console.log('userActionScrapeMyraceInfo', 'start');
  // Получаем новые ссылки
  const url = 'https://myrace.info/take/';
  const contentList = UrlFetchApp.fetch(url).getContentText();
  const $ = Cheerio.load(contentList);
  const itemList = $('.container-content.centered > a.events-list__item.row');

  const values = [];

  itemList.map((_, item) => {
    const $item = Cheerio.load(item);

    const date = $item('.date')
      .text()
      .replace(/(\d+)-(\d)+/, '$1');
    const name = $item('h2').clone().children().remove().end().text().trim();
    const city = $item('.flag').text().trim();
    const url = `https://myrace.info${$(item).attr('href')}`;
    values.push([url, date, name, city]);
  });

  const book = SpreadsheetApp.getActive();
  const sheet = book.getSheetByName('myrace.info');

  sheet.getRange(3, 1, sheet.getMaxRows() - 1, sheet.getMaxColumns()).clearContent();

  if (values.length) {
    sheet.getRange(2, 1, values.length, values[0].length).setValues(values);
  } else {
    sheet.getRange('2:2').clearContent();
  }
  console.log('userActionScrapeMyraceInfo', 'finish');
}

5. Парсинг russiarunning.com (файл russiarunning.com.gs):

Сайт RussiaRunning — это крупнейшая платформа для регистрации на спортивные события в России. Она объединяет организаторов спортивных событий и участников соревнований. На сайте можно выбрать событие, зарегистрироваться на него, оплатить участие и получить подтверждение.

Автор на Пермском марафоне

Автор на Пермском марафоне

Эта функция собирает информацию о соревнованиях с russiarunning.com и работает по другому, отправляя POST-запрос к API сайта. Она получает информацию о событии в формате JSON, извлекает соответствующие детали (URL-адреса, даты, имена и места) и записывает данные в документ Google Sheets на вкладку с именем russiarunning.com.

function userActionScrapeRussiarunningCom() {
  console.log('userActionScrapeRussiarunningCom', 'start');
  const url = 'https://russiarunning.com/api/events/list/ru';
  const payload = {
    Take: 500,
    DateFrom: new Date().toISOString().split('T')[0],
  };
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  };
  const data = UrlFetchApp.fetch(url, options);

  const items = JSON.parse(data.getContentText()).Items;

  const values = items.map((item) => {
    const { c, d, t, p } = item;
    const link = `https://russiarunning.com/event/${c}/`;
    const date = d.split('T')[0];
    const row = [link, date, t, p];
    return row;
  });

  const book = SpreadsheetApp.getActive();
  const sheet = book.getSheetByName('russiarunning.com');

  sheet.getRange(3, 1, sheet.getMaxRows() - 1, sheet.getMaxColumns()).clearContent();

  if (values.length) {
    sheet.getRange(2, 1, values.length, values[0].length).setValues(values);
  } else {
    sheet.getRange('2:2').clearContent();
  }
  console.log('userActionScrapeRussiarunningCom', 'finish');
}

Что в итоге?

После парсинга трёх сайтов через QUERY запрос делается выборка всех предстоящих соревнований от сегодняшнего дня вперёд в будущее. QUERY запрос нужен чтобы отсечь предыдущие даты — ведь при парсинге некоторые сайты отдают и предыдущие соревнования.

Скриншот таблицы  amateur_sports_competitions

Скриншот таблицы amateur_sports_competitions

Эта формула в гугл таблицах объединяет данные о соревнованиях из трех разных листов, фильтрует их, чтобы включить только будущие события (или события, происходящие сегодня), и сортирует эти события по дате в порядке возрастания:

=QUERY(
        {
        myrace.info!A:D;
        'iron-star.com'!A:D;
        russiarunning.com!A:D
        }; 
"SELECT * 
WHERE Col2 >= date '"&text(today();"yyyy-MM-dd")&"'
Order by Col2 asc
")

Что не получилось сделать

Идея сделать подобную таблицу пришла ещё в середине мае, но работа была закончена только сейчас и перед вами уже вторая версия кода — первый был гораздо более сложный и по другому осуществлял процесс парсинга.

Важную часть идею — не только собрать, но и отобразить места на карте — решили перенести во вторую часть этой статьи (работа над которой сейчас идёт), потому что столкнулись с тем, что не всегда указаны места проведения и не всегда они указаны четко: например указан какой-то посёлок, но в какой он находится области — это непонятно.

Как пользоваться для самых далёких от программирования

По ссылке ниже откроется ваша копия таблицы. Если в браузере выполнен вход в разные гугл-аккаунты, то нужно оставить только один, иначе при переходе по ссылке может появиться ошибка. По ссылке Вам предложат создать свою копию таблицы — создайте копию в свой аккаунт:

➡️amateur_sports_competitions⬅️

Дальше откройте редактор сценариев Google Apps:

 — В Google Таблице выберите «Расширения» > «Apps Script».

 — Редактор скриптов Google Apps откроется на новой вкладке.

Теперь, когда у вас открыт редактор сценариев приложений запустите функцию под названием run(). Чтобы её запустить нажмите кнопку воспроизведения (значок треугольника). 

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

Гугл предупредит, что приложение не проверено. Нужно выбрать «Дополнительные настройки», а потом перейти по нижней ссылке. После этого останется нажать на кнопку «Разрешить», и на этом первоначальная настройка будет готова.

Выполнив эти шаги, вы сможете успешно запустить скрипт поиска соревнований по всей России — он обновит уже имеющиеся данные в вашей копии таблицы.

Заключение

Проведена большая работа по созданию сводной таблицы по самым популярным площадкам, которые публикуют информацию о любительских соревнованиях.

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

Репозиторий программы: googlesheets-ru/amateur_sports_competitions

Код создан командой телеграм-канала «Таблицы Гугл»

Автор текста и идеи: Михаил Шардин

1 июля 2024 г.

Habrahabr.ru прочитано 1536 раз