Цель простая: по списку доменов сразу получить таблицу. В ней видно, есть ли площадка в Miralinks, можно ли её купить, сколько стоит размещение, сколько стоит написание, какой у площадки Ground ID и какая ссылка на карточку. Плюс трафик и ещё несколько метрик.
Зачем это нужно. Например, вы уже выгрузили доноров по конкурентам и хотите быстро понять, какие из них вообще можно купить на Miralinks и сколько это будет стоить. Открывать каждую площадку руками не нужно.
1. Подготовить список доменов
- Один домен — одна строка.
- Можно вставлять site.ru, www.site.ru или https://site.ru.
- Одинаковые домены лучше убрать заранее.
- Список вставляется в блок INPUT в скрипте внизу этой страницы.
Пример списка: rbc.ru, vc.ru, interfax.ru, lenta.ru.
2. Открыть Miralinks
- Зайти в свой аккаунт Miralinks.
- Открыть «Каталог площадок».
- Если нужна проверка по всему каталогу, нажать «Очистить фильтр».
- Проверить, что каталог не отфильтрован.
Контрольный признак — числа совпадают. Например: «Найдено 19780 площадок из 19780». Если второе число меньше, часть каталога скрыта фильтром, и скрипт её не увидит.

3. Открыть консоль
- Нажать F12.
- Перейти на вкладку Console.
- Если Chrome не даёт вставить код, руками набрать allow pasting и нажать Enter.
Писать нужно именно allow pasting. Своими руками, не вставкой.

4. Подготовить скрипт
- Прокрутить эту страницу до блока «Скрипт V2».
- Найти const INPUT.
- Удалить example.com, example2.ru и example3.ru.
- Вставить свои домены, по одному в строке.
- Нажать «Скопировать скрипт» и забрать весь код целиком.
Блок со списком выглядит так: const INPUT, затем ваши домены, каждый с новой строки.
5. Запустить проверку
- Вернуться в консоль открытого каталога Miralinks.
- Вставить весь скрипт.
- Нажать Enter.
- Второй раз скрипт не запускать.
Сразу после старта консоль пишет три вещи: сколько доменов в сверке, как остановить проверку командой window.__ML_STOP__ = true, и что берутся текущие фильтры каталога.
Строка Promise {} — это нормально. Скрипт уже работает.
6. Как понять, что цены собираются
Пока скрипт идёт, в консоли появляются зелёные строки. В них домен, цена размещения, цена написания, статус и прогресс.
Пример: rbc.ru, размещение 270000 ₽, написание 2105 ₽, статус available, 1 из 5898.
Это главный признак. Если такие строки есть, скрипт уже забирает обе цены.

7. Пока скрипт работает
- Не обновлять страницу.
- Не закрывать вкладку Miralinks.
- Не закрывать DevTools.
- Не запускать скрипт второй раз.
- На другие вкладки браузера переключаться можно.
Если нужно остановить проверку раньше, в консоли выполнить window.__ML_STOP__ = true.
8. Дождаться файла
В конце консоль пишет: V2 готово, найдено столько-то из стольких, CSV с ценами и параметрами скачан.
Файл сам появляется в загрузках. Имя такое: miralinks_check_v2_ и сегодняшняя дата, расширение csv.

9. Что смотреть в таблице
Откройте CSV в Excel или Google Таблицах. На скриншоте обведены две главные цены: размещение и написание.

- domain — какой домен проверяли.
- status — нашёлся домен среди видимых адресов каталога или нет.
- available_to_buy — можно ли купить площадку прямо сейчас.
- current_status — статус площадки в Miralinks.
- ground_id — ID площадки.
- card_url — ссылка на карточку.
- placement_price_rub — цена размещения.
- writing_price_rub — цена написания.
- total_with_writing_rub — размещение плюс написание.
- traffic — трафик площадки.
- articles_count — сколько статей.
- placement_time_text — как быстро размещают.
- last_placement_text — когда было последнее размещение.
- cf, tf, ahrefs_dr — SEO-метрики.
- yandex_indexed_pages — сколько страниц в индексе Яндекса.
10. Как читать строки
status «НАЙДЕН В MIRALINKS» — площадка сопоставлена по домену.
available_to_buy «ДА» — статус площадки available, её можно купить на момент проверки.
placement_price_rub — сколько стоит разместить статью.
writing_price_rub — сколько стоит написание статьи площадкой.
total_with_writing_rub — обе суммы вместе.
card_url собирается по Ground.id и ведёт на карточку в формате /grounds/view/.
Домен → есть в Miralinks → можно купить → цена размещения → цена написания → общая цена → трафик → статей → Ground ID → ссылка на карточку.
11. Проверить руками перед отбором
- Оставить только строки со status «НАЙДЕН В MIRALINKS».
- Проверить, что у найденных площадок заполнены цены.
- Оставить available_to_buy «ДА».
- Открыть несколько card_url и убедиться, что это нужные карточки.
- После этого уже отбирать доноров по таблице.
Скрипт V2
Перед запуском замените примеры в INPUT на свой список. Кнопка копирует весь код, от первой скобки до последней.
(async () => {
/*
MIRALINKS BULK CHECKER v2
Проверяет список доменов и выгружает:
- наличие в каталоге
- Ground.id
- ссылку на карточку по Ground.id → /grounds/view/<ID>
- цену размещения
- цену написания
- статус доступности
- трафик
- количество статей
- скорость/последнее размещение
- CF / TF / Ahrefs DR
- число страниц в индексе Яндекса
- category_ids
Запускать в авторизованном Miralinks на странице "Каталог площадок".
*/
const INPUT = `
example.com
example2.ru
example3.ru
`;
const REQUESTED_DOMAINS = INPUT
.split(/\r?\n|[,;\t ]+/)
.map(x => x.trim())
.filter(Boolean);
const DISPLAY_LENGTH = 50;
const PAGE_DELAY_MS = 2200;
const MAX_RETRIES = 3;
window.__ML_STOP__ = false;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
function normalizeHost(value) {
let s = String(value ?? '').trim().toLowerCase();
if (!s) return '';
s = s.replace(/\\\./g, '.');
if (!/^https?:\/\//i.test(s)) {
s = 'http://' + s;
}
try {
return new URL(s).hostname
.toLowerCase()
.replace(/^www\./, '')
.replace(/\.$/, '');
} catch {
return String(value ?? '')
.trim()
.toLowerCase()
.replace(/\\\./g, '.')
.replace(/^https?:\/\//i, '')
.replace(/^www\./i, '')
.split('/')[0]
.split('?')[0]
.split('#')[0]
.replace(/\.$/, '');
}
}
const requested = [...new Set(
REQUESTED_DOMAINS.map(normalizeHost).filter(Boolean)
)];
const wanted = new Set(requested);
const found = new Map();
let requestsMade = 0;
let catalogRowsSeen = 0;
let visibleUrlsSeen = 0;
function getSearchData() {
try {
return ML.Storages.getUsersRemoteStorage().__STORE__.lastCatalogFilter || {};
} catch {
throw new Error(
'Не удалось прочитать фильтр Miralinks. Запусти скрипт прямо в "Каталоге площадок".'
);
}
}
function addSortingFields(form) {
try {
for (const key in (window.dataTables || {})) {
if (!key.startsWith('Catalog_')) continue;
const settings = window.dataTables[key]?.fnSettings?.();
const sorting = settings?.aaSorting || [];
form.append('iSortingCols', sorting.length || 1);
sorting.forEach((item, i) => {
form.append(`bSortable_${i}`, 'true');
form.append(`iSortCol_${i}`, item[0]);
form.append(`sSortDir_${i}`, item[1]);
});
return;
}
} catch (e) {
console.warn('Не удалось прочитать сортировку таблицы. Продолжаю.', e);
}
form.append('iSortingCols', '0');
}
function generateBody(params = {}) {
const form = new FormData();
Object.entries(params).forEach(([key, value]) => {
form.append(key, String(value));
});
form.append('tsDataTableType', 'with_checking');
form.append('tsDataTableConfigType', 'dataTable.Catalog');
form.append('iDisplayLength', String(DISPLAY_LENGTH));
form.append('searchData', JSON.stringify(getSearchData()));
addSortingFields(form);
return form;
}
async function fetchCatalogPage(params) {
let lastError;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
if (window.__ML_STOP__) {
throw new Error('Проверка остановлена вручную.');
}
requestsMade++;
const response = await fetch(
`${location.origin}/ajaxPort/loadDataTableDataCatalog`,
{
method: 'POST',
credentials: 'include',
headers: {
'x-requested-with': 'XMLHttpRequest'
},
body: generateBody(params)
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
if (!Array.isArray(data?.aaData)) {
throw new Error('Miralinks вернул неожиданный ответ.');
}
return data;
} catch (e) {
lastError = e;
console.warn(`Ошибка запроса, попытка ${attempt}/${MAX_RETRIES}:`, e.message);
if (attempt < MAX_RETRIES) {
await sleep(PAGE_DELAY_MS * attempt);
}
}
}
throw lastError;
}
function collectCategoryIds(rowData) {
return Object.entries(rowData || {})
.filter(([key]) => /^category_ids(?:\.|$)/i.test(key))
.map(([, value]) => value)
.filter(v => v !== null && v !== undefined && String(v) !== '')
.join(',');
}
function findCardUrl(rowData) {
const groundId = rowData?.['Ground.id'];
if (!groundId) {
return { url: '', source: '' };
}
return {
url: `${location.origin}/grounds/view/${groundId}`,
source: 'Ground.id → /grounds/view/'
};
}
function asValue(rowData, key, fallback = '') {
const v = rowData?.[key];
return v === null || v === undefined ? fallback : v;
}
function inspectRow(row) {
const d = row?.rowData || {};
const rawUrl = d['Ground.folder_url_wl'];
if (!rawUrl) return;
visibleUrlsSeen++;
const host = normalizeHost(rawUrl);
if (!host || !wanted.has(host) || found.has(host)) return;
const card = findCardUrl(d);
const currentStatus = asValue(d, 'Ground.current_status');
const placementPrice = asValue(d, 'Ground.price_rur');
const writingPrice = asValue(d, 'Ground.article_price_rur');
found.set(host, {
domain: host,
status: 'НАЙДЕН В MIRALINKS',
available_to_buy: currentStatus === 'available' ? 'ДА' : (currentStatus ? 'НЕТ/ПРОВЕРИТЬ' : ''),
current_status: currentStatus,
ground_id: asValue(d, 'Ground.id'),
card_url: card.url,
card_url_source: card.source,
matched_url: String(rawUrl),
placement_price_rub: placementPrice,
writing_price_rub: writingPrice,
total_with_writing_rub:
Number.isFinite(Number(placementPrice)) && Number.isFinite(Number(writingPrice))
? Number(placementPrice) + Number(writingPrice)
: '',
traffic: asValue(d, 'Ground.traffic'),
traffic_interval: asValue(d, 'traffic.interval'),
traffic_from: asValue(d, 'traffic.from'),
traffic_to: asValue(d, 'traffic.to'),
articles_count: asValue(d, 'Ground.articles_count', asValue(d, 'articles_count')),
placement_time: asValue(d, 'Ground.placement_time'),
placement_time_text: asValue(d, 'placement_time_str'),
last_placement: asValue(d, 'Ground.last_placement'),
last_placement_text: asValue(d, 'last_placement_str'),
cf: asValue(d, 'Ground.cf'),
tf: asValue(d, 'Ground.tf'),
ahrefs_dr: asValue(d, 'Ground.ahrefs_dr'),
yandex_indexed_pages: asValue(d, 'Ground.ya_indexed_count'),
region_id: asValue(d, 'Ground.region_id'),
category_ids: collectCategoryIds(d),
hide_folder_url: asValue(d, 'Ground.hide_folder_url')
});
console.log(
`✅ ${host} | размещение ${placementPrice} ₽ | написание ${writingPrice} ₽ | ` +
`статус ${currentStatus || '—'} | ${found.size}/${requested.length}`
);
}
async function loadScope(initialParams = {}) {
let start = Number(initialParams.iDisplayStart || 0);
const groupValue = initialParams.sGroupValue;
while (true) {
if (window.__ML_STOP__) {
throw new Error('Проверка остановлена вручную.');
}
const params = { iDisplayStart: start };
if (groupValue) params.sGroupValue = groupValue;
const data = await fetchCatalogPage(params);
const rows = data.aaData;
if (!rows.length) return;
for (const row of rows) {
const d = row?.rowData || {};
catalogRowsSeen++;
if (Number(d['@count'] || 0) >= 2 && d['@groupby']) {
await sleep(PAGE_DELAY_MS);
await loadScope({
sGroupValue: d['@groupby'],
iDisplayStart: 0
});
continue;
}
inspectRow(row);
}
console.log(
`Miralinks v2: запросов ${requestsMade}; строк ${catalogRowsSeen}; ` +
`видимых URL ${visibleUrlsSeen}; найдено ${found.size}/${requested.length}`
);
if (found.size === requested.length) return;
if (rows.length < DISPLAY_LENGTH) return;
start += DISPLAY_LENGTH;
await sleep(PAGE_DELAY_MS);
}
}
function csvEscape(value) {
return `"${String(value ?? '').replace(/"/g, '""')}"`;
}
function downloadResults() {
const columns = [
'domain',
'status',
'available_to_buy',
'current_status',
'ground_id',
'card_url',
'card_url_source',
'matched_url',
'placement_price_rub',
'writing_price_rub',
'total_with_writing_rub',
'traffic',
'traffic_interval',
'traffic_from',
'traffic_to',
'articles_count',
'placement_time',
'placement_time_text',
'last_placement',
'last_placement_text',
'cf',
'tf',
'ahrefs_dr',
'yandex_indexed_pages',
'region_id',
'category_ids',
'hide_folder_url'
];
const rows = requested.map(domain => {
if (found.has(domain)) return found.get(domain);
return {
domain,
status: 'НЕ НАЙДЕН СРЕДИ ВИДИМЫХ URL',
available_to_buy: '',
current_status: '',
ground_id: '',
card_url: '',
card_url_source: '',
matched_url: '',
placement_price_rub: '',
writing_price_rub: '',
total_with_writing_rub: '',
traffic: '',
traffic_interval: '',
traffic_from: '',
traffic_to: '',
articles_count: '',
placement_time: '',
placement_time_text: '',
last_placement: '',
last_placement_text: '',
cf: '',
tf: '',
ahrefs_dr: '',
yandex_indexed_pages: '',
region_id: '',
category_ids: '',
hide_folder_url: ''
};
});
const csv = [
columns.map(csvEscape).join(';'),
...rows.map(obj =>
columns.map(col => csvEscape(obj[col])).join(';')
)
].join('\r\n');
const blob = new Blob(
['\ufeff' + csv],
{ type: 'text/csv;charset=utf-8' }
);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `miralinks_check_v2_${new Date().toISOString().slice(0, 10)}.csv`;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
if (!location.hostname.includes('miralinks')) {
throw new Error('Открой Miralinks → Каталог площадок и запусти скрипт там.');
}
console.log(`🚀 Miralinks v2. Доменов для сверки: ${requested.length}`);
console.log('Для остановки: window.__ML_STOP__ = true');
console.log('Используются ТЕКУЩИЕ фильтры каталога.');
try {
await loadScope({ iDisplayStart: 0 });
downloadResults();
console.log(
`✅ V2 готово. Найдено ${found.size} из ${requested.length}. ` +
`CSV с ценами и параметрами автоматически скачан.`
);
} catch (e) {
console.error('❌ V2 остановлен:', e);
console.log('Уже собрано:', found.size, 'из', requested.length);
throw e;
}
})();
Семантика · Структура · Коммерция