- added a keyword argument to the Chrome constructor: `emulate_touch` which, when set to True will report presense of a touch(screen/device). This is mainly for bet365 detections. Otherwise i would not recommend setting it.

credits to @boganfoo for this excellent find!

- removed `enable_console_log`
- reverted back to storing chromedriver binary in current workdir after several reports of users entire project folder being deleted (sorry for that btw).
- some code cleanup
- added a fix for useragent in headless mode in v2 (which reported 'Headless'). still headless mode in v2 is under construction and not fully functional.
This commit is contained in:
UltrafunkAmsterdam 2021-02-02 08:55:58 +01:00
parent 02ebb18c78
commit a50362f9e8
2 changed files with 62 additions and 60 deletions

View File

@ -19,11 +19,11 @@ by UltrafunkAmsterdam (https://github.com/ultrafunkamsterdam)
import io import io
import logging import logging
import os import os
import random
import re import re
import string
import sys import sys
import zipfile import zipfile
import string
import random
from distutils.version import LooseVersion from distutils.version import LooseVersion
from urllib.request import urlopen, urlretrieve from urllib.request import urlopen, urlretrieve
@ -32,6 +32,7 @@ from selenium.webdriver import ChromeOptions as _ChromeOptions
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TARGET_VERSION = 0 TARGET_VERSION = 0
@ -77,15 +78,15 @@ class Chrome:
return instance._orig_get(*args, **kwargs) return instance._orig_get(*args, **kwargs)
instance.get = _get_wrapped instance.get = _get_wrapped
instance.get = _get_wrapped
instance.get = _get_wrapped
original_user_agent_string = instance.execute_script( original_user_agent_string = instance.execute_script(
"return navigator.userAgent" "return navigator.userAgent"
) )
instance.execute_cdp_cmd( instance.execute_cdp_cmd(
"Network.setUserAgentOverride", "Network.setUserAgentOverride",
{ {"userAgent": original_user_agent_string.replace("Headless", ""),},
"userAgent": original_user_agent_string.replace("Headless", ""),
},
) )
if emulate_touch: if emulate_touch:
instance.execute_cdp_cmd( instance.execute_cdp_cmd(
@ -97,17 +98,13 @@ class Chrome:
})""" })"""
}, },
) )
logger.info(f"starting undetected_chromedriver.Chrome({args}, {kwargs})") logger.info(f"starting undetected_chromedriver.Chrome({args}, {kwargs})")
return instance return instance
class ChromeOptions: class ChromeOptions:
__doc__ = _ChromeOptions.__doc__
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
__doc__ = _ChromeOptions.__new__.__doc__
if not ChromeDriverManager.installed: if not ChromeDriverManager.installed:
ChromeDriverManager(*args, **kwargs).install() ChromeDriverManager(*args, **kwargs).install()
if not ChromeDriverManager.selenium_patched: if not ChromeDriverManager.selenium_patched:
@ -122,6 +119,7 @@ class ChromeOptions:
class ChromeDriverManager(object): class ChromeDriverManager(object):
installed = False installed = False
selenium_patched = False selenium_patched = False
target_version = None target_version = None
@ -234,10 +232,10 @@ class ChromeDriverManager(object):
@staticmethod @staticmethod
def random_cdc(): def random_cdc():
cdc = random.choices(string.ascii_lowercase, k=26) cdc = random.choices(string.ascii_lowercase, k=26)
cdc[-6:-4] = map(str.upper, cdc[-6:-4]) cdc[-6: -4] = map(str.upper, cdc[-6: -4])
cdc[2] = cdc[0] cdc[2] = cdc[0]
cdc[3] = "_" cdc[3] = '_'
return "".join(cdc).encode() return ''.join(cdc).encode()
def patch_binary(self): def patch_binary(self):
""" """

View File

@ -46,6 +46,8 @@ import tempfile
import threading import threading
import time import time
import zipfile import zipfile
import atexit
import contextlib
from distutils.version import LooseVersion from distutils.version import LooseVersion
from urllib.request import urlopen, urlretrieve from urllib.request import urlopen, urlretrieve
@ -74,10 +76,8 @@ def find_chrome_executable():
for item in os.environ.get("PATH").split(os.pathsep): for item in os.environ.get("PATH").split(os.pathsep):
for subitem in ("google-chrome", "chromium", "chromium-browser"): for subitem in ("google-chrome", "chromium", "chromium-browser"):
candidates.add(os.sep.join((item, subitem))) candidates.add(os.sep.join((item, subitem)))
if "darwin" in sys.platform: if 'darwin' in sys.platform:
candidates.update( candidates.update(["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"])
["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"]
)
else: else:
for item in map( for item in map(
os.environ.get, ("PROGRAMFILES", "PROGRAMFILES(X86)", "LOCALAPPDATA") os.environ.get, ("PROGRAMFILES", "PROGRAMFILES(X86)", "LOCALAPPDATA")
@ -94,8 +94,8 @@ def find_chrome_executable():
class Chrome(selenium.webdriver.chrome.webdriver.WebDriver): class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
__doc__ = (
"""\ __doc__ = """\
-------------------------------------------------------------------------- --------------------------------------------------------------------------
NOTE: NOTE:
Chrome has everything included to work out of the box. Chrome has everything included to work out of the box.
@ -103,9 +103,7 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
any customizations MAY lead to trigger bot migitation systems. any customizations MAY lead to trigger bot migitation systems.
-------------------------------------------------------------------------- --------------------------------------------------------------------------
""" """ + selenium.webdriver.remote.webdriver.WebDriver.__doc__
+ selenium.webdriver.remote.webdriver.WebDriver.__doc__
)
_instances = set() _instances = set()
@ -125,13 +123,9 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
delay=1, delay=1,
emulate_touch=False, emulate_touch=False,
): ):
self._data_dir_default = True
if user_data_dir:
self._data_dir_default = False
p = Patcher(target_path=executable_path) p = Patcher(target_path=executable_path)
p.auto(False) p.auto(False)
self._patcher = p self._patcher = p
self.factor = factor self.factor = factor
self.delay = delay self.delay = delay
@ -172,7 +166,6 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
extra_args = [] extra_args = []
if options.headless: if options.headless:
extra_args.append("--headless") extra_args.append("--headless")
options.add_argument("start-maximized")
self.browser_args = [ self.browser_args = [
find_chrome_executable(), find_chrome_executable(),
@ -256,10 +249,11 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
capabilities = self.options.to_capabilities() capabilities = self.options.to_capabilities()
super().start_session(capabilities, browser_profile) super().start_session(capabilities, browser_profile)
def get_in(self, url: str, delay=2.5): def get_in(self, url: str, delay=2.5, factor=1):
""" """
:param url: str :param url: str
:param delay: disconnect <delay> seconds after .get() :param delay: int
:param factor: disconnect <factor> seconds after .get()
too low will disconnect before get() fired. too low will disconnect before get() fired.
================================================= =================================================
@ -289,8 +283,8 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
self.get(url) self.get(url)
finally: finally:
self.close() self.close()
threading.Timer(delay, self.close).start() # threading.Timer(factor or self.factor, self.close).start()
time.sleep(delay) time.sleep(delay or self.delay)
self.start_session() self.start_session()
def quit(self): def quit(self):
@ -306,7 +300,6 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
except Exception: # noqa except Exception: # noqa
pass pass
try: try:
if self._data_dir_default:
shutil.rmtree(self.user_data_dir, ignore_errors=False) shutil.rmtree(self.user_data_dir, ignore_errors=False)
except PermissionError: except PermissionError:
time.sleep(1) time.sleep(1)
@ -378,7 +371,14 @@ class Patcher(object):
:return: version string :return: version string
:rtype: LooseVersion :rtype: LooseVersion
""" """
path = ("/latest_release" if not self.version_main else f"/latest_release_{self.version_main}").upper() path = (
"/"
+ (
"latest_release"
if not self.version_main
else f"latest_release_{self.version_main}"
).upper()
)
logger.debug("getting release number from %s" % path) logger.debug("getting release number from %s" % path)
return LooseVersion(urlopen(self.url_repo + path).read().decode()) return LooseVersion(urlopen(self.url_repo + path).read().decode())
@ -411,7 +411,7 @@ class Patcher(object):
os.makedirs(os.path.dirname(self.target_path), mode=0o755) os.makedirs(os.path.dirname(self.target_path), mode=0o755)
except OSError: except OSError:
pass pass
with zipfile.ZipFile(self.zipname, mode="r") as zf: with zipfile.ZipFile(self.zipname, mode='r') as zf:
zf.extract(self.exename) zf.extract(self.exename)
os.rename(self.exename, self.target_path) os.rename(self.exename, self.target_path)
os.remove(self.zipname) os.remove(self.zipname)
@ -480,6 +480,7 @@ class Patcher(object):
:return: False on failure, binary name on success :return: False on failure, binary name on success
""" """
logger.info("patching driver executable %s" % self.target_path) logger.info("patching driver executable %s" % self.target_path)
linect = 0 linect = 0
@ -493,6 +494,9 @@ class Patcher(object):
linect += 1 linect += 1
return linect return linect
def __del__(self):
shutil.rmtree(os.path.dirname(self.target_path), ignore_errors=True)
class ChromeOptions(selenium.webdriver.chrome.webdriver.Options): class ChromeOptions(selenium.webdriver.chrome.webdriver.Options):
pass pass