mutt/fetch_and_build_remaining_laws.py

153 lines
6.5 KiB
Python
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {{ font-family: 'DejaVu Sans', sans-serif; margin: 25mm 20mm; font-size: 10pt; line-height: 1.45; color: #111; }}
h1 {{ font-size: 14pt; text-align: center; border-bottom: 2px solid #333; padding-bottom: 8px; margin-bottom: 20px; }}
h2 {{ font-size: 11pt; margin-top: 22px; border-bottom: 1px solid #777; padding-bottom: 4px; color: #002b49; page-break-after: avoid; }}
p {{ margin: 6px 0; text-align: justify; text-indent: 1.5em; }}
table {{ width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 9pt; }}
th, td {{ border: 1px solid #777; padding: 5px 8px; text-align: left; }}
th {{ background-color: #f0f0f0; }}
.header-meta {{ text-align: center; font-style: italic; margin-bottom: 25px; color: #444; }}
</style>
</head>
<body>
<h1>{main_title}</h1>
<div class="header-meta">Официальный действующий текст в актуальной редакции со всеми изменениями и приложениями</div>
"""]
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'<h2>{sec_title}</h2>')
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'<p>{t}</p>')
elif elem.name == 'table':
html_parts.append(str(elem))
time.sleep(0.2)
except Exception as e:
print(f" [!] Ошибка загрузки раздела {href}: {e}")
html_parts.append('</body></html>')
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()