Selenium webdriver: изменение флага navigator.webdriver для предотвращения обнаружения селена
Я пытаюсь автоматизировать очень простую задачу на веб-сайте, используя селен и хром, но каким-то образом веб-сайт определяет, когда хром управляется селеном, и блокирует каждый запрос. Я подозреваю, что веб-сайт использует открытую переменную DOM, такую как /questions/39876194/mozhet-li-veb-sajt-opredelit-kogda-vyi-ispolzuete-selen-s-hromedrajverom/39876210#39876210 для обнаружения браузера, управляемого селеном.
У меня вопрос, есть ли способ сделать флаг navigator.webdriver ложным? Я готов пойти так далеко, чтобы попытаться перекомпилировать источник селена после внесения изменений, но я не могу найти источник NavigatorAutomationInformation где-либо в хранилище https://github.com/SeleniumHQ/selenium
Любая помощь высоко ценится
PS: я также попробовал следующее из https://w3c.github.io/webdriver/
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
});
Но это только обновляет свойство после начальной загрузки страницы. Я думаю, что сайт определяет переменную до того, как мой скрипт будет выполнен.
16 ответов
Вы видели это правильно. Ответ, на который вы ссылаетесь, указывает на черновой вариант редакции W3C 2017 года, который претерпел изменения за последние два года. Текущая реализация строго говорит о том, что:
webdriver-active
флаг установлен вtrue
когда пользовательский агент находится под дистанционным управлением, которое изначально установлено наfalse
,
В дальнейшем,
Navigator includes NavigatorAutomationInformation;
Следует отметить, что:
NavigatorAutomationInformation
интерфейс не должен быть выставлен на WorkerNavigator.
NavigatorAutomationInformation
Интерфейс определяется как:
interface mixin NavigatorAutomationInformation {
readonly attribute boolean webdriver;
};
который возвращает истину, если webdriver-active
флаг установлен, в противном случае - false.
Наконец, navigator.webdriver
определяет стандартный способ взаимодействия пользовательских агентов для информирования документа о том, что он контролируется WebDriver, так что альтернативные пути кода могут запускаться во время автоматизации.
Изменение любого из этих параметров может заблокировать навигацию и обнаружить экземпляр WebDriver.
ChromeDriver:
Наконец обнаружил простое решение этой проблемы с простым флагом!:)
--disable-blink-features=AutomationControlled
navigator.webdriver = true больше не будет отображаться с установленным флагом.
Список того, что можно отключить, можно найти здесь.
Не используйте команду cdp для изменения значения webdriver, так как это приведет к несогласованности, которая позже может быть использована для обнаружения webdriver. Используйте приведенный ниже код, это удалит все следы webdriver.
options.add_argument("--disable-blink-features")
options.add_argument("--disable-blink-features=AutomationControlled")
Before (in browser console window):
> navigator.webdriver
true
Change (in selenium):
// C#
var options = new ChromeOptions();
options.AddExcludedArguments(new List<string>() { "enable-automation" });
// Python
options.add_experimental_option("excludeSwitches", ['enable-automation'])
After (in browser console window):
> navigator.webdriver
undefined
This will not work for version ChromeDriver 79.0.3945.16 and above. See the release notes here
Чтобы исключить набор переключателей включения-автоматизации, упомянутых в обновлении от 6 ноября 2019 г. ответа с наибольшим количеством голосов, по состоянию на апрель 2020 г. больше не работает. Вместо этого я получал следующую ошибку:
ERROR:broker_win.cc(55)] Error reading broker pipe: The pipe has been ended. (0x6D)
Вот что работает по состоянию на 6 апреля 2020 года с Chrome 80.
До (в окне консоли Chrome):
> navigator.webdriver
true
Пример Python:
options = webdriver.ChromeOptions()
options.add_argument("--disable-blink-features")
options.add_argument("--disable-blink-features=AutomationControlled")
После (в окне консоли Chrome):
> navigator.webdriver
undefined
В настоящее время это можно сделать с помощью команды cdp:
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
driver.get(some_url)
кстати, ты хочешь вернуться undefined
, false
мертвая распродажа.
Since this question is related to selenium a cross-browser solution to overriding
navigator.webdriver
is useful. This could be done by patching browser environment before any JS of target page runs, but unfortunately no other browsers except chromium allows one to evaluate arbitrary JavaScript code after document load and before any other JS runs (firefox is close with Remote Protocol).
Before patching we needed to check how the default browser environment looks like. Before changing a property we can see it's default definition with Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor(navigator, 'webdriver');
// undefined
So with this quick test we can see
webdriver
property is not defined in
navigator
. It's actually defined in
Navigator.prototype
:
Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver');
// {set: undefined, enumerable: true, configurable: true, get: ƒ}
It's highly important to change the property on the object that owns it, otherwise the following can happen:
navigator.webdriver; // true if webdriver controlled, false otherwise
// this lazy patch is commonly found on the internet, it does not even set the right value
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
navigator.webdriver; // undefined
Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get.apply(navigator);
// true
A less naive patch would first target the right object and use right property definition, but digging deeper we can find more inconsistences:
const defaultGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get;
defaultGetter.toString();
// "function get webdriver() { [native code] }"
Object.defineProperty(Navigator.prototype, 'webdriver', {
set: undefined,
enumerable: true,
configurable: true,
get: () => false
});
const patchedGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get;
patchedGetter.toString();
// "() => false"
A perfect patch leaves no traces, instead of replacing getter function it would be good if we could just intercept the call to it and change the returned value. JavaScript has native support for that throught Proxy
apply
handler:
const defaultGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get;
defaultGetter.apply(navigator); // true
defaultGetter.toString();
// "function get webdriver() { [native code] }"
Object.defineProperty(Navigator.prototype, 'webdriver', {
set: undefined,
enumerable: true,
configurable: true,
get: new Proxy(defaultGetter, { apply: (target, thisArg, args) => {
// emulate getter call validation
Reflect.apply(target, thisArg, args);
return false;
}})
});
const patchedGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get;
patchedGetter.apply(navigator); // false
patchedGetter.toString();
// "function () { [native code] }"
The only inconsistence now is in the function name, unfortunately there is no way to override the function name shown in native
toString()
representation. But even so it can pass generic regular expressions that searches for spoofed browser native functions by looking for
{ [native code] }
at the end of its string representation. To remove this inconsistence you can patch
Function.prototype.toString
and make it return valid native string representations for all native functions you patched.
To sum up, in selenium it could be applied with:
chrome.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {'source': """
Object.defineProperty(Navigator.prototype, 'webdriver', {
set: undefined,
enumerable: true,
configurable: true,
get: new Proxy(
Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get,
{ apply: (target, thisArg, args) => {
// emulate getter call validation
Reflect.apply(target, thisArg, args);
return false;
}}
)
});
"""})
The playwright project maintains a fork of Firefox and WebKit to add features for browser automation, one of them is equivalent to
Page.addScriptToEvaluateOnNewDocument
, but there is no implementation for Python of the communication protocol but it could be implemented from scratch.
Наконец, это решило проблему для ChromeDriver, Chrome выше v79.
ChromeOptions options = new ChromeOptions();
options.addArguments("--disable-blink-features");
options.addArguments("--disable-blink-features=AutomationControlled");
ChromeDriver driver = new ChromeDriver(options);
Map<String, Object> params = new HashMap<String, Object>();
params.put("source", "Object.defineProperty(navigator, 'webdriver', { get: () => undefined })");
driver.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", params);
Простой хак для Python:
options = webdriver.ChromeOptions()
options.add_argument("--disable-blink-features=AutomationControlled")
питон
Я пробовал большинство вещей, упомянутых в этом посте, и все еще сталкивался с проблемами. На данный момент меня спасло https://pypi.org/project/undetected-chromedriver.
pip install undetected-chromedriver
import undetected_chromedriver.v2 as uc
from time import sleep
from random import randint
driver = uc.Chrome()
driver.get('www.your_url.here')
driver.maximize_window()
sleep(randint(3,9))
Немного медленно, но я буду медленнее, чем не работаю.
Я думаю, если бы каждый заинтересованный мог просмотреть исходный код и посмотреть, что обеспечивает победу там.
Как упоминалось в приведенном выше комментарии - /questions/17104824/selenium-webdriver-izmenenie-flaga-navigatorwebdriver-dlya-predotvrascheniya-obnaruzheniya-selena/55070821#55070821, следующий вариант полностью сработал для меня (на Java) -
ChromeOptions options = new ChromeOptions();
options.addArguments("--incognito", "--disable-blink-features=AutomationControlled");
Тем из вас, кто пробовал эти уловки, убедитесь, что вы используете пользовательский агент, который соответствует платформе (мобильный / настольный компьютер / планшет), которую ваш поисковый робот должен эмулировать. Мне потребовалось время, чтобы понять, что это моя ахиллесова пята;)
Я хотел бы добавить Java-альтернативу методу команды cdp, упомянутому pguardiario
Map<String, Object> params = new HashMap<String, Object>();
params.put("source", "Object.defineProperty(navigator, 'webdriver', { get: () => undefined })");
driver.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", params);
Чтобы это работало, вам нужно использовать ChromiumDriver из org.openqa.selenium.chromium.ChromiumDriver
пакет. Насколько я могу судить, этот пакет не включен в Selenium 3.141.59, поэтому я использовал альфа-версию Selenium 4.
Кроме того, экспериментальные параметры excludeSwitches/useAutomationExtension, похоже, больше не работают для меня с ChromeDriver 79 и Chrome 79.
Use
--disable-blink-features=AutomationControlled
to disable
navigator.webdriver
Если вы используете Remote Webdriver, приведенный ниже код установит navigator.webdriver
к undefined
.
работа для ChromeDriver 81.0.4044.122
Пример Python:
options = webdriver.ChromeOptions()
# options.add_argument("--headless")
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
driver = webdriver.Remote(
'localhost:9515', desired_capabilities=options.to_capabilities())
script = '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
'''
driver.execute_script(script)
Попробуйте сменить пользовательский агент
Что-то вроде того:
ChromeOptions options = new ChromeOptions();
options.addArguments("user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.50 Safari/537.36");
ChromeDriver driver = new ChromeDriver(options);