initial commit

This commit is contained in:
Herzic 2026-03-16 11:28:40 +03:00
commit 8e7903dc34
3 changed files with 227 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
.env
.env.*
!.env.example
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
.output
# Vite files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/
# Dependency directories
node_modules/

View File

@ -0,0 +1,200 @@
(async function() {
const csrfToken = "79684b90849bec4c80846f355dd1671b48338cc739007d81"; // ← ОБНОВИ!
const url = "https://auto.ru/krasnodar/dilery/cars/new/?place_lat_from=46.65304&place_lon_from=38.80775&place_lat_to=48.07858&place_lon_to=40.5024&geo_radius=300"; // Вставьте URL здесь
const params = new URLSearchParams(url.split('?')[1]);
const latMin = parseFloat(params.get('place_lat_from'));
const latMax = parseFloat(params.get('place_lat_to'));
const lonMin = parseFloat(params.get('place_lon_from'));
const lonMax = parseFloat(params.get('place_lon_to'));
const numLatSplits = 2;
const numLonSplits = 2;
const maxDepth = 10; // Максимальная глубина рекурсии (чтобы не бесконечно разбивать)
let allDealers = [];
const seenCodes = new Set();
// Функция для задержки (чтобы не спамить API)
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Рекурсивная функция для обработки области
async function processArea(latFrom, latTo, lonFrom, lonTo, depth = 0) {
if (depth > maxDepth) {
console.warn(`🚨 Максимальная глубина (${maxDepth}) достигнута для lat ${latFrom}-${latTo}, lon ${lonFrom}-${lonTo}. Пропускаем.`);
return;
}
const body = {
"section": "new",
"category": "cars",
"place_lat_from": latFrom.toFixed(6),
"place_lat_to": latTo.toFixed(6),
"place_lon_from": lonFrom.toFixed(6),
"place_lon_to": lonTo.toFixed(6),
"__dont_change_map": 1,
"geo_radius": 300,
"geo_id": [35],
"page": 1,
"page_size": 100
};
const areaDesc = `lat ${body.place_lat_from}-${body.place_lat_to}, lon ${body.place_lon_from}-${body.place_lon_to}`;
console.log(`\n${depth > 0 ? 'Рекурсия lvl ' + depth + ': ' : ''}Запрос для ${areaDesc}`);
try {
await delay(50); // Пауза 0.5 сек между запросами
const res = await fetch("https://auto.ru/-/ajax/desktop-search/dealersListing/", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-Csrf-Token": csrfToken,
"Accept": "application/json",
"Referer": "https://auto.ru/"
},
body: JSON.stringify(body)
});
if (!res.ok) {
console.warn(`Ошибка ${res.status} для ${areaDesc}`);
return;
}
const data = await res.json();
if (data.totalResultsCount && data.totalResultsCount > 100) {
console.log(`⚠️ Total = ${data.totalResultsCount} > 100. Разбиваем на 4 под-области (глубина ${depth + 1}).`);
// Разбиваем на 2x2
const latMid = (latFrom + latTo) / 2;
const lonMid = (lonFrom + lonTo) / 2;
await processArea(latFrom, latMid, lonFrom, lonMid, depth + 1);
await processArea(latFrom, latMid, lonMid, lonTo, depth + 1);
await processArea(latMid, latTo, lonFrom, lonMid, depth + 1);
await processArea(latMid, latTo, lonMid, lonTo, depth + 1);
return; // Не обрабатываем текущую область дальше, т.к. разбили
}
if (!data.dealers) return;
let newCount = 0;
for (const d of data.dealers) {
const code = d.dealerCode || d.code || d.id || d.dealerName;
if (code && !seenCodes.has(code)) {
seenCodes.add(code);
allDealers.push(d);
newCount++;
}
}
console.log(` +${data.dealers.length} дилеров (новых: ${newCount}, всего: ${allDealers.length})`);
console.log(` Total в области: ${data.totalResultsCount || 'неизвестно'}`);
} catch (e) {
console.error(`Ошибка для ${areaDesc}:`, e);
}
}
// Генерация начальных областей
const latStep = (latMax - latMin) / numLatSplits;
const lonStep = (lonMax - lonMin) / numLonSplits;
const subAreas = [];
for (let i = 0; i < numLatSplits; i++) {
const latFrom = latMin + i * latStep;
const latTo = (i === numLatSplits - 1 ? latMax : latMin + (i + 1) * latStep);
for (let j = 0; j < numLonSplits; j++) {
const lonFrom = lonMin + j * lonStep;
const lonTo = (j === numLonSplits - 1 ? lonMax : lonMin + (j + 1) * lonStep);
subAreas.push({ latFrom, latTo, lonFrom, lonTo });
}
}
console.log(`Сгенерировано начальных областей: ${subAreas.length} (${numLatSplits}×${numLonSplits})`);
console.log(`Размер одного квадрата ≈ ${(latStep * 111).toFixed(1)} × ${((lonStep * 111) * Math.cos((latMin + latMax) / 2 * Math.PI / 180)).toFixed(1)} км`);
// Обработка всех начальных областей последовательно
for (let idx = 0; idx < subAreas.length; idx++) {
const { latFrom, latTo, lonFrom, lonTo } = subAreas[idx];
await processArea(latFrom, latTo, lonFrom, lonTo);
}
console.log(`\n✅ Готово! Собрано уникальных дилеров: ${allDealers.length}`);
// Вывод всех дилеров в консоль в табличном виде
console.table(allDealers);
const codes = allDealers.map(d => d.dealerCode || d.code || d.id || d.dealerName || 'unknown');
navigator.clipboard.writeText(codes.join('\n')).then(() => {
console.log("Коды успешно скопированы в буфер!");
}).catch(err => {
console.error("Ошибка копирования:", err);
});
// НОВАЯ ЧАСТЬ: Запрос телефонов для каждого dealerCode
console.log("\n📞 Запрашиваем телефоны для всех дилеров...");
let allPhones = {}; // { code: [phones] }
let counter = 0;
for (const d of allDealers) {
counter++;
console.log(counter)
// if (counter > 10) continue;
const code = d.dealerCode; // Используем dealerCode, если есть
if (!code) {
console.warn(`Пропуск: Нет dealerCode для ${d.dealerName || d.code || d.id}`);
continue;
}
console.log(`Запрос телефонов для ${code}...`);
try {
await delay(100); // Пауза между запросами
const r = await fetch("https://auto.ru/-/ajax/desktop-search/getSalonPhones/", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-Csrf-Token": csrfToken,
"Accept": "application/json",
"Referer": "https://auto.ru/"
},
body: JSON.stringify({
code: code,
category: "cars",
section: "all",
geo_radius: 300,
geo_id: [35]
})
});
console.log(`Статус для ${code}: ${r.status}`);
if (!r.ok) {
console.warn(`Ошибка ${r.status} для ${code}`);
continue;
}
const data = await r.json();
console.log(`Полный ответ для ${code}:`, data);
const phones = data || [];
phones.map((v) => {
// phones: [{phone: '', ...}]
if (v.phone) {
v.phone = v.phone.replace(/\D/g, ''); // Оставляем только цифры
}
// add to allPhones
if (!allPhones[code]) {
allPhones[code] = [];
}
if (v.phone && !allPhones[code].includes(v.phone)) {
allPhones[code].push(v.phone);
}
})
console.log(`Телефоны для ${code}:`, phones.length ? phones : "нет телефонов");
} catch (e) {
console.error(`Ошибка для ${code}:`, e);
}
}
// Вывод всех телефонов в таблицу
console.log("\n📋 Все телефоны:");
console.table(Object.entries(allPhones).map(([code, phones]) => ({
code,
phones: phones.join(', ') || 'нет'
})));
// Скопировать телефоны в буфер (code: phones)
const phonesText = Object.entries(allPhones).map(([code, phones]) => `${code}: ${phones.join(', ') || 'нет'}`).join('\n');
navigator.clipboard.writeText(phonesText).then(() => {
console.log("Телефоны (code: phones) скопированы в буфер!");
}).catch(err => {
console.error("Ошибка копирования телефонов:", err);
});
})()
Latitude - Longitude
45.430925 - 38.190931
Latitude - Longitude
44.372643 - 38.190931
Latitude - Longitude
44.372643 - 39.919681
Latitude - Longitude
45.430925 - 39.919681

7
readme.md Normal file
View File

@ -0,0 +1,7 @@
## Инструменты
...
### Парсинг
- [collect_dealers_autoru](./parse/collect_dealers_autoru.js)
- Собирает номера автоматически через карту