# Как отслеживать новые ИИ-модели и обновления LLM: RSS, GitHub и Telegram-уведомления

> AI Release · @ai_release1 · https://ai-release.org/guides/kak-otslezhivat-novye-ii-modeli-i-obnovleniya-llm-rss-g.html

## TL;DR

- Время выполнения: 40–60 минут
- Уровень сложности: Intermediate
- Понадобится: Linux/macOS или Windows с WSL2, Python 3 (проверьте `python3 -V`), утилита `curl`, аккаунт Telegram и бот от @BotFather, доступ к `crontab` (или GitHub Actions), ~50 МБ свободного места под лог и файл состояния

## Требования и подготовка
![Иллюстрация к разделу «Требования и подготовка»](https://ai-release.org/guides/img/kak-otslezhivat-novye-ii-modeli-i-obnovleniya-llm-rss-g-ru-1.jpg)


- ОС: Linux (Debian/Ubuntu), macOS или Windows 11 с WSL2. Точный список поддерживаемых версий Python для вашей ОС — сверьтесь с документацией python.org.
- Python 3 и модуль `sqlite3`/`xml.etree` из стандартной библиотеки — внешние пакеты не нужны, поэтому версии зависимостей подбирать не придётся.
- `curl` для ручной проверки лент: `curl --version`.
- Бот Telegram: напишите @BotFather команду `/newbot`, получите токен вида `123456789:AA...` (значение ваше, не публикуйте).
- Права: возможность писать в каталог `/opt/ai-watch` (или любой свой) и редактировать свой `crontab`.
- Свободное место: файл состояния и лог растут медленно, но лог нужно ротировать.

Что отслеживать — определитесь до настройки. Ориентир по самим моделям: [Лучшие нейросети и ИИ-модели 2026](https://ai-release.org/guides/luchshie-nejroseti-i-modeli-2026.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide).

Чек-лист готовности:

- [ ] `python3 -V` печатает версию без ошибок
- [ ] токен бота сохранён в переменной окружения, а не в коде
- [ ] вы отправили боту любое сообщение (иначе он не сможет писать первым)
- [ ] выбран каталог проекта и создан `feeds.json`
- [ ] известен ваш `chat_id`

**Пример:** проверка окружения одной строкой.

```bash
python3 -V && curl --version | head -n 1
```

Ожидаемый вывод — две строки, например `Python 3.12.x` и `curl 8.x.x`. Если вторая строка отсутствует, установите `curl` пакетным менеджером.

## Шаг 1. Соберите и проверьте источники (RSS и GitHub)

**Что сделать:** составьте файл `feeds.json`, где ключ — человекочитаемое имя источника, значение — URL ленты. Для GitHub не нужен API-ключ: у каждого репозитория есть Atom-лента релизов вида `https://github.com/<owner>/<repo>/releases.atom`.

**Зачем:** один скрипт читает разнородные источники одинаково — RSS 2.0 и Atom парсятся одним кодом, а имена ключей потом попадают в заголовок уведомления.

**Как проверить:** каждая лента должна отдавать HTTP 200 и XML, а не HTML-страницу.

**Пример:** `feeds.json` с реальным репозиторием библиотеки Transformers.

```json
{
  "transformers-releases": "https://github.com/huggingface/transformers/releases.atom",
  "arxiv-cs-cl": "https://arxiv.org/rss/cs.CL"
}
```

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

```bash
curl -sI https://github.com/huggingface/transformers/releases.atom | head -n 3
curl -s https://arxiv.org/rss/cs.CL | head -c 120
```

Ожидаемо: первая команда печатает `HTTP/2 200` (или `HTTP/1.1 200`), вторая — начало XML с тегом `<rss`. Если вместо XML пришёл `<html>`, лента недоступна или требует другого URL.

Почему это работает: GitHub отдаёт релизы как обычный Atom-фид, поэтому отдельный парсер GitHub не нужен — отличие только в тегах `entry` вместо `item`.

Если вы следите за открытыми весами моделей, список репозиториев удобно взять отсюда: [Какие ИИ-модели имеют открытые веса и как их запустить бесплатно](https://ai-release.org/guides/kakie-ii-modeli-imeyut-otkrytye-vesa-i-kak-ih-zapustit.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide). Добавляйте в `feeds.json` только те репозитории, где релизы действительно публикуются.

| Источник | Что даёт | Авторизация | Частота опроса |
|---|---|---|---|
| GitHub `releases.atom` | номера версий, теги, changelog | не нужна | 1–2 раза в час |
| RSS 2.0 блога/хаба | анонсы моделей и статей | не нужна | раз в час |
| GitHub REST `/releases` | JSON, фильтры по `tag_name` | токен, лимиты — сверьтесь с документацией GitHub | по расписанию |

## Шаг 2. Настройте сбор и доставку в Telegram
![Иллюстрация к разделу «Шаг 2. Настройте сбор и доставку в Telegram»](https://ai-release.org/guides/img/kak-otslezhivat-novye-ii-modeli-i-obnovleniya-llm-rss-g-ru-2.jpg)


**Что сделать:** положите в каталог проекта скрипт `watch.py`. Он читает `feeds.json`, скачивает ленты, сравнивает идентификаторы записей с `state.json` и отправляет только новые записи в Telegram через Bot API.

**Зачем:** дедупликация по `guid`/`id` защищает от повторных уведомлений при каждом запуске cron; файл состояния переживает перезапуски.

**Как проверить:** запустите скрипт вручную и посмотрите на консольный вывод и сообщение в чате.

**Пример:** сохраните как `watch.py`.

```python
#!/usr/bin/env python3
import json, os, urllib.request, urllib.parse
import xml.etree.ElementTree as ET
from pathlib import Path

STATE, FEEDS = Path("state.json"), Path("feeds.json")
TG_TOKEN, TG_CHAT = os.environ["TG_TOKEN"], os.environ["TG_CHAT_ID"]

def fetch(url):
    req = urllib.request.Request(url, headers={"User-Agent": "ai-watch/1.0"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return r.read()

def parse(xml_bytes):
    root, items = ET.fromstring(xml_bytes), []
    for it in root.iter("item"):                     # RSS 2.0
        items.append({"id": (it.findtext("guid") or it.findtext("link") or "").strip(),
                      "title": (it.findtext("title") or "").strip(),
                      "link": (it.findtext("link") or "").strip()})
    if not items:                                    # Atom
        ns = {"a": "http://www.w3.org/2005/Atom"}
        for e in root.findall("a:entry", ns):
            link = e.find("a:link", ns)
            items.append({"id": (e.findtext("a:id", namespaces=ns) or "").strip(),
                          "title": (e.findtext("a:title", namespaces=ns) or "").strip(),
                          "link": link.get("href") if link is not None else ""})
    return [i for i in items if i["id"]]

def tg_send(text):
    data = urllib.parse.urlencode({"chat_id": TG_CHAT, "text": text,
                                   "parse_mode": "HTML",
                                   "disable_web_page_preview": "true"}).encode()
    req = urllib.request.Request(f"https://api.telegram.org/bot{TG_TOKEN}/sendMessage", data=data)
    with urllib.request.urlopen(req, timeout=30) as r:
        return r.status

def main():
    feeds = json.loads(FEEDS.read_text(encoding="utf-8"))
    seen = json.loads(STATE.read_text(encoding="utf-8")) if STATE.exists() else {}
    for name, url in feeds.items():
        try:
            items = parse(fetch(url))
        except Exception as e:
            print(f"[ERR] {name}: {e}")
            continue
        known = set(seen.get(name, []))
        new = [i for i in items if i["id"] not in known]
        for i in reversed(new):
            tg_send(f"<b>{name}</b>\n{i['title']}\n{i['link']}")
        seen[name] = list(known | {i["id"] for i in new})[-200:]
        print(f"[OK] {name}: new={len(new)}")
    STATE.write_text(json.dumps(seen, ensure_ascii=False, indent=2), encoding="utf-8")

if __name__ == "__main__":
    main()
```

Узнайте `chat_id`: отправьте боту сообщение и выполните запрос `getUpdates`.

```bash
curl -s "https://api.telegram.org/bot$TG_TOKEN/getUpdates"
```

В ответе найдите `"chat":{"id":123456789,...}` — это и есть `TG_CHAT_ID`. Затем запуск:

```bash
export TG_TOKEN="<токен-из-BotFather>"
export TG_CHAT_ID="<ваш-chat_id>"
python3 watch.py
```

Ожидаемый вывод при первом запуске: строки `[OK] transformers-releases: new=<N>` и `[OK] arxiv-cs-cl: new=<N>`, а в чате — пачка сообщений. Повторный запуск должен дать `new=0`.

Поставьте опрос на расписание — например, каждые 30 минут:

```bash
crontab -e
```

```cron
*/30 * * * * cd /opt/ai-watch && TG_TOKEN=<токен> TG_CHAT_ID=<chat_id> /usr/bin/python3 watch.py >> watch.log 2>&1
```

Альтернатива без своего сервера — GitHub Actions; секреты задаются в Settings → Secrets and variables → Actions, расписание в cron-синтаксисе:

```yaml
name: watch
on:
  schedule: [{cron: "0 */6 * * *"}]
  workflow_dispatch:
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: python3 watch.py
        env:
          TG_TOKEN: ${{ secrets.TG_TOKEN }}
          TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
```

Почему это работает: cron не наследует переменные из интерактивной оболочки, поэтому `TG_TOKEN` и `TG_CHAT_ID` указаны прямо в строке задания.

## Шаг 3. Проверьте работу и настройте контроль
![Иллюстрация к разделу «Шаг 3. Проверьте работу и настройте контроль»](https://ai-release.org/guides/img/kak-otslezhivat-novye-ii-modeli-i-obnovleniya-llm-rss-g-ru-3.jpg)


**Что сделать:** проведите три проверки — свежесть лога, отсутствие дублей и доставку тестового уведомления.

**Зачем:** молчащий бот неотличим от сломанного скрипта, поэтому нужен явный признак успеха на каждой итерации.

**Как проверить:**

```bash
tail -n 20 /opt/ai-watch/watch.log
docker --version 2>/dev/null | head -n 1
python3 - <<'PY'
import json, pathlib
s = json.loads(pathlib.Path("state.json").read_text(encoding="utf-8"))
print({k: len(v) for k, v in s.items()})
PY
```

Ожидаемый результат:

1. В `watch.log` появляются новые строки `[OK] ...` примерно с той же периодичностью, что задана в cron.
2. Словарь на экране содержит по ключу на каждый источник из `feeds.json`, счётчики не равны нулю.
3. Строк `[ERR]` в логе нет.

Тестовая доставка в обход расписания:

```bash
curl -s -X POST "https://api.telegram.org/bot$TG_TOKEN/sendMessage" \
  -d chat_id="$TG_CHAT_ID" -d text="ai-watch: проверка канала доставки"
```

Ожидаемо: JSON `{"ok":true,"result":{...}}` и сообщение в чате. Если пришло `"ok":false`, смотрите раздел проблем.

Настройте ротацию, иначе лог вырастет незаметно:

```bash
sudo tee /etc/logrotate.d/ai-watch >/dev/null <<'EOF'
/opt/ai-watch/watch.log {
  weekly
  rotate 4
  compress
  missingok
  notifempty
}
EOF
```

Почему это работает: `logrotate` переименовывает файл и создаёт новый, а cron продолжает писать в тот же путь.

Итог шага: у вас есть расписание, лог с меткой `[OK]` на каждый источник и подтверждённый канал доставки. Смежные сценарии автоматизации описаны в [гайде по ИИ-помощнику для интернет-магазина на локальной модели](https://ai-release.org/guides/kak-sobrat-ii-pomoschnika-dlya-internet-magazina-na-lok.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) — там тот же принцип «скрипт + расписание + внешний сервис».

## Возможные проблемы и решения

**Проблема. Лента не парсится, в логе ошибка синтаксиса XML**

- Ошибка: `[ERR] arxiv-cs-cl: syntax error: line 1, column 0`
- Причина: по URL вернулась HTML-страница (редирект на страницу логина, 404 или смена адреса ленты), а парсер ожидает XML.
- Решение: проверьте ответ вручную и уберите источник из `feeds.json`, если лента недоступна.

```bash
curl -sI "<URL-ленты>" | head -n 3
curl -s "<URL-ленты>" | head -c 200
```

Если видите `HTTP/2 404` или `<html>` — URL устарел, сверьтесь с документацией источника.

**Проблема. Бот не доставляет сообщения**

- Ошибка: `{"ok":false,"error_code":400,"description":"Bad Request: chat not found"}`
- Причина: неверный `chat_id`, либо вы ни разу не написали боту — он не может инициировать диалог первым.
- Решение: отправьте боту любое сообщение, повторите `getUpdates`, скопируйте `message.chat.id` целиком (он может быть отрицательным для групп) и обновите `TG_CHAT_ID`.

```bash
curl -s "https://api.telegram.org/bot$TG_TOKEN/getUpdates" | grep -o '"chat":{"id":-\?[0-9]*'
```

**Проблема. Cron падает сразу при старте**

- Ошибка: `KeyError: 'TG_TOKEN'`
- Причина: переменные окружения заданы в `~/.bashrc`, который cron не читает.
- Решение: перенесите `TG_TOKEN` и `TG_CHAT_ID` в строку задания cron, как в примере Шага 2, либо используйте `EnvironmentFile` в systemd-таймере.

```bash
crontab -l | grep watch.py
```

Команда должна показать строку с обеими переменными перед `/usr/bin/python3`.

## Резюме и следующие шаги

Вы получили автономный конвейер: `feeds.json` с источниками, `watch.py` с дедупликацией через `state.json` и cron, который раз в 30 минут отправляет новые записи в Telegram. Дальше добавьте источники, которых не хватает, и настройте фильтр по ключевым словам в заголовке, чтобы снизить шум.

## FAQ

**Чем GitHub Releases отличается от RSS-ленты?**
Ничем по механике: `releases.atom` — это Atom-фид, тот же формат, что и RSS, просто с тегами `entry` вместо `item`, поэтому один парсер читает оба.

**Обязательно ли держать свой сервер?**
Нет: тот же скрипт запускается по расписанию в GitHub Actions, секреты `TG_TOKEN` и `TG_CHAT_ID` хранятся в настройках репозитория.

**Почему приходит старая запись повторно?**
Скорее всего удалён или пересоздан `state.json` — идентификаторы потеряны. Скрипт считает все записи новыми; верните файл из резервной копии или удалите лишние сообщения вручную.

**Какой интервал опроса выбрать?**
Начните с 30–60 минут: этого достаточно для релизов и анонсов, а лимиты API и rate limit остаются в норме. Точные ограничения конкретного источника сверьте с его документацией.
