Парсер законов на n8n через publication.pravo.gov.ru

:movie_camera: Делюсь видео/воркфлоу

:bust_in_silhouette: Автор: Адепт E-commerce

:robot: Что автоматизирует: Мониторинг и анализ новых законодательных актов РФ с портала pravo.gov.ru. Воркфлоу ежедневно проверяет RSS-ленту, фильтрует документы по типу (приказы, указы, постановления, распоряжения) и ключевым словам, скачивает PDF-файлы и с помощью Claude Sonnet 4.5 анализирует суть изменений простым языком. Готовые уведомления отправляются в Telegram с кратким анализом и ссылками на документы.

:wrench: Основные ноды:

  • Schedule Trigger — запуск каждый день в 10:00

  • HTTP Request — получение RSS с pravo.gov.ru (200 последних документов)

  • XML to JSON — парсинг RSS-ленты

  • Data Table — хранение истории отправленных документов (дедупликация)

  • Code (JavaScript) — фильтрация по дате, типу документа, ключевым словам

  • Split In Batches — обработка документов по одному

  • HTTP Request — скачивание PDF файлов

  • Anthropic Claude — анализ документа простым языком (для детей)

  • IF (размер файла) — проверка размера PDF (>30 МБ не анализируются)

  • Telegram — отправка уведомлений с анализом

  • Wait — пауза 1 минута между документами (чтобы не забанил Telegram)

Ключевые фишки:

  • Настраиваемые фильтры через переменные (дата, типы документов, ключевые слова, размер файла, Telegram ID)

  • Автоматическая дедупликация через Data Table

  • Умный анализ ИИ: Claude объясняет суть изменений языком, понятным даже 10-летнему ребёнку

  • Форматированное сообщение в Telegram с эмодзи и Markdown

:paperclip: JSON воркфлоу:

{
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 10
            }
          ]
        }
      },
      "id": "adf36990-67c9-4d75-a176-76af543a4b6e",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        1424,
        1024
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "3487d123-7656-4a35-9e03-e9730c4920e8",
      "name": "XML to JSON",
      "type": "n8n-nodes-base.xml",
      "typeVersion": 1,
      "position": [
        1968,
        1024
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combinationMode": "mergeByPosition",
        "options": {}
      },
      "id": "7f3dda64-0ac7-44c6-895d-2bbcda481324",
      "name": "Merge Data",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 2.1,
      "position": [
        2368,
        1008
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "d71cf2a8-a749-4640-ba23-54dd9decb8d9",
      "name": "Split In Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        2784,
        1008
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "value": "aeBpFIWfk0Fpt9am",
          "mode": "list",
          "cachedResultName": "sent_laws",
          "cachedResultUrl": "/projects/kTCa4jlG3hfnQ0Rc/datatables/aeBpFIWfk0Fpt9am"
        }
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        2176,
        928
      ],
      "id": "36a23e19-9a70-4208-93dd-6980a99ce2e5",
      "name": "Get row(s)",
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "resource": "document",
        "modelId": {
          "__rl": true,
          "value": "claude-sonnet-4-5-20250929",
          "mode": "list",
          "cachedResultName": "claude-sonnet-4-5-20250929"
        },
        "text": "Ты юрист. Кратко объясни простым и понятным языком суть изменений в документе (до 400 знаков). Чтобы понял 10 летний ребёнок",
        "inputType": "binary",
        "options": {
          "maxTokens": 1024
        }
      },
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "typeVersion": 1,
      "position": [
        3600,
        1024
      ],
      "id": "01ee451f-5100-4236-89da-378b5a9a8b61",
      "name": "Analyze document",
      "credentials": {
        "anthropicApi": {
          "id": "FYhg0KPqOd9Ze4EM",
          "name": "Anthropic account"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "da6ca9de-ded6-4180-8b82-c4bcc93ceaca",
              "name": "min_date",
              "value": "2026-01-25",
              "type": "string"
            },
            {
              "id": "cccf53d2-a543-4d5e-a389-81cdbcea1cc7",
              "name": "max_file_size_mb",
              "value": "30",
              "type": "string"
            },
            {
              "id": "e657ee16-277f-49ec-82bb-451be239ceb1",
              "name": "telegram_chat_id",
              "value": "26899549",
              "type": "string"
            },
            {
              "id": "d8578b74-e940-4885-8766-54467a25ba6a",
              "name": "allowed_types",
              "value": "Приказ, указ, постановление, Распоряжение",
              "type": "string"
            },
            {
              "id": "ea4ba7af-c441-4a09-b3e0-6d7ebf1a4860",
              "name": "keywords_filter",
              "value": "уголовно, исполнительн, надзор",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1600,
        1024
      ],
      "id": "d9290407-3aa9-48b0-b4cf-e45b45a9bbbc",
      "name": "Global Settings"
    },
    {
      "parameters": {
        "content": "# 📋 НАСТРОЙКИ WORKFLOW\n\n---\n\n## 🗓️ min_date\n**Минимальная дата публикации документов**\n- Формат: `YYYY-MM-DD`\n- Пример: `2026-01-25`\n- ⚠️ Документы раньше этой даты игнорируются\n\n## 📦 max_file_size_mb\n**Максимальный размер PDF для анализа ИИ**\n- Формат: число в мегабайтах\n- Пример: `30`\n- 💡 Файлы больше 30 МБ не анализируются\n\n## 💬 telegram_chat_id\n**ID чата Telegram для отправки уведомлений**\n- Формат: числовой ID\n- Пример: `26899549`\n- 🔍 Узнать свой ID: [@userinfobot](https://t.me/userinfobot)\n\n## 📄 allowed_types\n**Типы документов для отслеживания**\n- Формат: через запятую (регистр не важен)\n- Примеры:\n  - `\"Приказ\"` - только приказы\n  - `\"Приказ, Указ\"` - приказы и указы\n  - `\"Приказ, Указ, Постановление, Распоряжение\"`\n- 💡 Фильтр проверяет **начало** названия документа\n\n## 🔍 keywords_filter\n**Ключевые слова в заголовке (опционально)**\n- Формат: через запятую (регистр не важен)\n- Примеры:\n  - `\"уголовно\"` - только документы со словом \"уголовно\"\n  - `\"уголовно, исполнительн\"` - с любым из этих слов\n  - Оставьте пустым `\"\"` - чтобы отключить фильтр\n- 💡 Ищет слово **в любом месте** заголовка\n\n---\n\n✏️ **Для изменения настроек откройте этот узел**  \n🔄 **После изменений пересохраните workflow**",
        "height": 1120,
        "width": 528
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        832,
        480
      ],
      "typeVersion": 1,
      "id": "2c2334a0-866b-4dc4-87e4-107140c5853f",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "value": "aeBpFIWfk0Fpt9am",
          "mode": "list",
          "cachedResultName": "sent_laws",
          "cachedResultUrl": "/projects/kTCa4jlG3hfnQ0Rc/datatables/aeBpFIWfk0Fpt9am"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "doc_id": "={{ $('Проверка размера').item.json.docId }}",
            "sent_at": "={{ $now.toISO() }}",
            "title": "={{ $('Проверка размера').item.json.title }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "doc_id",
              "displayName": "doc_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "sent_at",
              "displayName": "sent_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "title",
              "displayName": "title",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        4016,
        1024
      ],
      "id": "b9542872-6d06-42e3-976f-3bd53b953f65",
      "name": "Insert row"
    },
    {
      "parameters": {
        "jsCode": "// Исправляем MIME type для PDF файлов\nconst items = $input.all();\n\nreturn items.map(item => {\n  if (item.binary && item.binary.data) {\n    // Явно устанавливаем правильный MIME type\n    item.binary.data.mimeType = 'application/pdf';\n    \n    // На всякий случай проверяем расширение\n    if (item.binary.data.fileName && !item.binary.data.fileName.endsWith('.pdf')) {\n      item.binary.data.fileName += '.pdf';\n    }\n  }\n  \n  return item;\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3216,
        1008
      ],
      "id": "17b0cddf-669c-48c7-9b57-684a5bb2b12d",
      "name": "Fix MIME type"
    },
    {
      "parameters": {
        "chatId": "={{ $('Global Settings').item.json.telegram_chat_id }}",
        "text": "=🔔 *Новый документ проанализирован*\n\n📋 {{ $('Split In Batches').item.json.title.split('\\n')[0].trim() }}\n\n{{ $json.content?.[0]?.text ? '┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈\\n\\n🤖 *Суть изменений:*\\n\\n' + $json.content[0].text + '\\n\\n┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈' : '⚠️ _Документ слишком большой для автоматического анализа_' }}\n\n📖 [Читать полностью]({{ $('Split In Batches').item.json.link }}) | 📥 [Скачать]({{ $('Split In Batches').item.json.pdfLink }})",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3776,
        1024
      ],
      "id": "47fb3ed8-acd9-4fdf-bcc2-cdd6e0265f41",
      "name": "Отправить анализ",
      "webhookId": "c8b30e62-c3b7-4ebc-a183-a8c25ef3ad57",
      "credentials": {
        "telegramApi": {
          "id": "XGd0mwsObb6mRNML",
          "name": "Бетси"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Global Settings').item.json.telegram_chat_id }}",
        "text": "=📜 {{ $json.title }}\n\n{{ $json.output ? \"📄 \" + $json.output : \"⚠️ Файл слишком большой для автоматического анализа.\" }}\n\n🔗 Читать на сайте: {{ $json.link }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3600,
        816
      ],
      "id": "12d0539d-bb77-4684-aa4c-28eb8fb67d98",
      "name": "Отправить документ",
      "webhookId": "c8b30e62-c3b7-4ebc-a183-a8c25ef3ad57",
      "credentials": {
        "telegramApi": {
          "id": "XGd0mwsObb6mRNML",
          "name": "Бетси"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 3
          },
          "conditions": [
            {
              "id": "c1199fea-5855-484e-93c5-f83698c5f3f9",
              "leftValue": "={{\n$binary.data.fileSize.toUpperCase().includes('GB') ? parseFloat($binary.data.fileSize) * 1024 :\n$binary.data.fileSize.toUpperCase().includes('MB') ? parseFloat($binary.data.fileSize) :\nparseFloat($binary.data.fileSize) / 1024\n}}",
              "rightValue": "={{ $('Global Settings').item.json.max_file_size_mb }}",
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        3376,
        1008
      ],
      "id": "0b908025-fe4b-451a-ad60-3c6e5a4c3bac",
      "name": "Проверка размера"
    },
    {
      "parameters": {
        "url": "={{ $json.pdfLink }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/pdf"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "098de8b5-5574-44fa-af57-433fb3989b24",
      "name": "Скачать PDF",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        3024,
        1008
      ]
    },
    {
      "parameters": {
        "jsCode": "const limit = 10;\n\nconst allInputs = $input.all();\n\n// Получаем настройки из Global Settings\nconst settings = $('Global Settings').first().json;\nconst minDate = new Date(settings.min_date);\nconst allowedTypes = settings.allowed_types\n  .split(',')\n  .map(t => t.trim().toLowerCase());\n\n// Новое: получаем ключевые слова для фильтрации\nconst keywordsFilter = settings.keywords_filter \n  ? settings.keywords_filter.split(',').map(k => k.trim().toLowerCase())\n  : [];\n\nconsole.log('Min date:', minDate.toISOString());\nconsole.log('Allowed types:', allowedTypes);\nconsole.log('Keywords filter:', keywordsFilter);\n\n// Находим RSS данные\nconst rssItem = allInputs.find(item => item.json.rss);\nif (!rssItem) {\n  console.log('ERROR: RSS data not found!');\n  return [];\n}\n\nconst rssData = rssItem.json.rss.channel.item;\nconsole.log('Total RSS items:', rssData.length);\n\n// Получаем список уже отправленных документов\nconst sentLaws = allInputs\n  .filter(item => item.json.doc_id)\n  .map(item => item.json.doc_id);\nconsole.log('Already sent:', sentLaws.length);\n\n// Фильтруем документы\nlet newLaws = rssData.filter(item => {\n  const pubDate = new Date(item.pubDate);\n  const docId = item.link.split('/').pop();\n  const titleLower = item.title.toLowerCase();\n  \n  // Проверяем тип документа (начало title)\n  const matchesType = allowedTypes.some(type => \n    titleLower.startsWith(type)\n  );\n  \n  // Новое: проверяем наличие ключевых слов\n  const matchesKeywords = keywordsFilter.length === 0 || \n    keywordsFilter.some(keyword => titleLower.includes(keyword));\n  \n  // Проверяем дату\n  const afterMinDate = pubDate >= minDate;\n  \n  // Проверяем, что не отправляли ранее\n  const notSent = !sentLaws.includes(docId);\n  \n  console.log(`${docId.substring(0, 20)}: type=${matchesType}, keywords=${matchesKeywords}, date=${afterMinDate}, notSent=${notSent}`);\n  \n  return matchesType && matchesKeywords && afterMinDate && notSent;\n});\n\nconsole.log('Filtered laws:', newLaws.length);\n\n// Подготавливаем данные для отправки\nnewLaws = newLaws.map(law => {\n  const docId = law.link.split('/').pop();\n  return { \n    json: {\n      ...law,\n      docId: docId,\n      pdfLink: `http://publication.pravo.gov.ru/file/pdf?eoNumber=${docId}`\n    }\n  };\n});\n\nreturn newLaws.slice(0, limit);"
      },
      "id": "4831fe4b-f915-4e0c-8352-7343f527b839",
      "name": "Фильтр и подготовка",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2560,
        1008
      ]
    },
    {
      "parameters": {
        "url": "http://publication.pravo.gov.ru/api/rss?pageSize=200",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "id": "8e3d83d1-6005-4906-91a6-55138e33e9a4",
      "name": "Получить RSS",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        1776,
        1024
      ]
    },
    {
      "parameters": {
        "amount": 60,
        "unit": "seconds"
      },
      "id": "963a481b-fcd0-4c0b-b3d2-20a47bd8606a",
      "name": "Wait 1m",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        4240,
        1024
      ],
      "webhookId": "f45730e5-db55-43dd-9b94-c70d01822b94"
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Global Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "XML to JSON": {
      "main": [
        [
          {
            "node": "Get row(s)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Data": {
      "main": [
        [
          {
            "node": "Фильтр и подготовка",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split In Batches": {
      "main": [
        [],
        [
          {
            "node": "Скачать PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s)": {
      "main": [
        [
          {
            "node": "Merge Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze document": {
      "main": [
        [
          {
            "node": "Отправить анализ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Global Settings": {
      "main": [
        [
          {
            "node": "Получить RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert row": {
      "main": [
        [
          {
            "node": "Wait 1m",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fix MIME type": {
      "main": [
        [
          {
            "node": "Проверка размера",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Отправить анализ": {
      "main": [
        [
          {
            "node": "Insert row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Отправить документ": {
      "main": [
        [
          {
            "node": "Insert row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Проверка размера": {
      "main": [
        [
          {
            "node": "Отправить документ",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Analyze document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Скачать PDF": {
      "main": [
        [
          {
            "node": "Fix MIME type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Фильтр и подготовка": {
      "main": [
        [
          {
            "node": "Split In Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Получить RSS": {
      "main": [
        [
          {
            "node": "XML to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 1m": {
      "main": [
        [
          {
            "node": "Split In Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "e24f19d897fbcfb06d96c9173e7c8dc910053690edc62df56555ef40299436c1"
  }
}
3 лайка

Всем привет. Попробую это флоу. Все здорово, только у меня нет подписки на Антропик. И я решил делать через доклинг, а так как в pdf там картинки, то приходится использовать ocr. И у меня файл парсится 3-5 минут. В чем проблема кто подскажет. Неужели через Claude Sonnet настолько быстрее? Для информации: сервер 8 Gb, 4 core. память заружена на 20 %.

Подписка и не нужна, чтобы по api юзать антропик

А какой ocr? Свой развернул?

Да, на сервере в Docker, принимает файлы, отдает Markdown, при необходимости делает OCR.

ну 3-5 минут, зато вероятно что ошибок меньше. ocr штука старая и довольно стабильная. Я бы тоже через неё делал, но не хотел усложнять снимая как его развернуть на vps

Сервак то не перегружается при распознании? 3-5 минут на каких файлах? Какой размер, кол-во страниц?

При распознавании сервер грузится на 100%, 2 страницы 300 kB. Распознает плохо, с ошибками. Может из-за качества pdf на gov.ru. Pdf с текстом и таблицами грузит хорошо.

Ого! Видимо такой ocr. Вообще он себя так не должен вести. Может есть смысл рассмотреть другой?

Не)) Разобрался. У меня русского языка на ocr не было. Как установил - все полетело). Спасибо.

Неожиданное решение :grinning_face:

Я не смотрел какие маркдауды ocr выдавало. А оказалось, что так как русского языка нет, выходил примерно такой текст латиницей: HeKOTOpBIX Bonpocax OpTaHOB Kpass, а нейронка понимала смысл и приходило понятное сообщение в ТГ. Оч. смешно :rofl:

1 лайк

использовал воркфлоу для Белорусского портала. Пришлось допиливать, т.к. наш портал блокирует автоматические запросы и ссылки на пдф не дает. Но ничего помог Gemini. Вместо Antropic использовал Gemini-ноду тоже. В итоге всё работает.

3 лайка