Кого я еще не видел у нас здесь...
Вообще, это я со скриптами экспериментирую, но некоторый смысл данная табличка имеет... Посмотрим, сколько тут будет через год, через два и т.д.
Вообще, это я со скриптами экспериментирую, но некоторый смысл данная табличка имеет... Посмотрим, сколько тут будет через год, через два и т.д.
— Я зделяль. ©
Итак, прошу любить и жаловать — INat::Get — софтина для получения и обработки данных с iNaturalist. Основное изначальное предназначение — подбивать всякую статистику для проектов на том же iNaturalist’е, но варианты использования гораздо шире.
Первым делом хочу отметить, что текущее состояние — это ранняя альфа. Я не рекомендую никому этим пользоваться иначе как из любопытства и желания поучаствовать. Тем не менее делаю пост уже сейчас в надежде, что любопытные желающие найдутся. Со своей стороны готов подробно отвечать на вопросы и учитывать пожелания.
iNaturalist предоставляет открытый доступ к огромному массиву наблюдений, а также по сути к постоянно актуализируему таксономическому справочнику (тут можно обсуждать нюансы, но для любительских целей это очень хорошие данные). Интерфейс самого сайта не покрывает и, конечно, не может покрывать все возможные варианты запросов и выборок, но мы можем получить сами данные через механизм выгрузок или посредством открытого API, и второй вариант богаче, гибче и вообще интересней.
В качестве примера таких отчетов, которые нельзя получить просто из интерфейса, приведу свои посты в проекте «Биоразнообразие Артинского района». Не потому, что они представляют собой что-то особо ценное, а именно как демонстрацию:
«Неитоги 2023» (как бы итоги сезона, только подведенные в его середине). Здесь интересны «Новинки» и «Потеряшки».
«Сравнение с соседями». Не просто количественное сравнение, а сравнение видового состава.
Эти посты были сформированы по данным выгрузок, а не 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», где можно посмотреть процесс планирования в реальном времени. Здесь постараюсь дать сводную картину.
На первом этапе — полноценное руководство пользователя на русском языке. Затем расширенное руководство как на русском, так и на английском.
Сейчас многие поля, в том числе такие важные, как охранный статус, просто игнорируются. Это категорически неправильно и, естественно, будет исправлено уже в бета-версии.
Сюда же отнесу поддержку всех ключей запроса доступных в API. Тут есть некоторые нюансы, которые следует продумать, почему собственно на данном этапе их и нет, но все решаемо.
Уже замеченные баги есть, и их, само собой, исправлять нужно, причем в первую очередь. К счастью, критичный, т.е. приводящий к вылету, только один и возникает он редко.
Есть запланированная, но пока нереализованная базовая функциональность, такая как чистка устаревших данных.
Есть мысли, как можно уменьшить количество запросов к API, что существенно ускорит работу в целом.
Тут с одной стороны, явно нужно сделать поддержку не только упрощенной разметки, используемой в журналах iNaturalist, но и форматов, которые можно использовать в различных местах независимо.
С другой стороны, надо бы добавить что-то типа вывода иерархических списков, а возможно и каких-то еще вариантов оформления.
Все это пока на уровне исследования и формулирования задачи.
Подробно расписывать не буду, но там есть над чем работать. В первую очередь это касается разбора и трансляции условий проектов.
Буду рад вопросам, замечаниям и предложениям, как в комментариях к этому посту, так и в соответствующем разделе на GitHub. Можно так же писать в личные сообщения на iNaturalist, хотя предпочтительно все же общаться в открытых комментариях, чтобы не возникало дублирования.
Гнездовой сезон закончился давно, можно надеяться, что основная масса наблюдений уже загружена... Хотя не факт, конечно.
# | Год | Наблюдения | Виды | Новые |
---|---|---|---|---|
1 | 2012 | 6 | 4 | 4 |
2 | 2016 | 10 | 7 | 7 |
3 | 2017 | 20 | 15 | 11 |
4 | 2018 | 3 | 3 | 2 |
5 | 2019 | 1 | 1 | 1 |
6 | 2020 | 2 | 2 | 2 |
7 | 2021 | 237 | 81 | 63 |
8 | 2022 | 300 | 87 | 25 |
9 | 2023 | 246 | 84 | 13 |
825 | 128 |
Тор-10 наблюдателей среди тех, кто набрал не менее 10 видов.
# | Наблюдатель | Виды | Наблюдения |
---|---|---|---|
1 | @shikhalev | 65 | 162 |
2 | @nastasya40 | 40 | 41 |
3 | @ginger_owl | 24 | 30 |
# | Наблюдатель | Виды | Наблюдения |
---|---|---|---|
1 | @shikhalev | 86 | 370 |
2 | @nastasya40 | 71 | 140 |
3 | @sundry_divers | 40 | 54 |
4 | @vit_polyak | 36 | 55 |
5 | @katrinkat84 | 36 | 68 |
6 | @ginger_owl | 34 | 49 |
7 | @ksanavolya | 27 | 35 |
8 | @tanniii66 | 18 | 26 |
Таксоны, наблюдавшиеся в сезоне 2023 впервые.
# | Наблюдатель | Виды | Наблюдения |
---|---|---|---|
1 | @shikhalev | 11 | 15 |
2 | @nastasya40 | 2 | 2 |
3 | @ginger_owl | 2 | 2 |
4 | @maxim_2412 | 1 | 1 |
5 | @deniselin | 1 | 1 |
Ранее найденные таксоны без подтвержденных наблюдений в последние 3 сезона.
Сравнение выполнялось со следующими территориями (с соответствующими ограничениями по месяцам):
# | Место | Виды | Наблюдения |
---|---|---|---|
1 | Ачитский городской округ (OSM 2023), SV, RU | 11 | 16 |
2 | Belokatayskiy rayon, BK, RU | 7 | 7 |
3 | Красноуфимский округ (OSM 2023), SV, RU | 55 | 136 |
4 | Mechetlinskiy rayon, BK, RU | 2 | 4 |
5 | Нижнесергинский муниципальный район (OSM 2023), SV, RU | 66 | 166 |
6 | Nyazepetrovskiy mun rayon (2020), CL, RU, RU | 12 | 13 |
95 | 341 |
Таксоны, не найденные ни у кого из соседей.
# | Наблюдатель | Виды | Наблюдения |
---|---|---|---|
1 | @nastasya40 | 31 | 44 |
2 | @shikhalev | 23 | 47 |
3 | @sundry_divers | 11 | 11 |
4 | @ginger_owl | 11 | 14 |
5 | @vit_polyak | 10 | 15 |
6 | @katrinkat84 | 10 | 20 |
7 | @ksanavolya | 8 | 8 |
8 | @naturalist6980 | 4 | 4 |
9 | @tanniii66 | 3 | 3 |
10 | @elenasyutkina | 2 | 2 |
11 | @romanovy-aleksey-i-elizaveta | 1 | 1 |
Таксоны, обнаруженные у соседей, но пока(?) не найденные здесь.