— Я зделяль. ©
Итак, прошу любить и жаловать — INat::Get — софтина для получения и обработки данных
с iNaturalist. Основное изначальное предназначение — подбивать всякую статистику
для проектов на том же iNaturalist’е, но варианты использования гораздо шире.
Первым делом хочу отметить, что текущее состояние — это ранняя альфа. Я не рекомендую никому этим пользоваться иначе
как из любопытства и желания поучаствовать. Тем не менее делаю пост уже сейчас в надежде, что любопытные желающие найдутся.
Со своей стороны готов подробно отвечать на вопросы и учитывать пожелания.
Зачем?
iNaturalist предоставляет открытый доступ к огромному массиву наблюдений, а также
по сути к постоянно актуализируему таксономическому справочнику (тут можно обсуждать нюансы, но для любительских целей
это очень хорошие данные). Интерфейс самого сайта не покрывает и, конечно, не может покрывать все возможные варианты
запросов и выборок, но мы можем получить сами данные через механизм выгрузок или посредством открытого API,
и второй вариант богаче, гибче и вообще интересней.
В качестве примера таких отчетов, которые нельзя получить просто из интерфейса, приведу свои посты в проекте
«Биоразнообразие Артинского района». Не потому, что они представляют собой что-то особо ценное,
а именно как демонстрацию:
Эти посты были сформированы по данным выгрузок, а не API, посредством мною же написанного inat-script,
в процессе работы с которым (и над которым) я осознал все недостатки механизма выгрузок:
- ограниченность данных;
- необходимость ручного создания процесса выгрузки и, потом, загрузки файла;
- и главное — неизвестный заранее срок готовности — некоторые выгрузки делались вовсе несколько дней.
Кроме того, сам скрипт, как всякий первый блин, требовал существенной переработки для того, чтобы удобно внедрять в него новые
варианты выборок и отчетов, и я решил написать с нуля новый инструмент, работающий непосредственно с API.
Принцип действия
На самом деле я сначала пытался сделать как-то так, чтобы отчеты формировались через конфигурационные файлы. Однако гибкости
в таком подходе никакой (ну или потребуется senior-yaml-developer для использования, ЕВПОЧЯ). В итоге пришел к выводу, что
проще предположить в продвинутом пользователе базовые знания Ruby…
В общем, программа запускает ruby-скрипты, называемые задачами, которые работают на уровне абстрактных выборок и списков,
оставляя все обращения к API и кэширование ответов под капотом. По большому счету пользователь имеет дело (помимо объектов,
представляющих собственно данные) с двумя классами: DataSet
, который представляет собой набор наблюдений, уже отфильтрованный
тем или иным образом; и List
— по сути датасет, сгруппированный по неким объектам, как правило — таксонам.
Для формирования вывода имеется специальный объект Table
, который сначала определяется, т.е. задаются колонки с заголовками,
шириной и выравниванием, а затем наполняется данными, которые должны представлять собой массив хэш-таблиц… Звучит страшно,
но в действительности скрипты могут быть совсем простые. Например, давайте получим список видов в Артинском районе, которых
я никогда не наблюдал.
user = User::by_login 'shikhalev'
place = Place::by_slug 'artinskiy-gorodskoy-okrug-osm-2023-sv-ru'
user_dataset = select user_id: user.id
place_dataset = select place_id: place.id
user_list = user_dataset.to_list
place_list = place_dataset.to_list
result_list = place_list - user_list
result_table = table do
column '#', width: 3, align: :right, data: :line_no
column 'Таксон', data: :taxon
column 'К-во набл.', width: 6, align: :right, data: :count
end
result_rows = result_list.map { |ds| { taxon: ds.object, count: ds.count } }
result_table << result_rows
File.write 'notmy.htm', result_table.to_html
Файл я поместил в каталог примеров под именем notmy.inat
, теперь мы можем его запустить командой:
$ inat-get notmy.inat
И через некоторое время получим результат.
Результат можно увидеть в моем посте на iNaturalist. Да, на данный момент, все форматирование рассчитано именно и только
на посты в iNat, активно используя тамошние стили. Есть планы расширить данный момент, но об этом позже.
Что важно, если мы тут же запустим ту же команду, то результат получим практически мгновенно, причем идентичный. А если выждем сутки,
то некоторое дополнительное время понадобится, но существенно меньшее, чем при первом запуске. Это первый ключевой момент — данные кэшируются.
Кроме того, следует обратить внимание на то, что на самом-то деле API отдает не более 200 наблюдений за один запрос. Здесь же
этого ограничения мы не видим и работаем с полными датасетами (объемом 3k+ и 4k+) — организация последовательной постраничной загрузки
так же находится под капотом.
Некоторые детали
-
Метод select
выполняет запрос и возвращает объект класса DataSet
. Именованные параметры данного метода примерно соответствуют
параметрам API, правда, реализованы не все.
-
Класс DataSet
инкапсулирует набор наблюдений, плюс опционально ассоциирует его с некоторым объектом. В основном его поведение
определяется включенным модулем Enumerable
, и бинарными операциями:
-
|
— объединение;
-
&
— пересечение;
-
-
— разность.
Также у класса DataSet
имеется важный метод to_list
, который создает объект класса List
, группируя наблюдения по тому или иному
параметру. Для группировки используется proc
-объект, который должен выдавать по наблюдению собственно ключ группировки. Наиболее
полезные (на мой взгляд) группировки уже определены как константы модуля Listers
:
-
Listers::SPECIES
возвращает таксон, «приведенный к виду». В кавычках потому, что результатом может быть как вид, так и гибрид
или комплекс.
-
Listers::YEAR
возвращает год.
- И так далее.
Если вызвать to_list
без параметров, то по умолчанию будет использован Listers::SPECIES
.
-
Класс List
представляет собой список датасетов с ассоциированными значениями. Также реализует модуль Enumerable
, только итерируемыми
элементами будут объекты класса DataSet
, а не Observation
. Для списков также определены некоторые бинарные операции:
-
+
— объединение;
-
*
— пересечение;
-
-
— разность (что и использовано в примере).
Объединенный датасет из списка можно получить посредством метода to_dataset
.
-
Классы данных: Observation
, Taxon
, Place
, User
и так далее предоставляют собственно данные. Свойств там много, их следует
отдокументировать, но пока руки не дошли. Впрочем, это тот случай, когда код действительно является документацией, так что см. каталог
entity
.
Отмечу, что все они имеют метод класса by_id
для получения соответствующего объекта, и в дополнение классы Place
и Project
имеют
метод by_slug
, а класс User
— метод by_login
.
Установка
Установка максимально проста:
# gem install inat-get
Правда, на данный момент все это гарантированно работает только под Linux. Тестирование под Windows в планах есть, но скорее ближе
к весне.
Планы
Вообще, на гитхабе есть такой замечательный раздел «Issues», где можно посмотреть процесс планирования в реальном времени.
Здесь постараюсь дать сводную картину.
К версии 1.0
- Документация
На первом этапе — полноценное руководство пользователя на русском языке. Затем расширенное руководство как на русском, так и на английском.
- Данные
Сейчас многие поля, в том числе такие важные, как охранный статус, просто игнорируются. Это категорически неправильно и, естественно,
будет исправлено уже в бета-версии.
Сюда же отнесу поддержку всех ключей запроса доступных в API. Тут есть некоторые нюансы, которые следует продумать, почему собственно
на данном этапе их и нет, но все решаемо.
- Доделки и исправления
Уже замеченные баги есть, и их, само собой, исправлять нужно, причем в первую очередь. К счастью, критичный, т.е. приводящий к вылету,
только один и возникает он редко.
Есть запланированная, но пока нереализованная базовая функциональность, такая как чистка устаревших данных.
- Оптимизация запросов
Есть мысли, как можно уменьшить количество запросов к API, что существенно ускорит работу в целом.
Дальнейшее развитие
- Разнообразные возможности вывода
Тут с одной стороны, явно нужно сделать поддержку не только упрощенной разметки, используемой в журналах iNaturalist, но и форматов,
которые можно использовать в различных местах независимо.
С другой стороны, надо бы добавить что-то типа вывода иерархических списков, а возможно и каких-то еще вариантов оформления.
Все это пока на уровне исследования и формулирования задачи.
- Оптимизация кэширования
Подробно расписывать не буду, но там есть над чем работать. В первую очередь это касается разбора и трансляции условий проектов.
Обратная связь
Буду рад вопросам, замечаниям и предложениям, как в комментариях к этому посту, так и в соответствующем разделе на GitHub.
Можно так же писать в личные сообщения на iNaturalist, хотя предпочтительно все же общаться в открытых комментариях, чтобы не возникало
дублирования.