feat: update script add copy on excel format
This commit is contained in:
parent
acee37e1af
commit
5a3a5b3f0b
@ -1,229 +1,158 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Yandex Promo Links Collector (Multi-Query Console Version)
|
// @name Yandex Promo + Direct Links Collector
|
||||||
// @namespace yandex.console.collector.promo.multi
|
// @namespace yandex.console.collector.promo.multi
|
||||||
// @version 1.2
|
// @version 2.0
|
||||||
// @description Собирает промо ссылки из выдачи Яндекса для нескольких запросов — версия для Tampermonkey (единый вывод)
|
// @description Собирает промо-ссылки и прямые ссылки на сайты рекламодателей из выдачи Яндекса
|
||||||
// @match https://yandex.ru/search/*
|
// @match https://yandex.ru/search/*
|
||||||
// @match https://www.yandex.ru/search/*
|
// @match https://www.yandex.ru/search/*
|
||||||
// @match https://yandex.com/search/*
|
// @match https://yandex.com/search/*
|
||||||
// @grant GM_xmlhttpRequest
|
// @grant GM_xmlhttpRequest
|
||||||
|
// @grant GM_setClipboard
|
||||||
// @connect yandex.ru
|
// @connect yandex.ru
|
||||||
// @connect yandex.com
|
// @connect yandex.com
|
||||||
|
// @connect *
|
||||||
// @run-at document-idle
|
// @run-at document-idle
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
const QUERIES = [
|
|
||||||
|
|
||||||
'купить Belgee в Нижнем Новгороде',
|
const BRANDS = ['Belgee', 'Haval', 'Chery', 'Geely', 'Changan', 'Exeed', 'Omoda',
|
||||||
'купить бу Belgee в Нижнем Новгороде',
|
'Jaecoo', 'Tank', 'Jetour', 'Forthing', 'BAIC', 'Dongfeng', 'Hongqi'];
|
||||||
'купить в Нижнем Новгороде бу Belgee ',
|
const REGIONS = ['Спб', 'Ленинградской области', 'Нижнем Новгороде'];
|
||||||
'купить в Нижнем Новгороде Belgee ',
|
const QUERIES = [];
|
||||||
|
for (const r of REGIONS) {
|
||||||
'купить Haval в Нижнем Новгороде',
|
for (const b of BRANDS) {
|
||||||
'купить бу Haval в Нижнем Новгороде',
|
QUERIES.push(
|
||||||
'купить в Нижнем Новгороде бу Haval ',
|
`купить ${b} в ${r}`,
|
||||||
'купить в Нижнем Новгороде Haval ',
|
`купить бу ${b} в ${r}`,
|
||||||
|
`купить в ${r} бу ${b}`,
|
||||||
'купить Chery в Нижнем Новгороде',
|
`купить в ${r} ${b}`,
|
||||||
'купить бу Chery в Нижнем Новгороде',
|
);
|
||||||
'купить в Нижнем Новгороде бу Chery ',
|
}
|
||||||
'купить в Нижнем Новгороде Chery ',
|
}
|
||||||
|
|
||||||
'купить Geely в Нижнем Новгороде',
|
|
||||||
'купить бу Geely в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Geely ',
|
|
||||||
'купить в Нижнем Новгороде Geely ',
|
|
||||||
|
|
||||||
'купить Changan в Нижнем Новгороде',
|
|
||||||
'купить бу Changan в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Changan ',
|
|
||||||
'купить в Нижнем Новгороде Changan ',
|
|
||||||
|
|
||||||
'купить Exeed в Нижнем Новгороде',
|
|
||||||
'купить бу Exeed в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Exeed ',
|
|
||||||
'купить в Нижнем Новгороде Exeed ',
|
|
||||||
|
|
||||||
'купить Omoda в Нижнем Новгороде',
|
|
||||||
'купить бу Omoda в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Omoda ',
|
|
||||||
'купить в Нижнем Новгороде Omoda ',
|
|
||||||
|
|
||||||
'купить Jaecoo в Нижнем Новгороде',
|
|
||||||
'купить бу Jaecoo в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Jaecoo ',
|
|
||||||
'купить в Нижнем Новгороде Jaecoo ',
|
|
||||||
|
|
||||||
'купить Tank в Нижнем Новгороде',
|
|
||||||
'купить бу Tank в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Tank ',
|
|
||||||
'купить в Нижнем Новгороде Tank ',
|
|
||||||
|
|
||||||
'купить Jetour в Нижнем Новгороде',
|
|
||||||
'купить бу Jetour в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Jetour ',
|
|
||||||
'купить в Нижнем Новгороде Jetour ',
|
|
||||||
|
|
||||||
'купить Forthing в Нижнем Новгороде',
|
|
||||||
'купить бу Forthing в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Forthing ',
|
|
||||||
'купить в Нижнем Новгороде Forthing ',
|
|
||||||
|
|
||||||
'купить BAIC в Нижнем Новгороде',
|
|
||||||
'купить бу BAIC в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу BAIC ',
|
|
||||||
'купить в Нижнем Новгороде BAIC ',
|
|
||||||
|
|
||||||
'купить Dongfeng в Нижнем Новгороде',
|
|
||||||
'купить бу Dongfeng в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Dongfeng ',
|
|
||||||
'купить в Нижнем Новгороде Dongfeng ',
|
|
||||||
|
|
||||||
'купить Hongqi в Нижнем Новгороде',
|
|
||||||
'купить бу Hongqi в Нижнем Новгороде',
|
|
||||||
'купить в Нижнем Новгороде бу Hongqi ',
|
|
||||||
'купить в Нижнем Новгороде Hongqi ',
|
|
||||||
];
|
|
||||||
const MAX_PAGES_PER_QUERY = 3;
|
const MAX_PAGES_PER_QUERY = 3;
|
||||||
const PAGE_DELAY_MS = 500;
|
const PAGE_DELAY_MS = 800;
|
||||||
const QUERY_DELAY_MS = 1000;
|
const QUERY_DELAY_MS = 1500;
|
||||||
|
const RESOLVE_REDIRECTS = false; // true = переходить по yabs (это клик по рекламе!)
|
||||||
|
|
||||||
var allLinks = [];
|
const baseUrl = location.origin;
|
||||||
var uniqueLinks = new Set();
|
const rows = [];
|
||||||
var baseUrl = window.location.origin;
|
const seen = new Set();
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||||
|
|
||||||
function cleanUrl(url) {
|
function cleanUrl(url) {
|
||||||
try {
|
try {
|
||||||
var urlObj = new URL(url);
|
const u = new URL(url, baseUrl);
|
||||||
urlObj.searchParams.delete('primary_reqid');
|
['primary_reqid', 'rnd', 'reqid'].forEach(p => u.searchParams.delete(p));
|
||||||
urlObj.searchParams.delete('rnd');
|
return u.toString();
|
||||||
urlObj.searchParams.delete('reqid');
|
} catch { return url; }
|
||||||
return urlObj.toString();
|
|
||||||
} catch (e) {
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
function processPage(doc, queryLinks) {
|
function gmFetch(url, extra = {}) {
|
||||||
var nodes = doc.querySelectorAll('li.serp-item a[href]');
|
|
||||||
var pageLinks = [];
|
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
|
||||||
var href = nodes[i].getAttribute('href');
|
|
||||||
if (href && href.startsWith('https://yabs.yandex.ru')) {
|
|
||||||
var fullUrl = href.startsWith('http') ? href : baseUrl + href;
|
|
||||||
var cleanedUrl = cleanUrl(fullUrl);
|
|
||||||
if (!uniqueLinks.has(cleanedUrl)) {
|
|
||||||
uniqueLinks.add(cleanedUrl);
|
|
||||||
pageLinks.push(cleanedUrl);
|
|
||||||
queryLinks.push(cleanedUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pageLinks.length;
|
|
||||||
}
|
|
||||||
function getNextPageUrl(doc) {
|
|
||||||
var nextBtn = doc.querySelector('a.Pager-Item_type_next');
|
|
||||||
if (!nextBtn) {
|
|
||||||
nextBtn = doc.querySelector('a[aria-label="Следующая страница"]');
|
|
||||||
}
|
|
||||||
if (nextBtn) {
|
|
||||||
var href = nextBtn.getAttribute('href');
|
|
||||||
if (href) {
|
|
||||||
return href.startsWith('http') ? href : baseUrl + href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
async function processQuery(query) {
|
|
||||||
var queryLinks = [];
|
|
||||||
var pagesProcessed = 0;
|
|
||||||
var searchUrl = `${baseUrl}/search/?text=${encodeURIComponent(query)}`;
|
|
||||||
try {
|
|
||||||
var html = await gmFetch(searchUrl);
|
|
||||||
var parser = new DOMParser();
|
|
||||||
var doc = parser.parseFromString(html, 'text/html');
|
|
||||||
processPage(doc, queryLinks);
|
|
||||||
pagesProcessed++;
|
|
||||||
var nextUrl = getNextPageUrl(doc);
|
|
||||||
while (nextUrl && pagesProcessed < MAX_PAGES_PER_QUERY) {
|
|
||||||
await new Promise(r => setTimeout(r, PAGE_DELAY_MS));
|
|
||||||
html = await gmFetch(nextUrl);
|
|
||||||
doc = parser.parseFromString(html, 'text/html');
|
|
||||||
processPage(doc, queryLinks);
|
|
||||||
pagesProcessed++;
|
|
||||||
nextUrl = getNextPageUrl(doc);
|
|
||||||
}
|
|
||||||
console.log(`[Запрос: "${query}"] Обработано страниц: ${pagesProcessed}, промо ссылок: ${queryLinks.length}`);
|
|
||||||
allLinks.push(queryLinks.join('\n'));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ Ошибка для запроса "${query}":`, error);
|
|
||||||
}
|
|
||||||
return queryLinks.length;
|
|
||||||
}
|
|
||||||
function gmFetch(url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
GM_xmlhttpRequest({
|
GM_xmlhttpRequest({
|
||||||
method: 'GET',
|
method: 'GET', url, timeout: 15000,
|
||||||
url: url,
|
headers: { 'User-Agent': navigator.userAgent, 'Referer': baseUrl },
|
||||||
headers: {
|
...extra,
|
||||||
'User-Agent': navigator.userAgent,
|
onload: r => (r.status >= 200 && r.status < 400) ? resolve(r) : reject(new Error('HTTP ' + r.status)),
|
||||||
'Referer': baseUrl
|
onerror: reject,
|
||||||
},
|
ontimeout: () => reject(new Error('Timeout')),
|
||||||
onload: (response) => {
|
|
||||||
if (response.status >= 200 && response.status < 300) {
|
|
||||||
resolve(response.responseText);
|
|
||||||
} else {
|
|
||||||
reject(new Error(`HTTP ${response.status}`));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onerror: (err) => reject(err),
|
|
||||||
ontimeout: (err) => reject(new Error('Timeout'))
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function copyToClipboard(text) {
|
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
// Рекламный ли блок: старое слово «Промо/Реклама», новый значок-стрелка или yabs-ссылка
|
||||||
return navigator.clipboard.writeText(text);
|
function isPromo(item) {
|
||||||
|
if (item.querySelector('.AdvLabel, .OrganicAdvLabel, .AdvLabel-Icon, [aria-label="Промо"], [aria-label="Реклама"]')) return true;
|
||||||
|
const t = item.querySelector('.AdvLabel-Text, .Label');
|
||||||
|
return !!(t && /^(Промо|Реклама)$/i.test(t.textContent.trim()));
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
|
// Прямой адрес сайта из видимой строки адреса под заголовком
|
||||||
|
function extractDirect(item) {
|
||||||
|
const pathItem = item.querySelector('.Organic-Path .Path-Item, .Path .Path-Item');
|
||||||
|
const pathText = (item.querySelector('.Organic-Path, .Path') || pathItem || {}).textContent || '';
|
||||||
|
const display = pathText.replace(/\s+/g, ' ').trim();
|
||||||
|
// домен — первая часть строки адреса до « › »
|
||||||
|
const host = ((pathItem ? pathItem.textContent : display).split('›')[0] || '').trim().toLowerCase();
|
||||||
|
if (/^([a-zа-яё0-9-]+\.)+[a-zа-яё]{2,}$/i.test(host)) return { direct: 'https://' + host, display };
|
||||||
|
// запасной вариант: прямая не-яндексовая ссылка в блоке
|
||||||
|
for (const a of item.querySelectorAll('a[href^="http"]')) {
|
||||||
|
const h = a.getAttribute('href');
|
||||||
|
if (!/yandex\.|yabs\.|ya\.ru/.test(h)) return { direct: h, display };
|
||||||
|
}
|
||||||
|
return { direct: '', display };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDomain(url) {
|
||||||
|
try { return new URL(url).hostname.replace(/^www\./, ''); } catch { return ''; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveYabs(url) {
|
||||||
|
try { const r = await gmFetch(url); return r.finalUrl || ''; } catch { return ''; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processPage(doc, query, page) {
|
||||||
|
const items = doc.querySelectorAll('li.serp-item');
|
||||||
|
let pos = 0, found = 0;
|
||||||
|
for (const item of items) {
|
||||||
|
pos++;
|
||||||
|
if (!isPromo(item)) continue;
|
||||||
|
const promoA = item.querySelector('a[href*="yabs.yandex."]');
|
||||||
|
const promo = promoA ? cleanUrl(promoA.getAttribute('href')) : '';
|
||||||
|
let { direct, display } = extractDirect(item);
|
||||||
|
if (!direct && promo && RESOLVE_REDIRECTS) {
|
||||||
|
direct = await resolveYabs(promo);
|
||||||
|
await sleep(500);
|
||||||
|
}
|
||||||
|
const key = getDomain(direct) || promo;
|
||||||
|
if (!key || seen.has(query + '|' + key)) continue;
|
||||||
|
seen.add(query + '|' + key);
|
||||||
|
const title = (item.querySelector('h2, .OrganicTitle-LinkText') || {}).textContent || '';
|
||||||
|
rows.push([query, page, pos, getDomain(direct), title.trim(), display, direct, promo]);
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextPageUrl(doc) {
|
||||||
|
const a = doc.querySelector('a.Pager-Item_type_next, a[aria-label="Следующая страница"]');
|
||||||
|
const h = a && a.getAttribute('href');
|
||||||
|
return h ? new URL(h, baseUrl).toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processQuery(query) {
|
||||||
|
let url = `${baseUrl}/search/?text=${encodeURIComponent(query)}`;
|
||||||
|
let page = 0, total = 0;
|
||||||
|
const parser = new DOMParser();
|
||||||
try {
|
try {
|
||||||
var textarea = document.createElement('textarea');
|
while (url && page < MAX_PAGES_PER_QUERY) {
|
||||||
textarea.value = text;
|
if (page) await sleep(PAGE_DELAY_MS);
|
||||||
textarea.style.position = 'fixed';
|
const html = (await gmFetch(url)).responseText;
|
||||||
textarea.style.left = '-9999px';
|
if (/showcaptcha|checkcaptcha/i.test(html)) { console.warn('⚠️ Капча на запросе:', query); break; }
|
||||||
document.body.appendChild(textarea);
|
const doc = parser.parseFromString(html, 'text/html');
|
||||||
textarea.focus();
|
page++;
|
||||||
textarea.select();
|
total += await processPage(doc, query, page);
|
||||||
var success = document.execCommand('copy');
|
url = getNextPageUrl(doc);
|
||||||
document.body.removeChild(textarea);
|
|
||||||
success ? resolve() : reject();
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
}
|
||||||
});
|
console.log(`[${query}] страниц: ${page}, рекламных сайтов: ${total}`);
|
||||||
|
} catch (e) { console.error(`❌ ${query}:`, e); }
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`🚀 Запуск сбора промо ссылок для ${QUERIES.length} запросов (макс. страниц на запрос: ${MAX_PAGES_PER_QUERY})...`);
|
|
||||||
(async function run() {
|
(async function run() {
|
||||||
var totalLinks = 0;
|
console.log(`🚀 Запросов: ${QUERIES.length}, страниц на запрос: ${MAX_PAGES_PER_QUERY}`);
|
||||||
for (let i = 0; i < QUERIES.length; i++) {
|
for (let i = 0; i < QUERIES.length; i++) {
|
||||||
if (i > 0) await new Promise(r => setTimeout(r, QUERY_DELAY_MS));
|
if (i) await sleep(QUERY_DELAY_MS);
|
||||||
totalLinks += await processQuery(QUERIES[i]);
|
await processQuery(QUERIES[i]);
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n' + '='.repeat(50));
|
|
||||||
console.log(`✅ СБОР ЗАВЕРШЁН`);
|
|
||||||
console.log(`📄 Запросов обработано: ${QUERIES.length}`);
|
|
||||||
console.log(`🔗 Промо ссылок собрано всего: ${totalLinks}`);
|
|
||||||
console.log('='.repeat(50) + '\n');
|
|
||||||
if (totalLinks > 0) {
|
|
||||||
var outputText = allLinks.join('\n');
|
|
||||||
console.log(outputText);
|
|
||||||
await copyToClipboard(outputText);
|
|
||||||
console.log('\n✅ Все промо ссылки (с разделителями по запросам) скопированы в буфер обмена!');
|
|
||||||
console.log('💡 Нажмите Ctrl+V в любом месте для вставки');
|
|
||||||
} else {
|
|
||||||
console.log('⚠️ Промо ссылки не найдены');
|
|
||||||
}
|
}
|
||||||
|
const header = ['Запрос', 'Стр', 'Поз', 'Домен', 'Заголовок', 'Видимый адрес', 'Прямая ссылка', 'Промо-ссылка'];
|
||||||
|
const tsv = [header, ...rows].map(r => r.map(c => String(c).replace(/[\t\n]/g, ' ')).join('\t')).join('\n');
|
||||||
|
console.table(rows.map(r => ({ Запрос: r[0], Домен: r[3], Прямая: r[6] })));
|
||||||
|
if (rows.length) {
|
||||||
|
GM_setClipboard(tsv, 'text');
|
||||||
|
console.log(`✅ Найдено ${rows.length}. Таблица в буфере, вставьте в Excel/Google Таблицы (Ctrl+V)`);
|
||||||
|
} else console.log('⚠️ Рекламных блоков не найдено');
|
||||||
})();
|
})();
|
||||||
})();
|
})();
|
||||||
Loading…
Reference in New Issue
Block a user