Save all currently accumulated repository changes as a backup snapshot for Gitea so no local work is lost.
308 lines
11 KiB
JavaScript
308 lines
11 KiB
JavaScript
// Готовый код для вставки в ноду "парсим суд" в n8n workflow
|
||
// Скопируйте весь этот код в поле Body ноды HTTP Request
|
||
|
||
export default async function ({ page, context }) {
|
||
// Получаем данные из переменных n8n workflow
|
||
// ВАЖНО: шаблоны n8n должны быть в кавычках!
|
||
const url = '{{ $json.link }}';
|
||
const status = '{{ $json.status }}';
|
||
|
||
if (!url || url === '') throw new Error('❌ Не передан url');
|
||
|
||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||
|
||
// Определяем тип суда по URL
|
||
const isMoscowCourt = /mos-(gorsud|sud)\.ru/.test(url);
|
||
const isRegionalCourt = /\.sudrf\.ru/.test(url) && !isMoscowCourt;
|
||
|
||
// Установка заголовков и поведения браузера
|
||
await page.setViewport({ width: 1920, height: 1080 });
|
||
await page.setExtraHTTPHeaders({
|
||
"Referer": isMoscowCourt ? "https://mos-sud.ru/" : "https://sudrf.ru/",
|
||
"Origin": isMoscowCourt ? "https://mos-sud.ru" : "https://sudrf.ru",
|
||
"Accept-Language": "ru,en;q=0.9",
|
||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||
"Upgrade-Insecure-Requests": "1",
|
||
});
|
||
await page.setUserAgent(
|
||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
||
);
|
||
|
||
await page.goto(url, { waitUntil: "networkidle2", timeout: 60000 });
|
||
|
||
// Закрыть баннеры cookies, если есть
|
||
try {
|
||
await page.waitForSelector("#cookie-disclaimer .cd-close-button, .cookie-accept, .cookie__close", { timeout: 3000 });
|
||
const btns = await page.$$("#cookie-disclaimer .cd-close-button, .cookie-accept, .cookie__close");
|
||
if (btns[0]) await btns[0].click();
|
||
} catch (_) {}
|
||
|
||
await sleep(2000);
|
||
|
||
// ========================================
|
||
// ПАРСИНГ РЕГИОНАЛЬНЫХ СУДОВ (*.sudrf.ru)
|
||
// ========================================
|
||
if (isRegionalCourt) {
|
||
// Определяем div для парсинга (аналогично parscourt.php)
|
||
const divId = (status === 'представительство в суде 1й инстанции' ||
|
||
status === 'выдача листа' ||
|
||
status === 'исполнительное производство' ||
|
||
status === 'заявление на лист') ? 'cont2' : 'cont3';
|
||
|
||
const events = await page.evaluate((divId) => {
|
||
const clean = (str) => (str ? str.replace(/\s+/g, ' ').trim() : '');
|
||
|
||
const div = document.querySelector(`#${divId}`);
|
||
if (!div) return [];
|
||
|
||
const rows = Array.from(div.querySelectorAll('tr'));
|
||
const events = [];
|
||
|
||
rows.forEach((row) => {
|
||
const tds = row.querySelectorAll('td');
|
||
if (tds.length < 2) return;
|
||
|
||
const event_name = clean(tds[0]?.textContent || '');
|
||
const event_date = clean(tds[1]?.textContent || '');
|
||
const event_time = clean(tds[2]?.textContent || '');
|
||
const location = clean(tds[3]?.textContent || '');
|
||
const event_result = clean(tds[4]?.textContent || '');
|
||
const event_basis = clean(tds[5]?.textContent || '');
|
||
const note = clean(tds[6]?.textContent || '');
|
||
const publication_date = clean(tds[7]?.textContent || '');
|
||
|
||
// Пропускаем записи, если название события не указано или дата неверная
|
||
if (!event_name || !event_date || event_date === '1970-01-01') {
|
||
return;
|
||
}
|
||
|
||
events.push({
|
||
event_name,
|
||
event_date,
|
||
event_time,
|
||
location,
|
||
event_result,
|
||
event_basis,
|
||
note,
|
||
publication_date
|
||
});
|
||
});
|
||
|
||
return events;
|
||
}, divId);
|
||
|
||
// Возвращаем последнее событие (аналогично parscourt.php)
|
||
if (events.length > 0) {
|
||
const lastEvent = events[events.length - 1];
|
||
|
||
// Форматируем даты (аналогично parscourt.php)
|
||
const formatDate = (dateStr) => {
|
||
if (!dateStr) return '';
|
||
try {
|
||
const date = new Date(dateStr);
|
||
if (isNaN(date.getTime())) return dateStr;
|
||
return date.toISOString().split('T')[0]; // YYYY-MM-DD
|
||
} catch {
|
||
return dateStr;
|
||
}
|
||
};
|
||
|
||
return {
|
||
url,
|
||
source: new URL(url).hostname,
|
||
court_type: 'regional',
|
||
last_event: {
|
||
event_name: lastEvent.event_name,
|
||
event_date: formatDate(lastEvent.event_date),
|
||
event_time: lastEvent.event_time,
|
||
location: lastEvent.location,
|
||
event_result: lastEvent.event_result,
|
||
event_basis: lastEvent.event_basis,
|
||
note: lastEvent.note,
|
||
publication_date: formatDate(lastEvent.publication_date),
|
||
// Для совместимости с parscourt.php (кириллические ключи)
|
||
Наименование: lastEvent.event_name,
|
||
Дата: formatDate(lastEvent.event_date),
|
||
Время: lastEvent.event_time,
|
||
Место: lastEvent.location,
|
||
Результат: lastEvent.event_result,
|
||
Основание: lastEvent.event_basis,
|
||
Примечание: lastEvent.note,
|
||
'Дата размещения': formatDate(lastEvent.publication_date)
|
||
},
|
||
all_events: events
|
||
};
|
||
}
|
||
|
||
return {
|
||
url,
|
||
source: new URL(url).hostname,
|
||
court_type: 'regional',
|
||
last_event: null,
|
||
message: 'События не найдены'
|
||
};
|
||
}
|
||
|
||
// ========================================
|
||
// ПАРСИНГ МОСКОВСКИХ СУДОВ (mos-gorsud.ru)
|
||
// ========================================
|
||
if (isMoscowCourt) {
|
||
// Ждём карточку
|
||
await page.waitForSelector(
|
||
".detail-cart .row_card, .case-card, .case-details, .content, main .wrapper_innercontent",
|
||
{ timeout: 20000 }
|
||
);
|
||
|
||
// Активируем вкладки
|
||
try {
|
||
for (const id of ["#ui-id-1", "#ui-id-2", "#ui-id-3"]) {
|
||
if (await page.$(id)) await page.click(id);
|
||
}
|
||
const tabLinks = await page.$$(`a[href^="#tabs-"], .tabs_wrapper a.ui-tabs-anchor`);
|
||
if (tabLinks.length) for (const a of tabLinks) await a.click();
|
||
await page.waitForTimeout(300);
|
||
} catch (_) {}
|
||
|
||
const data = await page.evaluate(() => {
|
||
const norm = (el) => (el ? el.textContent.replace(/\s+/g, " ").trim() : "");
|
||
const qsa = (sel) => Array.from(document.querySelectorAll(sel));
|
||
|
||
// Таблицы
|
||
function tableToRows(tbody) {
|
||
return Array.from(tbody.querySelectorAll("tr")).map((tr) => {
|
||
const tds = tr.querySelectorAll("td");
|
||
return Array.from(tds).map((td) => norm(td.querySelector("div") || td));
|
||
});
|
||
}
|
||
|
||
const sessionsTbody = document.querySelector("#tabs-2 table tbody");
|
||
const hearingsRows = sessionsTbody ? tableToRows(sessionsTbody) : [];
|
||
const hearings = hearingsRows.map((cols) => ({
|
||
datetime: cols[0] || null,
|
||
hall: cols[1] || null,
|
||
stage: cols[2] || null,
|
||
result: cols[3] || null,
|
||
basis: cols[4] || null,
|
||
}));
|
||
|
||
const stTbody = document.querySelector("#tabs-1 #state-history table tbody");
|
||
const stateRows = stTbody ? tableToRows(stTbody) : [];
|
||
const states = stateRows.map((cols) => ({
|
||
date: cols[0] || null,
|
||
state: cols[1] || null,
|
||
basis_doc: cols[2] || null,
|
||
}));
|
||
|
||
return {
|
||
hearings,
|
||
history: { states },
|
||
};
|
||
});
|
||
|
||
// Извлекаем последнее событие (аналогично MoscowCourtParser)
|
||
let lastEvent = null;
|
||
|
||
// Проверяем заседания (hearings)
|
||
if (data.hearings && data.hearings.length > 0) {
|
||
const hearing = data.hearings[data.hearings.length - 1];
|
||
|
||
if (hearing.datetime) {
|
||
const datetime = hearing.datetime;
|
||
let event_date = '';
|
||
let event_time = '';
|
||
|
||
// Формат: "27.10.2025 09:30" или "27.10.2025"
|
||
const match1 = datetime.match(/(\d{2}\.\d{2}\.\d{4})\s+(\d{2}:\d{2})/);
|
||
if (match1) {
|
||
event_date = match1[1];
|
||
event_time = match1[2];
|
||
} else {
|
||
const match2 = datetime.match(/(\d{2}\.\d{2}\.\d{4})/);
|
||
if (match2) {
|
||
event_date = match2[1];
|
||
}
|
||
}
|
||
|
||
if (event_date) {
|
||
// Форматируем дату для БД (DD.MM.YYYY -> YYYY-MM-DD)
|
||
const formatDate = (dateStr) => {
|
||
const parts = dateStr.split('.');
|
||
if (parts.length === 3) {
|
||
return `${parts[2]}-${parts[1]}-${parts[0]}`;
|
||
}
|
||
return dateStr;
|
||
};
|
||
|
||
lastEvent = {
|
||
event_name: hearing.stage || 'Судебное заседание',
|
||
event_date: formatDate(event_date),
|
||
event_time: event_time,
|
||
location: hearing.hall || '',
|
||
event_result: hearing.result || '',
|
||
event_basis: hearing.basis || '',
|
||
note: '',
|
||
publication_date: formatDate(event_date),
|
||
// Для совместимости с parscourt.php
|
||
Наименование: hearing.stage || 'Судебное заседание',
|
||
Дата: event_date,
|
||
Время: event_time,
|
||
Место: hearing.hall || '',
|
||
Результат: hearing.result || '',
|
||
Основание: hearing.basis || '',
|
||
Примечание: '',
|
||
'Дата размещения': event_date
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
// Если заседаний нет, проверяем историю состояний
|
||
if (!lastEvent && data.history?.states && data.history.states.length > 0) {
|
||
const state = data.history.states[data.history.states.length - 1];
|
||
|
||
if (state.date && state.state) {
|
||
const formatDate = (dateStr) => {
|
||
const parts = dateStr.split('.');
|
||
if (parts.length === 3) {
|
||
return `${parts[2]}-${parts[1]}-${parts[0]}`;
|
||
}
|
||
return dateStr;
|
||
};
|
||
|
||
lastEvent = {
|
||
event_name: state.state,
|
||
event_date: formatDate(state.date),
|
||
event_time: '',
|
||
location: '',
|
||
event_result: '',
|
||
event_basis: state.basis_doc || '',
|
||
note: '',
|
||
publication_date: formatDate(state.date),
|
||
// Для совместимости с parscourt.php
|
||
Наименование: state.state,
|
||
Дата: state.date,
|
||
Время: '',
|
||
Место: '',
|
||
Результат: '',
|
||
Основание: state.basis_doc || '',
|
||
Примечание: '',
|
||
'Дата размещения': state.date
|
||
};
|
||
}
|
||
}
|
||
|
||
return {
|
||
url,
|
||
source: new URL(url).hostname,
|
||
court_type: 'moscow',
|
||
last_event: lastEvent,
|
||
all_hearings: data.hearings,
|
||
all_states: data.history?.states || []
|
||
};
|
||
}
|
||
|
||
// Если тип суда не определён
|
||
throw new Error(`Неизвестный тип суда для URL: ${url}`);
|
||
}
|
||
|