Короткий дисклеймер: Проект живой, ленту бот крутит дважды в сутки. Почитать готовый результат можно в моём Telegram‑канале Bio_news (ссылка в конце), а покритиковать — тут. А теперь к тому, как вся эта система устроена.

  • Введение (Кто меня пнул или что меня пнуло?)

    Биология, как известно, — наука XXI века. Сейчас ею занимаются все, кому не лень, ну а те немногие, кто не занимается профессионально, как минимум активно интересуются. При этом «мокрых» данных генерируется огромное количество, а уж in silico и подавно — Big Data и все такое. Естественно, не всё публикуется, но тем не менее каждый день выходит тонна биологических публикаций в нескольких тысячах (!) рецензируемых научных журналов. Однако ж при всем этом простой, четкой и качественной новостной ленты или регулярно выходящих кратких, свежих и интересных новостей из мира биологии попросту нет. Во всяком случае, я такого не нашел ни на русском, ни на английском. Обычно это либо интересные, но длиннющие статьи профессионалов (и не очень), для чтения которых нужно профильное образование и пара часов свободного времени (и к тому же они совершенно не обязательно посвящены новым открытиям), либо новости биологии подаются вперемешку с космосом и IT‑трендами (ну кому это интересно?), либо вовсе какой‑то годный материал густо присыпан всякой гомеопатией и кликбейтом в духе «ученые опять победили рак». А вот такого, чтобы сварил себе утром чашечку кофейку и потратил 3 минуты на несколько коротких новостей про динозавров, какие‑нибудь тычинки, фермент EC1.1.1.1 и метаболизм гипертермофильных архей — вот такого нету. Ну а раз нет, значит, надо сделать самому.

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

  • Как все работает:

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

    • Этап 1. Сбор новостей. Каждые два часа фоновые скрипты прочёсывают источники, собирают свежие материалы, относящиеся к биологии, и закидывают их в свой пул новостей. Источники в основном англоязычные агрегаторы (так как крупные издательства посылают куда подальше нашего маленького парсера), но не только — среди источников есть, например, RSS Американского общества микробиологов, есть и другие, всего пока 13 источников. Сюда же я могу вручную через Телеграм‑бота подкинуть ссылку на интересную мне статью или уже готовый текст новости (LLM потом поправит орфографию, пунктуацию и грамматику и приведёт текст к формату выпуска, но содержание, стиль и порядок мыслей не трогает) — у таких «ручных» новостей всегда наивысший вес (про веса ниже).

      Этап 2. Фильтрация и ранжирование.

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

      Всё это работает на большом наборе правил. Одно из них, например, заключается в том, что если один источник вдруг начинает заваливать пул новостями сильнее остальных, ему временно снижают вес штрафом, который потом потихоньку тает, пока источник не «остынет». Без этого мы могли бы читать целые выпуски, например, про integrative agrobiology and carbon sequestration или новые веяния в том, как ИИ смоделировал то, смоделировал сё и перемоделировал потом всё это.

      Этап 3. LLM.

      Каждые 12 часов пачка из 21 новости отправляется к моделям: Gemini 3.6 Flash или Claude Sonnet 5. Их задача — выбрать 7 самых интересных новостей и сверстать финальный выпуск: переписать каждую находку своими словами по‑русски, в едином стиле выпуска; подобрать к ней «панчлайн» — сочную завершающую фразу в шутливой форме (эдакие шутки из склепа, за которые мне пока зачастую стыдно) и добавить дополнительные рубрики (о них ниже). Невыбранные новости либо возвращаются в пул, либо (если модель решила, что они вообще кривые и косые) навсегда выкидываются.

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

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

      Изначально я давал LLM гораздо больше свободы, но они откровенно подтупливают — частично из‑за того, что модели не топовые, но главное потому что биологические темы не прощают импровизаций, которые нейросети так обожают: «проверю код, а не буду гадать», после чего делает ровно наоборот, вот это вот всё. В общем, пришлось поступить по‑государственному и жёстко закрутить гайки: основной отсев и фильтрация идут ещё на втором этапе и сама LLM прилично зарегулирована, так что у неё не так много шансов накосячить. Примером одного из многих запретов является то, что нельзя обесценивать науку иронией в духе «и так все знали». Кроме одного специально прописанного исключения: если исследование было сделано в Великобритании и результат действительно самоочевиден («учёные подтвердили, что голодные люди хотят есть»), то можно и нужно позволить себе саркастическое «британские учёные выяснили». Расширять это на другие страны нельзя — только Британия имеет полное право быть смешной таким образом.

      Помимо собственно 7 новостей LLM докидывают в выпуск дополнительные рубрики — «Статью недели», «День в истории» и «Персону дня»:

      • Статья недели. Идея в том, чтобы посмотреть, что именно биологи читают прямо сейчас. Для этого мы используем bioRxiv — портал биологических и биомедицинских препринтов. Там есть два варианта атрибутов: BIO и MED, и мы берём только BIO, тем самым опять жёстко притесняя медицину (так ей и надо). Берём мы самую читаемую статью на отрезке в одну неделю. Если какую‑то статью уже брали в предыдущем выпуске, скрипт берёт следующую по количеству просмотров. Важно понимать, что в этой рубрике выходят не обязательно самые свежие материалы, а именно самые читаемые на этой неделе статьи, хотя почти всегда одно соответствует другому.

      • День в истории. Тут информация берётся из Википедии с открытого API “в этот день”, который отдаёт список исторических событий для даты. Алгоритм по ключевым словам оставляет из них только биологические, и только старше 50 лет. Но поскольку событий после такого отсева находится мало, то рубрика появляется редко. Ну как редко — пока было всего 1 раз.

      • Персона дня. Здесь случайным образом показываются учёные‑биологи, которые с большой долей вероятности внесли весомый вклад в свою область. Тут пришлось поизобретать велосипед: Google Scholar ботов не любит, а у Semantic Scholar нет метки области науки у самого автора — только у статей. Поэтому чтобы вообще найти биолога, скрипт ищет статьи по случайной теме с меткой области «Biology» и собирает их авторов, среди них ищет тех, у кого индекс Хирша ≥ 30, что подразумевает серьёзный вклад в науку (не буду уточнять под кого подгонялась эта цифра). Но одного индекса мало: автор мог просто участвовать в чужой статье не по своей специальности. Поэтому дальше среди самых цитируемых статей такого учёного скрипт ищет ту, где он указан последним автором, что в биологии обычно указывает на то, что это руководитель работы, а значит, тема самой статьи с большой долей вероятности и есть его основная область интересов. Я добавил эту рубрику потому что, мне показалось, что читателю интересно посмотреть на живого успешного биолога, и кто знает — может быть, кто‑нибудь из читателей обнаружит в одном из выпусков себя?

      Этап 4. Публикация. В результате всей этой бурной деятельности мы два раза в сутки имеем готовый дайджест, состоящий из 7 главных новостей, статьи недели, дня в истории (вечно отсутствующего) и персоны дня. Система кидает свёрстанный дайджест в мой приватный телеграм‑чат, где я, просто нажав на кнопочку на панели бота, могу выпустить его в публичный ТГ‑канал. Перед этим я могу попросить бота удалить какую‑то из новостей, если мне кажется, что она неподходящая или кривая. Я было дал возможность боту кидать дайджест сразу в канал, но он еще маленький и неразумный, поэтому пришлось ввести тумблер, что называется «human‑in‑the‑loop». В планах есть и другие кнопки (ВК, ФБ, может, еще чего), ну а пока после публикации в ТГ‑канале специальный синхрофазотрон/десептикон синхробот Дзена кидает его в Дзен. А то мало ли, Телеграм решат ограничить — я о таких идеях, конечно, не слышал, но вдруг фантазии такие у кого‑нибудь появятся.

  • Архитектура. Ниже представлена полная схема того, как устроен весь процесс — от съёма источников до генерации и публикации финального выпуска:

  • Ну и товар лицом! Проект всё ещё находится в стадии активного допиливания, в первую очередь алгоритмы фильтрации, вот сейчас занялся скринингом статей‑исходников по Q1 Scimago. Но мне самому уже интересно читать, что он там находит и публикует и ручных корректировок уже готового выпуска, как мелких, так выкидывания целых отдельных новостей из дайджеста, все меньше и меньше.

    Прошу любить и жаловать. Лайк, шер, репост. Буду искренне рад фидбеку, критике и свежим идеям. Книга отзывов и предложений находится ровно под этой статьей в комментариях.

  • ПЫСЫ. Техника. Все скрипты бота работают на Python и были написаны и редактируются с помощью моделей Claude (Sonnet и Opus разных версий). Использовались следующие внешние библиотеки:

    • python-telegram-bot (telegram, telegram.ext, telegram.request) — сам Telegram‑бот: приём команд, JobQueue для периодических задач (RSS, статья недели, проверка окна дайджеста). JobQueue использует APScheduler внутри себя — это видно в логах (apscheduler.scheduler, apscheduler.executors), но проект его не импортирует и не устанавливает отдельно, это транзитивная зависимость python-telegram-bot.

    • anthropic — официальный SDK для вызова Claude (from anthropic import Anthropic).

    • requests — весь остальной HTTP: DOI/CrossRef (url_resolver.py), Semantic Scholar (person_of_the_day.py), Wikipedia onthisday (history_today.py), и Gemini тоже — намеренно через сырой REST, без официального SDK google-generativeai.

    • feedparser — разбор RSS‑фидов, один вызов: feedparser.parse(url) в rss_puller.py.

    • beautifulsoup4 (bs4) — разбор HTML‑страниц агрегаторов при поиске DOI первоисточника (BeautifulSoup(html, "html.parser") в url_resolver.py).

    • python-dotenv — load_dotenv(override=True) в bot.py и digest_builder.py, с явным комментарием в коде: должен вызываться ДО импорта config.py, иначе переменные из .env не успеют подхватиться.

    • httpx — используется не только как транспорт внутри python-telegram-bot, а и напрямую: в bot.py через него настроен принудительный IPv4 (httpx.AsyncHTTPTransport(local_address="0.0.0.0")) — обход проблемы с подключением к Telegram по IPv6.

    • googlenewsdecoder — декодирование редиректных ссылок Google News в настоящий адрес статьи (from googlenewsdecoder import new_decoderv1 в url_resolver.py).

    Логирование. Настроено штатным logging.basicConfig — без ротации: файл bot_stderr.log растёт бесконечно, пока его не почистить руками. За сегодня он благополучно распух до состояния, когда Get-Content без -Tail открывать уже страшно.