Как максимально быстро кликнуть на элемент в браузере?

Ссылка скопирована
1 ответ

Привет, хабр. Есть потребность реализовать расширение-кликер для хрома. У меня есть АМО CRM в ней нам падают лиды, далее, кто первый успел взять лид нажатием кнопки "принять" на всплывающем окне, тот и будет с ним работать. У моей коллеги, написан, какой-то софт/расширение, которое кликает на эту кнопку ещё до появления её в визуальной части сайта. Написал такой скрипт:

// Функция для отслеживания появления и нажатия на элемент function observeAndClickElement() {     const targetSelector = '.f5-notifier-notification-action_btn';      // Функция для нажатия на элемент     function clickElement(element) {         if (element) {             // Вызов функции клика             element.click();             console.log("Кнопка в уведомлении была нажата");         }     }      // Функция для рекурсивной проверки наличия элемента     function checkForElement() {         const notificationButton = document.querySelector(targetSelector);         clickElement(notificationButton);          // Запуск следующей проверки         requestAnimationFrame(checkForElement);     }      // Запуск первой проверки     checkForElement(); }  // Запуск отслеживания и нажатия на элемент observeAndClickElement();

// Функция для отслеживания появления и нажатия на элемент function observeAndClickElement() { const targetSelector = '.f5-notifier-notification-action_btn'; // Функция для нажатия на элемент function clickElement(element) { if (element) { // Вызов функции клика element.click(); console.log("Кнопка в уведомлении была нажата"); } } // Функция для рекурсивной проверки наличия элемента function checkForElement() { const notificationButton = document.querySelector(targetSelector); clickElement(notificationButton); // Запуск следующей проверки requestAnimationFrame(checkForElement); } // Запуск первой проверки checkForElement(); } // Запуск отслеживания и нажатия на элемент observeAndClickElement();

С таким скриптом, коллега всё равно берёт все заявки, а значит каким-то образом раньше меня получает информацию, о полявлении кнопки. Дсотупа к какой-либо серверной части нет ни у меня, ни у неё.
Как можно оптимизировать/переписать код чтобы стать первым или хотябы забирать часть её заявок, если вдруг потолком будет скорость интернета и другие факторы на которые я не смогу повлиять?

Дополнительно:

Скорее всего, используется не кликер, а прямые запросы к API.

Ну а вообще это не дело, и лучше бы начальство озаботилось этой проблемой. Хотя если все лиды будут забираться одним человеком, оно и так может заподозрить неладное...

  • shurshur, И скорее всего это ответ, что коллега - крыса.
    Возможно вебхуки https://www.amocrm.ru/developers/content/crm_platf... , но надо знать руководство чуть больше, или доступ к программистам, которые настраивали.
  • shurshur, Жаловались начальству, сказал - я проверил, там всё нормально, а то что у коллеги по 100 лидов в месяц, когда у остальных 30, это подозрений не вызывает)) Она просто теперь работает меньше, так как пока она "онлайн" все лиды - её.
  • Dmitry Bay, Доступа к программистам нет, в распоряжении только браузер и аккаунт менеджера)
  • vadimeasy, это ваша информация про программистов и доступам. Примите это.

    Опять же, вы цепляетесь за кнопку - новые евенты. А в AMO скорее всего есть запрос, и может даже со стороны фронта, который запрашивает внешние события.

    А вам нужно цепляться за возможность запрашивать их чуть раньше

  • Посмотрите в сторону MutationObserver, он вызывает обработчик в микротаск-очереди, следовательно он будет приоритетнее, чем RAF.
  • Александр, Пробовал таким способом, но всё равно медленно. Вчера смотрел вкладку "Сеть" во время принятия уведомлений, увидел, что самое первое во время прихода новой заявки - это звук. Также мне показалось странным, но может быть я чего-то недопонимаю, запрос который отправляется от клиента к серверу всегда одинаковый, то есть я нажима на кнопку "ПРИНЯТЬ" и при любой заявке всегда идентичный запрос отправляется. Может быть я смогу отправлять этот запрос, сразу, как приходит звук.
  • Может она не "кликает на кнопку", а сразу выполняет событие, которое завязано на клик по кнопке?

    PS подписался. Интересно, чем закончится.

  • Денис Кузьмин, вчера копался во вкладке нетворк в браузере, суть в следующем, когда вебсокет получает ответ от сервера с заявкой, он также отправляет action.twig - файл с шаблоном для рендера плашки с кнопкой "ПРИНЯТЬ" и передает в нем идентификаторы заявки, как я понял, этот шаблон моментально рендерит плашку с данными, которые идут в запрос, который отправляется на сервер при нажатии на кнопку "ПРИНЯТЬ". Я реализовал такой скрипт, который при появлении нужного мне класса, сразу отправляет запрос с данными из ДИВа (в который передаются данные из action.twig), жду новых заявок чтобы понять, как поведёт себя скриптец)
    Если у вас есть идеи, как можно из этого action вытащить данные раньше, чем они отрисуются в DOM, буду благодарен)
  • Ответы:

    Для таких вещей советую использовать MutationObserver вместо рекурсивной проверки наличия элемента. MutationObserver позволяет отслеживать изменения в DOM-дереве и вызывать функцию обратного вызова при их возникновении. Это может существенно ускорить работу функции, так как она не будет постоянно проверять наличие элемента.

    Вот по вашему примеру будет примерно так

    function observeAndClickElement() {   const targetSelector = '.f5-notifier-notification-action_btn';   const observer = new MutationObserver((mutationsList) => {     for (let mutation of mutationsList) {       if (mutation.type === 'childList') {         const notificationButton = document.querySelector(targetSelector);         if (notificationButton) {           notificationButton.click();           console.log("Кнопка в уведомлении была нажата");         }       }     }   );   observer.observe(document.body,  childList: true, subtree: true ); observeAndClickElement();

    function observeAndClickElement() { const targetSelector = '.f5-notifier-notification-action_btn'; const observer = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { if (mutation.type === 'childList') { const notificationButton = document.querySelector(targetSelector); if (notificationButton) { notificationButton.click(); console.log("Кнопка в уведомлении была нажата"); } } } ); observer.observe(document.body, childList: true, subtree: true ); observeAndClickElement();

    В этой функции мы создаем новый экземпляр MutationObserver и передаем ему функцию обратного вызова. Затем мы запускаем отслеживание изменений в DOM-дереве, указав, что мы хотим отслеживать изменения в дочерних элементах body. Когда происходит изменение, мы проверяем наличие элемента и, если он есть, нажимаем на него.

    Такой подход должен работать гораздо быстрее

    vadimeasy,
    КРЫСА она) Это ответ.
    Представь, что разница в 70 лидов стоят условные 1К за лид.
    За откат в 30-50% она может и начальника обработать и группу разработчиков.

    Т.е. на ее аккаунт может заявка сразу уходить, если она проходит по параметрам (Жирная заявка). А вы пытаетесь найти способ вытащить несуществующие лиды из системы. В этом и проблема.

    Вы можете даже проверить, если ваш скрипт работает, и в среднем будет у всех 30 лидов, а у вас допустим 45, а у этой коллеги так же 100 - это будет означать как минимум то, что ваш скрипт работает. И дело тут не в скорости скрипта.

    Ответ 2
    Появление кнопки - это следствие.
    Ищите событие, которое его запускает, там либо соединение с сокетом, и вам надо будет внедриться в функцию сокета, либо в ajax проверку. Так вы сможете ускорить получение ответа.
    +
    Пинг до сервера проверяйте. 20-30мс разницы - уже громадная.

    • Мой скрипт, точно работает, потому что пока она оффлайн, я забираю все заявки.
      Спасибо, попробую поискать событие.

    Попробуй для начала в тупую в бесконечном цикле пытаться находить эту кнопку при помощи
    document.querySelector()
    document.querySelectorAll()
    и нажимать, если нашлась.

    • Пробовал так, браузер виснет через несколько минут :(
    • vadimeasy, Вообще странно, что оно так работает. Но могу предложить тогда сомнительное решение - Селениум. Там тоже можно реализовать проверку наличия кнопок. Настроить сохранение кэша, куки и т.д. Получится как обычный браузер, только запускается через скрипт Python/Другого языка. Но не факт конечно что сработает.
    • vadimeasy, скорее всего скрипт написан криво. Код предоставить можешь?
    • maksam07, Код в вопросе приложен, но это уже обновленный, а тот цикл, что ты предалагешь, не отличается от тех, что ребята ниже скинули в ответы.

    А это точно единственная кнопка с таким классом на всю страницу и других таких не бывает?
    Если да, то вот один из самых простых вариантов

    setInterval(() => {     document.querySelector('.f5-notifier-notification-action_btn')?.click() }, 10)

    setInterval(() => { document.querySelector('.f5-notifier-notification-action_btn')?.click() }, 10)

    Если всё же классов таких несколько, то надо либо во все такие кнопки тыкать, либо привязываться по более конкретному пути

    • Опередил на минуту меня ахаха
    • Такая кнопка одна, сегодня утром делал ровно также, как вы предлагаете, всё равно у колллеги быстрее работает :(
    • vadimeasy, так может коллега вообще с сокетами работает напрямую?
    • maksam07, Этого я не знаю, моя задача реализовать крипт максимально быстрым способом, с запросами я ещё не пробовал, сегодня буду пробовать такой способ. Может быть поможет.

    Может попробовать без проверки?
    Сразу напрямую кликать, элемент появится он кликнет

    типо такого

    setInterval(() => {     document.querySelector('knopka')?.click() }, 100)

    setInterval(() => { document.querySelector('knopka')?.click() }, 100)

    Пока браузер не обновишь оно и будет кликать, сделать отдельно маленькое окно, пусть себе висит
    Кажется пока ты пытаешься проверить, она в тупую бьет по кнопке

    • И ниже пример про посмотреть что в вкладке network и самому напрямую из консоли браузера долбить, все токены/куки у тебя есть, должно сработать

    Необходимо реверсинженерингом выявить способ, которым события доходят до клиента.

    Для начала открой консоль разработчика (f12) и зайди на вкладку networks
    - если там будут периодические события (например с секундным интервалом) по названию (в url или еще как) похожие на получение статуса, то это значит используется медленный метод http get (это значит клиент получает информацию о событии со случайной задержкой порядка этой секунды), значит дальше можно искать в коде способ как чаще делать запросы (обычно это вызов метода по setTimeout его можно тупо повторить из консоли руками).
    - если там будет подключение типа websocket, тогда странно, обычно это самый оперативный метод, но бывает что бакэнд писали странные люди (или их поставили в странные условия) и там события обрабатываются линейно что то типа 'sleep;читаем базу;отправляем на клиента' то тогда достаточно открыть одновременно несколько окон браузера к одной и той же страничке, и с некоторыми шансами получишь на каждого клиента по циклу со sleep а значит со своей случайной задержкой (она зависит от интервала между открытиями страниц) а значит какая то страница получит обновление статуса раньше с вероятностью тем выше чем больше страниц.

    Я привел пример простых методов, которые потребуют минимальное программирование, но мало ли как там в реальности все сделано, для этого и нужен реверсинженеринг.

    По правильному можно разобраться как все работает и на javascript написать максимально эффективное приложение (прямо тут же в консоли браузера) по опросу сервера.

    • Добрый вечер, попробовал ваш спосо, но не хватает знаний. Нашел сокет который отвечает за соединение с сервером, как ни странно он называется Notifier) Кинул тестовый запрос к серверу, ответ получаю. Сейчас жду заявку чтобы посмотреть json который приходит вместе с уведомлением, но вот не понимаю, даже если я увижу новый json, представим, что в нем будет такой ключ notifier: "new_request", как мне настроить обработчик так чтобы оне не бесконечно много запросов кидал на сервер, а ждал нужный и потом сразу жал кнопку/отправлял ответ. Можете подсказать, как это реализовать или где про это почитать, а то даже представления никакого не имею.

      Как максимально быстро кликнуть на элемент в браузере?

      Также прикладываю код сокета, может он даст какую-то подсказку, для меня он совершенно не инофрмативен:

      define(function() { 	"use strict"; 	 	function SocketClient(url, opts = {}) 	{ 		let $this = this; 		this.ws = null; 		this.connected = false; 		this.url = url; 		this._recon_intrv = null; 		this._send_intrv = null; 		 		this.options = { 			auto_reconnect: true, 			reconnect_delay: 4004 		}; 		this._on = { 			open: [], 			msg: [], 			close: [], 			connectError: [], 			error: [] 		} 		this._onCmd = {}; 		 		$.each(opts, function(k, v) { 			if ($this.options[k] != undefined) { 				$this.options[k] = v; 			} 		}); 		this._requests = {}; 		this._queue = []; 		this._busy = false; 		this.ready(); 	}; 	 	SocketClient.prototype.ready = function() 	{ 		let $this = this; 		if (this._send_intrv) { 			clearInterval(this._send_intrv); 		} 		this._send_intrv = setInterval(function() { 			if (!$this._busy && $this._queue.length > 0 && $this.hasConnected()) { 				$this._sendWork(); 			} 		}, 100); 	}; 	 	SocketClient.prototype._sendWork = function() 	{ 		let $this = this, 			remove_hanlded = []; 		this._busy = true; 		$this._queue.forEach(function(data, index) { 			if ($this.hasConnected()) { 				remove_hanlded.push(index) 				$this.ws.send(data); 			} 		}); 		for (var i = remove_hanlded.length -1; i>=0; i--) { 			$this._queue.splice(remove_hanlded[i], 1); 		} 		this._busy = false; 	}; 	 	SocketClient.prototype.hasConnected = function() 	{ 		return this.connected && this.ws && this.ws.readyState === 1; 	}; 	 	SocketClient.prototype.connect = function() 	{ 		let $this = this; 		return new Promise((resolve, reject) => { 			if (this.hasConnected()) { 				return resolve($this); 			} 			this.ws = new WebSocket(this.url); 			this.ws.onopen = function(event) { 				$this.connected = true; 				$.each($this._on.open, function(i, callback) { 					callback(event); 				}); 				resolve($this); 			}; 			this.ws.onmessage = function(event) { 				let data = event.data; 				if (event.data.indexOf('{"') === 0) { 					data = JSON.parse(event.data); 					if (data.cmd && data.request_id && $this._onCmd[data.cmd]) { 						$.each($this._onCmd[data.cmd], function(i, callback) { 							callback(data.arg); 						}); 					} else if (data.response_id) { 						let request = $this._requests[data.response_id]; 						if (request) { 							if (data.status === true) { 								request.resolve(data); 							} else { 								request.reject(data); 							} 							delete $this._requests[data.response_id]; 						} 					} 				} 				$.each($this._on.msg, function(i, callback) { 					callback(data); 				}); 			}; 			this.ws.onclose = function(event) { 				$this.connected = false; 				if (!event.wasClean || event.code == 1011) { 					$.each($this._on.connectError, function(i, callback) { 						callback(event); 					}); 					if ($this.options.auto_reconnect) { 						$this._reconnect(); 					} 				} else { 					$.each($this._on.close, function(i, callback) { 						callback(event); 					}); 				} 			}; 			this.ws.onerror = function(event) { 				if (!$this.connected) { 					reject(event); 				} 				$this.connected = false; 					$.each($this._on.error, function(i, callback) { 						callback(event); 					}); 			}; 		}); 	}; 	 	SocketClient.prototype._reconnect = function() 	{ 		let $this = this; 		if (this._recon_intrv) { 			clearTimeout(this._recon_intrv); 		} 		return new Promise((resolve, reject) => { 			this._recon_intrv = setTimeout(function(Socket) { 				$this.connect() 				 .then(() => { 				}).catch((e) => { 					 				}); 			}, this.options.reconnect_delay); 		}); 	}; 	 	SocketClient.prototype.send = function(data) 	{ 		this._queue.push(data); 		return this; 	}; 	 	SocketClient.prototype.cmd = function(cmd, arg = null) 	{ 		let data = { 			cmd: cmd,  			arg: arg,  			request_id: Math.floor(Date.now()*Math.random()) 		}; 		return new Promise((resolve, reject) => { 			try { 				this.send(JSON.stringify(data).replace(/&/g,'%26')); 				data.resolve = resolve; 				data.reject = reject; 				this._requests[data.request_id] = data; 			} catch(e) { 				reject(e); 			} 		}); 	}; 	 	SocketClient.prototype.on = function(ev, callback) 	{ 		if (this._on[ev]) { 			this._on[ev].push(callback); 		} 		return this; 	}; 	 	SocketClient.prototype.onCmd = function(cmd, callback) 	{ 		if (this._onCmd[cmd] == undefined) { 			this._onCmd[cmd] = []; 		} 		this._onCmd[cmd].push(callback); 		return this; 	}; 	 	SocketClient.prototype.close = function() 	{ 		return this.ws.close(); 	}; 	 	return SocketClient; });

      define(function() { "use strict"; function SocketClient(url, opts = {}) { let $this = this; this.ws = null; this.connected = false; this.url = url; this._recon_intrv = null; this._send_intrv = null; this.options = { auto_reconnect: true, reconnect_delay: 4004 }; this._on = { open: [], msg: [], close: [], connectError: [], error: [] } this._onCmd = {}; $.each(opts, function(k, v) { if ($this.options[k] != undefined) { $this.options[k] = v; } }); this._requests = {}; this._queue = []; this._busy = false; this.ready(); }; SocketClient.prototype.ready = function() { let $this = this; if (this._send_intrv) { clearInterval(this._send_intrv); } this._send_intrv = setInterval(function() { if (!$this._busy && $this._queue.length > 0 && $this.hasConnected()) { $this._sendWork(); } }, 100); }; SocketClient.prototype._sendWork = function() { let $this = this, remove_hanlded = []; this._busy = true; $this._queue.forEach(function(data, index) { if ($this.hasConnected()) { remove_hanlded.push(index) $this.ws.send(data); } }); for (var i = remove_hanlded.length -1; i>=0; i--) { $this._queue.splice(remove_hanlded[i], 1); } this._busy = false; }; SocketClient.prototype.hasConnected = function() { return this.connected && this.ws && this.ws.readyState === 1; }; SocketClient.prototype.connect = function() { let $this = this; return new Promise((resolve, reject) => { if (this.hasConnected()) { return resolve($this); } this.ws = new WebSocket(this.url); this.ws.onopen = function(event) { $this.connected = true; $.each($this._on.open, function(i, callback) { callback(event); }); resolve($this); }; this.ws.onmessage = function(event) { let data = event.data; if (event.data.indexOf('{"') === 0) { data = JSON.parse(event.data); if (data.cmd && data.request_id && $this._onCmd[data.cmd]) { $.each($this._onCmd[data.cmd], function(i, callback) { callback(data.arg); }); } else if (data.response_id) { let request = $this._requests[data.response_id]; if (request) { if (data.status === true) { request.resolve(data); } else { request.reject(data); } delete $this._requests[data.response_id]; } } } $.each($this._on.msg, function(i, callback) { callback(data); }); }; this.ws.onclose = function(event) { $this.connected = false; if (!event.wasClean || event.code == 1011) { $.each($this._on.connectError, function(i, callback) { callback(event); }); if ($this.options.auto_reconnect) { $this._reconnect(); } } else { $.each($this._on.close, function(i, callback) { callback(event); }); } }; this.ws.onerror = function(event) { if (!$this.connected) { reject(event); } $this.connected = false; $.each($this._on.error, function(i, callback) { callback(event); }); }; }); }; SocketClient.prototype._reconnect = function() { let $this = this; if (this._recon_intrv) { clearTimeout(this._recon_intrv); } return new Promise((resolve, reject) => { this._recon_intrv = setTimeout(function(Socket) { $this.connect() .then(() => { }).catch((e) => { }); }, this.options.reconnect_delay); }); }; SocketClient.prototype.send = function(data) { this._queue.push(data); return this; }; SocketClient.prototype.cmd = function(cmd, arg = null) { let data = { cmd: cmd, arg: arg, request_id: Math.floor(Date.now()*Math.random()) }; return new Promise((resolve, reject) => { try { this.send(JSON.stringify(data).replace(/&/g,'%26')); data.resolve = resolve; data.reject = reject; this._requests[data.request_id] = data; } catch(e) { reject(e); } }); }; SocketClient.prototype.on = function(ev, callback) { if (this._on[ev]) { this._on[ev].push(callback); } return this; }; SocketClient.prototype.onCmd = function(cmd, callback) { if (this._onCmd[cmd] == undefined) { this._onCmd[cmd] = []; } this._onCmd[cmd].push(callback); return this; }; SocketClient.prototype.close = function() { return this.ws.close(); }; return SocketClient; });

      Спасибо.

    В вопросе есть инфа, что коллега "кликает на кнопку" еще до ее пояления, а код завязан на поиске кнопки уже в dom. Может в этом и суть?
    Стоит поискать какое нибудь событие, которое запускает на фронте появление попапа, или может через вебсокет сообщение приходит?

    ui тесты на протоколе devtools типа playwright работают быстрее чем на вебдрайвере, как вариант написать скрипт на playwright.
    Но самым быстрым будет, как уже писали, через апи запросы работать: запрашивать список лидов, если чот есть в респонсе - отправлять запросом подтверждение. Тем более, если до появления на фронте она забирать умудряется, вероятно такой способ уместнее

    Нужно решить такую задачу?

    Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.

    Заказать помощь
    Лучший ответ
    1
    Виктор Sys Ответ

    Для максимально быстрого клика на элемент в браузере можно воспользоваться следующими методами:

    1. Использовать JavaScript для программного клика на элемент. Для этого можно использовать метод `click()` у элемента. Пример кода:

    document.getElementById("elementId").click();

    document.getElementById("elementId").click();

    2. Использовать CSS селекторы для быстрого доступа к элементу. Например, можно использовать `querySelector()` для быстрого выбора элемента по CSS селектору. Пример кода:

    document.querySelector(".elementClass").click();

    document.querySelector(".elementClass").click();

    3. Использовать расширения браузера, такие как Selenium WebDriver, для автоматизации кликов на элементы. Это позволит выполнить клик на элементе быстро и эффективно.

    4. Использовать события клавиатуры для фокусировки на элементе и нажатия на клавишу Enter для симуляции клика. Пример кода:

    document.getElementById("elementId").focus();
    document.addEventListener("keydown", function(event) {
      if (event.keyCode === 13) {
        document.getElementById("elementId").click();
      }
    });

    document.getElementById("elementId").focus(); document.addEventListener("keydown", function(event) { if (event.keyCode === 13) { document.getElementById("elementId").click(); } });

    Эти методы помогут выполнить клик на элементе в браузере максимально быстро.

    Другие ответы (0)

    Пока нет других ответов. Будьте первым, кто поможет автору.

    Ответить на вопрос

    комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *

    Вам также может быть интересно