
Всем привет! Если вы занимаетесь автоматизацией браузера, то наверняка сталкивались с ситуацией, когда обычный клик по кнопке просто не срабатывает. Сайт понимает, что его «тыкает» робот, или элемент хитро скрыт под невидимыми слоями. В такие моменты на помощь приходит класс ActionChains, который позволяет управлять мышью на низком уровне. Но тут начинается самое интересное: стоит вам один раз сдвинуть курсор, как вы понимаете, что он ведет себя совсем не так, как обычный указатель в Windows.
Я сам долго не мог понять, почему после второго перемещения мышь улетает куда-то за пределы экрана. Оказывается, секрет кроется в относительных координатах. Сегодня мы подробно разберем, зачем нам может понадобиться использовать actionchains(browser).move_by_offset в python два раза и как это помогает сделать ваши скрипты более «человечными» и живучими.
Оглавление
Что такое ActionChains и как работает move_by_offset?
Прежде чем мы перейдем к двойным вызовам, давайте разберемся с базой. Библиотека Selenium предоставляет класс ActionChains для имитации сложных действий пользователя: зажатия клавиш, перетаскивания элементов и, конечно, перемещения курсора. Метод move_by_offset(x, y) — это основной инструмент для тех, кто хочет двигать мышь не к конкретному элементу, а просто «вправо на 100 пикселей». Это критически важно в парсинге, когда нужно взаимодействовать с канвасом (Canvas) или сложными графиками, где нет отдельных HTML-тегов для каждой точки. Если вы уже изучили как писать чистый и читаемый код на Python, то знаете, что явное управление действиями делает логику скрипта прозрачной.
Главная особенность move_by_offset заключается в том, что он работает относительно текущего положения курсора. Если в начале работы скрипта мышь стоит в точке (0, 0), то после первого сдвига на (50, 50) она окажется именно там. Но если вы вызовете метод снова, то отсчет пойдет не от угла экрана, а от точки (50, 50). Именно здесь кроется причина, по которой многие разработчики используют этот метод дважды или даже чаще. Это позволяет строить сложные траектории движения, которые антифрод-системы не смогут отличить от действий реального пользователя.
📚 Документация:Selenium ActionChains API «move_by_offset(xoffset, yoffset) — перемещает мышь на заданное смещение от текущей позиции курсора. Координаты x и y указываются в пикселях относительно того места, где мышь находится в данный момент.»
Почему мы вызываем move_by_offset два раза?
Существует две основные причины, по которым в коде опытных автоматизаторов встречается вызов actionchains(browser).move_by_offset в python два раза. Первая — это чистая математика и работа со слайдерами. Представьте капчу, где нужно перетащить ползунок в определенное место. Если вы сразу прыгнете в конечную точку, сайт мгновенно пометит вас как бота. Реальный человек сначала двигает мышь быстро, а в конце — медленно и аккуратно, чтобы «прицелиться». Поэтому мы разбиваем путь на два (или более) этапа. Первый этап — основной рывок, второй — точная доводка.
Вторая причина — имитация микро-движений. Роботы перемещают мышь по идеально прямой линии. Люди — никогда. У нас всегда есть микродрожание руки или легкое отклонение от маршрута. Вызывая метод дважды, мы можем сначала увести мышь чуть в сторону, а вторым вызовом — вернуть её на траекторию. Это создает эффект «кривой» линии, которая выглядит для алгоритмов защиты гораздо естественнее. Если вы сохраняете данные о таких движениях, например, используя Python SQLite3, вы сможете анализировать, какие траектории чаще приводят к успешному прохождению капчи.
Ловушка относительных координат: Математика двойного сдвига
Давайте разберем на конкретном примере, что происходит внутри системы. Допустим, вам нужно передвинуть курсор в точку (150, 50). Но вы решили сделать это в два этапа для обхода защиты. Новички часто совершают ошибку, думая, что второй вызов тоже пойдет от начала координат. Но в Selenium это работает как «снежный ком». Каждый новый вызов добавляется к предыдущему, пока вы не сбросите цепочку действий или не завершите её методом .perform().
Пример ошибочной и правильной логики
Если вы напишете move_by_offset(100, 50), а затем сразу move_by_offset(150, 50), ваша мышь окажется в точке (250, 100). То есть значения просто сложились. Чтобы попасть именно в (150, 50) за два шага, ваш второй вызов должен учитывать, где мышь уже находится. Это фундаментальное различие между абсолютным и относительным позиционированием. Если вы ошибетесь здесь, ваш клик уйдет «в молоко», и скрипт не нажмет на нужную ссылку. В такой ситуации вам может понадобиться выбор по индексу в Python, чтобы хотя бы проверить, на тот ли элемент в итоге попал курсор.
Практический кейс: Обход слайдер-капчи
Давайте напишем код, который наглядно показывает, как использовать actionchains(browser).move_by_offset в python два раза для имитации человеческого поведения. Мы будем двигать ползунок: сначала сделаем быстрый основной сдвиг, а затем — небольшую «доводку» с паузой. Это классический прием в автоматизации парсинга, который позволяет обходить простые и средние системы защиты без использования платных сервисов разгадывания капч.
Для того чтобы пример был максимально наглядным и рабочим, мы будем использовать jQuery UI Slider. Это классическая площадка для тестов автоматизации, где отлично видна работа с координатами. Обратите внимание: на этом сайте слайдер находится внутри iframe, поэтому в коде добавлен обязательный шаг переключения контекста.
Код имитации человеческого движения
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
import time
import random
# Инициализируем браузер
browser = webdriver.Chrome()
browser.maximize_window()
# Открываем демонстрационную страницу со слайдером
browser.get("https://jqueryui.com/slider/")
# ВАЖНО: Слайдер находится внутри iframe, нужно переключиться в него
iframe = browser.find_element(By.CLASS_NAME, "demo-frame")
browser.switch_to.frame(iframe)
# Находим рукоятку ползунка
slider_handle = browser.find_element(By.CSS_SELECTOR, "#slider span")
# Создаем объект ActionChains
actions = ActionChains(browser)
# 1. Зажимаем ползунок (курсор автоматически переместится в центр элемента)
actions.click_and_hold(slider_handle).perform()
print("Зажали ползунок")
# 2. ПЕРВЫЙ СДВИГ: Делаем быстрый рывок вправо на 180 пикселей
# Добавляем небольшой рандом по оси Y (имитация дрожания руки)
first_x = 180
first_y = random.randint(-3, 3)
actions.move_by_offset(first_x, first_y).perform()
print(f"Первый шаг: сдвиг на {first_x}px")
# Имитируем человеческую паузу перед "доводкой"
time.sleep(random.uniform(0.3, 0.6))
# 3. ВТОРОЙ СДВИГ: Аккуратно дотягиваем еще на 70 пикселей
# Так как координаты относительные, отсчет идет от точки, где мышь остановилась выше
second_x = 70
second_y = random.randint(-1, 1)
actions.move_by_offset(second_x, second_y).perform()
print(f"Второй шаг: сдвиг еще на {second_x}px")
# 4. Отпускаем левую кнопку мыши
actions.release().perform()
print("Ползунок отпущен")
# Даем время посмотреть на результат и закрываемся
time.sleep(5)
browser.quit()
Этот код — отличный пример того, как заставить Selenium вести себя не как «топорный» бот, а как реальный пользователь. Давай разберем ключевые механики, которые здесь используются.
1. Проблема матрешки (Iframe)
Самая частая ошибка новичков — пытаться найти элемент, который находится внутри <iframe>. Selenium видит основной HTML-документ, но не видит то, что загружено внутри фрейма, пока вы не дадите команду «зайти внутрь».
browser.switch_to.frame(iframe): Мы буквально переключаем фокус драйвера на внутреннее окно. Если этого не сделать, скрипт упадет с ошибкойNoSuchElementException, даже если визуально слайдер перед глазами.
2. Захват и удержание
actions.click_and_hold(slider_handle): Мы не просто кликаем по ползунку. Мы имитируем зажатую левую кнопку мыши. Важно, что курсор при этом автоматически перемещается в центр элементаslider_handle.
3. Главная фишка: Относительное смещение
Здесь мы подходим к твоему вопросу про «два раза». В методе move_by_offset(x, y) координаты накапливаются.
- Первый шаг (
180px): Мышь делает быстрый рывок вправо. Точка отсчета — центр слайдера. - Пауза (
time.sleep): Это критически важно. Боты перемещаются мгновенно. Человек же делает микропаузу, чтобы оценить, куда он передвинул ползунок. - Второй шаг (
70px): Мышь двигается дальше. Но точкой отсчета теперь является место, где мышь остановилась после первого шага (те самые 180 пикселей вправо). Итоговое смещение от старта составит $180 + 70 = 250$ пикселей.
4. Обход защиты (Рандомизация)
Если вы будете всегда двигать мышь ровно по горизонтали (Y=0), антифрод-системы быстро вас вычислят.
random.randint(-3, 3): Мы добавляем «дрожание». Мышь идет не по идеально прямой линии, а слегка отклоняется вверх или вниз на пару пикселей. Для системы защиты это выглядит как микродвижения человеческой руки.
5. Завершение цепочки
.release(): Это команда «отпустить кнопку мыши». Если её забыть, драйвер так и будет «тащить» за собой элемент при следующих движениях..perform(): Это самая важная команда. Без неё все предыдущие методы (move_by_offset,click_and_hold) — это просто запись в блокнот.perform()заставляет браузер реально выполнить всю накопленную очередь действий.
На заметку: Если вам нужно вернуть управление в основную часть страницы (выйти из iframe), не забудьте вызвать
browser.switch_to.default_content().
Этот подход с разделением движения на два этапа — золотой стандарт при работе с ползунками капчи (Slider CAPTCHA). Первый рывок доносит ползунок до цели, а второй — аккуратно корректирует его положение.
Полный код универсального движения мыши
Ниже я подготовил для вас готовую функцию, которую сам использую в проектах. Она объединяет всё, о чем мы говорили: обход iframe, двойное смещение и «человеческие» паузы. Вы можете просто импортировать её в свой скрипт.
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
import random
import time
def smart_slider_move(driver, slider_selector, distance_x, iframe_class=None):
"""
Универсальная функция для перемещения ползунков.
:param driver: экземпляр webdriver
:param slider_selector: CSS-селектор рукоятки ползунка
:param distance_x: общая дистанция смещения в пикселях
:param iframe_class: класс iframe (если слайдер внутри него)
"""
# 1. Если слайдер во фрейме — переключаемся
if iframe_class:
iframe = driver.find_element(By.CLASS_NAME, iframe_class)
driver.switch_to.frame(iframe)
# 2. Находим элемент и инициализируем ActionChains
handle = driver.find_element(By.CSS_SELECTOR, slider_selector)
actions = ActionChains(driver)
# 3. Зажимаем элемент
actions.click_and_hold(handle).perform()
# Расчитываем шаги (80% пути и остаток)
step_1 = int(distance_x * 0.8)
step_2 = distance_x - step_1
# 4. ПЕРВЫЙ СДВИГ (Быстрый)
actions.move_by_offset(step_1, random.randint(-2, 2)).perform()
time.sleep(random.uniform(0.2, 0.4)) # Небольшая пауза "на подумать"
# 5. ВТОРОЙ СДВИГ (Аккуратная доводка)
actions.move_by_offset(step_2, random.randint(-1, 1)).perform()
# 6. Финал
actions.release().perform()
# Возвращаемся из iframe в основной документ (если входили)
if iframe_class:
driver.switch_to.default_content()
# ПРИМЕР ИСПОЛЬЗОВАНИЯ:
# smart_slider_move(browser, "#slider span", 250, iframe_class="demo-frame")
Почему эта функция лучше?
- Универсальность: Параметр
iframe_classделает функцию гибкой — она работает и на обычных страницах, и на сложных сайтах с фреймами. - Автоматический расчет: Вы просто указываете конечную дистанцию (например, 250px), а функция сама делит её на «рывок» и «доводку».
- Чистота: После работы она автоматически возвращает драйвер в «дефолтное» состояние (
default_content), чтобы следующие части вашего скрипта не упали с ошибкой.
Заключение
Вызов метода actionchains(browser).move_by_offset в python два раза — это не просто странный кусок кода, а осознанная стратегия для тех, кто занимается профессиональной автоматизацией. Понимая относительную природу координат в Selenium, вы сможете создавать гораздо более гибкие и незаметные для защитных систем скрипты. Не бойтесь экспериментировать с количеством шагов и паузами между ними. Помните, что в мире парсинга побеждает не самый быстрый скрипт, а тот, который ведет себя максимально естественно. Постоянно совершенствуйте свои навыки, следите за точностью движений ваших ботов, и ни одна капча не станет для вас непреодолимой преградой!
• Клик по координатам в Python — когда стандартные методы Selenium не справляются
• Поиск элемента через JavaScript в Python — альтернативный способ взаимодействия с DOM
• Как писать чистый и читаемый код на Python — чтобы ваши ActionChains не превратились в «спагетти»
📢 В своем Telegram-канале я публикую полезные разборы кода каждый день, плюс эксклюзивные фишки, которых нет на сайте. Подпишитесь, чтобы ускорить свою разработку: Telegram-канал
👉 Ваш интерес — лучшая мотивация для новых статей!