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

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

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

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

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

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

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

Проблема с поиском на самом деле существует - уже несколько лет в теме и последний год занимаюсь вместо живого тренера под руководством 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  на 29.06.2024
Скриншот таблицы amateur_sports_competitions на 29.06.2024

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

=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 г.

Комментарии (13)


  1. csl
    01.07.2024 00:49
    +1

    Можете добавить ещё и в хаб Data Mining, или и так уже пять, так много?

    Update: трудно сказать, как на практике, но следуя правилам сайта, 5 - максимум


    1. empenoso Автор
      01.07.2024 00:49
      +1

      Не даёт в шестой хаб добавить - 5 максимум.


  1. vkomp
    01.07.2024 00:49
    +1

    Добречкого. Не знаю ни одного из перечисленных сайтов, потому что есть orgeo.ru. Больше про спортивное ориентирование или спортивный туризм, но и другие виды присутствуют.


    1. empenoso Автор
      01.07.2024 00:49

      Я про этот сайт никогда не слышал, но посмотрел и это тоже похоже один из агрегаторов.

      Нашего региона, в котором я живу или ближайших регионов там нет.


      1. vkomp
        01.07.2024 00:49

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


        1. empenoso Автор
          01.07.2024 00:49

          Тогда это такой же сайт как описан в статье


  1. dimas846
    01.07.2024 00:49

    Парсить HTML стороннего сайта всегда дело неблагодарное. Рано или поздно фронтэнд поменяется и парсинг перестанет работать. У этих сайтов нет Data API? может есть RSS?


    1. empenoso Автор
      01.07.2024 00:49

      У одного есть


    1. empenoso Автор
      01.07.2024 00:49

      Когда что-то меняется в разметке, можно и в коде поменять, когда работать перестаёт


      1. mikelavr
        01.07.2024 00:49

        Это требует непрерывной поддержки...


        1. oshliaer
          01.07.2024 00:49
          +1

          Как и все в современном мире. Скрапинг данных не может быть исключением. Любая программа требует поддержки и развития, так сложилось.


  1. mikelavr
    01.07.2024 00:49
    +2

    Та же проблема, автоматизацию не делал, но могу подкинуть вам дополнительных источников информации:

    Каталоги забегов:

    https://runcalendar.ru/

    https://begaem.com/blizhayshie-startyi-v-rossii/

    https://get.run/races/europe/russia/

    https://reg.place/

    https://results.racetime.online/

    https://probeg.org/

    Крупные организаторы:

    https://runc.run/

    https://goldenringrun.ru/ (эти забеги есть на russiarunning.com)

    https://runningmr.ru/

    https://grom.club/

    https://pushkin-run.ru/

    https://goldenultra.ru/

    https://mosathletics.ru

    http://www.sportspirit.org/

    https://arta-sport.ru/

    Паркраны:

    https://5verst.ru/

    https://runpark.ru/

    https://s95.ru/

    Я сам только бегун (асфальт и стадион). Немножко трейла, велосипеда и плавания есть - но без соревнований).

    Веду табличку забегов для себя (и своего клуба G_Gorilla_Runners), данные собираются вручную. Там есть и единичные забеги, которые организуются раз в год.

    https://docs.google.com/spreadsheets/d/1CQ2tjqPaP277HgqUiqiS95GT9CQRAWnxAEgHofzMYCY

    Зимой живу в Москве, летом во Владимирской области, и далеко не выезжаю - максимум 4-5 часов на машине. Поэтому ориентация на эти регионы и вокруг.

    Карты очень не хватает, факт. Карта хорошо сделана только у паркранов.


    1. mityayka1
      01.07.2024 00:49

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