#!/usr/bin/env python3 """ Скрипт автономной сборки официальных полных текстов действующих нормативно-правовых актов РФ с портала КонсультантПлюс в структурированные PDF через wkhtmltopdf. Собираемые документы: 1. Постановление Правительства РФ № 354 (cons_doc_LAW_114247) 2. Постановление Госстроя РФ № 170 (cons_doc_LAW_44772) 3. СанПиН 2.1.3684-21 (cons_doc_LAW_376166) 4. Приказ Генеральной прокуратуры РФ № 45 (cons_doc_LAW_141740) """ import os import sys import time import subprocess import urllib.request from bs4 import BeautifulSoup import fitz HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'} DOCS = [ { 'law_id': 'cons_doc_LAW_114247', 'out_name': '04_ПП_РФ_354_Коммунальные_Услуги_Полный.pdf', 'title_prefix': 'Постановление Правительства РФ от 06.05.2011 № 354' }, { 'law_id': 'cons_doc_LAW_44772', 'out_name': '06_Постановление_Госстроя_170_Эксплуатация_Жилфонда_Полный.pdf', 'title_prefix': 'Постановление Госстроя РФ от 27.09.2003 № 170 (МДК 2-03.2003)' }, { 'law_id': 'cons_doc_LAW_376166', 'out_name': '07_СанПиН_2.1.3684-21_Санитарные_Требования_МКД_Полный.pdf', 'title_prefix': 'СанПиН 2.1.3684-21 (Постановление Главного санврача РФ № 3)' }, { 'law_id': 'cons_doc_LAW_141740', 'out_name': '08_Приказ_Генпрокуратуры_45_Инструкция_Рассмотрения_Обращений_Полный.pdf', 'title_prefix': 'Приказ Генеральной прокуратуры РФ от 30.01.2013 № 45' } ] DEST_DIR = '/home/matrixhasyou/mutt/legal_sources' os.makedirs(DEST_DIR, exist_ok=True) def build_law(law_info): law_id = law_info['law_id'] out_pdf = os.path.join(DEST_DIR, law_info['out_name']) base_url = f'https://www.consultant.ru/document/{law_id}/' print(f"\n=======================================================") print(f"[*] Сборка документа: {law_info['title_prefix']} ({law_id})") print(f"=======================================================") req = urllib.request.Request(base_url, headers=HEADERS) try: with urllib.request.urlopen(req, timeout=15) as resp: soup = BeautifulSoup(resp.read().decode('utf-8', errors='ignore'), 'html.parser') except Exception as e: print(f"[-] Ошибка загрузки оглавления {base_url}: {e}") return False raw_title = soup.find('title').text main_title = raw_title.split('\\')[0].strip() if '\\' in raw_title else raw_title.strip() links = [] seen = set() for a in soup.find_all('a', href=True): href = a['href'] if href.startswith(f'/document/{law_id}/') and len(href) > len(f'/document/{law_id}/'): clean_href = href.split('#')[0] if clean_href not in seen: seen.add(clean_href) links.append((clean_href, a.text.strip())) print(f"[+] Найдено разделов/статей: {len(links)}") html_parts = [f"""

{main_title}

Официальный действующий текст в актуальной редакции со всеми изменениями и приложениями
"""] for i, (href, sec_title) in enumerate(links, 1): url = 'https://www.consultant.ru' + href print(f" [{i}/{len(links)}] Загрузка: {sec_title[:60]}...") r = urllib.request.Request(url, headers=HEADERS) try: with urllib.request.urlopen(r, timeout=20) as res: sec_soup = BeautifulSoup(res.read().decode('utf-8', errors='ignore'), 'html.parser') html_parts.append(f'

{sec_title}

') for elem in sec_soup.find_all(['p', 'table']): if elem.name == 'p': t = elem.text.strip() if t and not 'Подготовлена редакция' in t and not 'КонсультантПлюс' in t and not 'Открыть документ' in t: html_parts.append(f'

{t}

') elif elem.name == 'table': html_parts.append(str(elem)) time.sleep(0.2) except Exception as e: print(f" [!] Ошибка загрузки раздела {href}: {e}") html_parts.append('') tmp_html = f'/tmp/{law_id}.html' with open(tmp_html, 'w', encoding='utf-8') as f: f.write('\n'.join(html_parts)) print(f"[*] Компиляция PDF через wkhtmltopdf: {out_pdf}...") subprocess.run([ 'wkhtmltopdf', '--encoding', 'utf-8', '--margin-top', '20mm', '--margin-bottom', '20mm', '--margin-left', '15mm', '--margin-right', '15mm', tmp_html, out_pdf ], check=True) try: doc = fitz.open(out_pdf) pages_count = len(doc) except Exception: pages_count = 0 size_kb = os.path.getsize(out_pdf) / 1024 print(f"[✔] УСПЕШНО СОЗДАН: {out_pdf} ({size_kb:.1f} КБ, {pages_count} страниц)") return True def main(): for doc_info in DOCS: build_law(doc_info) print("\n[🎉] ВСЕ ЗАКОНЫ РФ ПОЛНОСТЬЮ СОБРАНЫ В PDF!") if __name__ == '__main__': main()