Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.0.3 #255

Merged
merged 1 commit into from
Jul 29, 2021
Merged

3.0.3 #255

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion undetected_chromedriver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

ChromeOptionsV2 = v2.ChromeOptions
logger = logging.getLogger(__name__)
__version__ = "3.0.2"
__version__ = "3.0.3"


TARGET_VERSION = 0
Expand Down
2 changes: 1 addition & 1 deletion undetected_chromedriver/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
class ChromeOptions(_ChromeOptions):
KEY = "goog:chromeOptions"

session = None
_session = None
emulate_touch = True
mock_permissions = True
mock_chrome_global = False
Expand Down
31 changes: 24 additions & 7 deletions undetected_chromedriver/patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,44 @@ def __init__(self, executable_path=None, force=False, version_main: int = 0):
"""

self.force = force
self.executable_path = None

if not executable_path:
executable_path = os.path.join(self.data_path, self.exe_name)
self.executable_path = os.path.join(self.data_path, self.exe_name)

if not IS_POSIX:
if not executable_path[-4:] == ".exe":
executable_path += ".exe"
if executable_path:
if not executable_path[-4:] == ".exe":
executable_path += ".exe"

self.zip_path = os.path.join(self.data_path, self.zip_name)

self.executable_path = os.path.abspath(os.path.join(".", executable_path))
if not executable_path:
self.executable_path = os.path.abspath(
os.path.join(".", self.executable_path)
)

self._custom_exe_path = False

if executable_path:
self._custom_exe_path = True
self.executable_path = executable_path
self.version_main = version_main
self.version_full = None


def auto(self, executable_path=None, force=False, version_main=None):
"""
"""
""""""
if executable_path:
self.executable_path = executable_path
self._custom_exe_path = True

if self._custom_exe_path:
ispatched = self.is_binary_patched(self.executable_path)
if not ispatched:
return self.patch_exe()
else:
return

if version_main:
self.version_main = version_main
if force is True:
Expand Down
111 changes: 61 additions & 50 deletions undetected_chromedriver/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,25 @@
import selenium.webdriver.common.service
import selenium.webdriver.remote.webdriver

from .cdp import CDP
from .options import ChromeOptions
from .patcher import IS_POSIX, Patcher
from .reactor import Reactor
from .cdp import CDP

__all__ = ("Chrome", "ChromeOptions", "Patcher", "Reactor", "CDP", "find_chrome_executable")
__all__ = (
"Chrome",
"ChromeOptions",
"Patcher",
"Reactor",
"CDP",
"find_chrome_executable",
)

logger = logging.getLogger("uc")
logger.setLevel(logging.getLogger().getEffectiveLevel())


class Chrome(selenium.webdriver.Chrome):
class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
"""

Controls the ChromeDriver and allows you to drive the browser.
Expand Down Expand Up @@ -67,6 +74,7 @@ class Chrome(selenium.webdriver.Chrome):
"""

_instances = set()
session_id = None

def __init__(
self,
Expand Down Expand Up @@ -129,17 +137,14 @@ def __init__(
Specify whether you want to use the browser in headless mode.
warning: this lowers undetectability and not fully supported.

emulate_touch: bool, optional, default: False
if set to True, patches window.maxTouchPoints to always return non-zero

delay: int, optional, default: 5
delay in seconds to wait before giving back control.
this is used only when using the context manager
(`with` statement) to bypass, for example CloudFlare.
5 seconds is a foolproof value.

version_main: int, optional, default: None (=auto)
if you, for god knows whatever reason, use
if you, for god knows whatever reason, use
an older version of Chrome. You can specify it's full rounded version number
here. Example: 87 for all versions of 87

Expand All @@ -149,22 +154,28 @@ def __init__(
setting it is not recommended, unless you know the implications and think
you might need it.
"""
patcher = Patcher(executable_path=executable_path, force=patcher_force_close, version_main=version_main)

patcher = Patcher(
executable_path=executable_path,
force=patcher_force_close,
version_main=version_main,
)
patcher.auto()

if not options:
options = ChromeOptions()

try:
if options.session and options.session is not None:
if hasattr(options, "_session") and options._session is not None:

# prevent reuse of options,
# as it just appends arguments, not replace them
# you'll get conflicts starting chrome
raise RuntimeError("you cannot reuse the ChromeOptions object")
except AttributeError:
pass

options.session = self
options._session = self

debug_port = selenium.webdriver.common.service.utils.free_port()
debug_host = "127.0.0.1"
Expand Down Expand Up @@ -250,9 +261,9 @@ def __init__(
options.add_argument("--window-size=1920,1080")
options.add_argument("--start-maximized")
options.add_argument("--no-sandbox")
# fixes "could not connect to chrome" error when running
# fixes "could not connect to chrome" error when running
# on linux using privileged user like root (which i don't recommend)

options.add_argument(
"--log-level=%d" % log_level
or divmod(logging.getLogger().getEffectiveLevel(), 10)[0]
Expand Down Expand Up @@ -280,15 +291,16 @@ def __init__(
if not desired_capabilities:
desired_capabilities = options.to_capabilities()


self.browser = subprocess.Popen(
[options.binary_location, *options.arguments],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True,
close_fds=IS_POSIX,
)

super().__init__(
super(Chrome, self).__init__(
executable_path=patcher.executable_path,
port=port,
options=options,
Expand Down Expand Up @@ -320,10 +332,22 @@ def __init__(
reactor.start()
self.reactor = reactor


if options.headless:
self._configure_headless()

orig_get = self.get

# def get_wrapped(*args, **kwargs):

# self.execute_cdp_cmd(
# "Network.setExtraHTTPHeaders",
# {"headers": {"dnt": "1", "cache-control": "no-cache"}},
# )
#
# return orig_get(*args, **kwargs)
#
# self.get = get_wrapped

def _configure_headless(self):

orig_get = self.get
Expand Down Expand Up @@ -508,12 +532,12 @@ def add_cdp_listener(self, event_name, callback):
self.reactor.add_event_handler(event_name, callback)
return self.reactor.handlers
return False

def clear_cdp_listeners(self):
if self.reactor and isinstance(self.reactor, Reactor):
self.reactor.handlers.clear()

def tab_new(self, url:str):
def tab_new(self, url: str):
"""
this opens a url in a new tab.
apparently, that passes all tests directly!
Expand All @@ -526,17 +550,18 @@ def tab_new(self, url:str):
-------

"""
if not hasattr(self, 'cdp'):
if not hasattr(self, "cdp"):
from .cdp import CDP

self.cdp = CDP(self.options)
self.cdp.tab_new(url)

def reconnect(self):
def reconnect(self, timeout=0.1):
try:
self.service.stop()
except Exception as e:
logger.debug(e)

time.sleep(timeout)
try:
self.service.start()
except Exception as e:
Expand All @@ -550,31 +575,33 @@ def reconnect(self):
def start_session(self, capabilities=None, browser_profile=None):
if not capabilities:
capabilities = self.options.to_capabilities()
super().start_session(capabilities, browser_profile)
super(Chrome, self).start_session(capabilities, browser_profile)


def quit(self):
logger.debug("closing webdriver")
self.service.process.kill()
try:
if self.reactor and isinstance(self.reactor, Reactor):
self.reactor.event.set()
super().quit()

except Exception: # noqa
pass
try:
logger.debug("killing browser")
self.browser.kill()
self.browser.terminate()
self.browser.wait(1)

except TimeoutError as e:
logger.debug(e, exc_info=True)
except Exception: # noqa
pass

if hasattr(self, 'keep_user_data_dir') \
and not self.keep_user_data_dir \
or self.keep_user_data_dir is False:
for _ in range(3):
if (
hasattr(self, "keep_user_data_dir")
and hasattr(self, "user_data_dir")
and not self.keep_user_data_dir
):
for _ in range(5):
try:
logger.debug("removing profile : %s" % self.user_data_dir)
shutil.rmtree(self.user_data_dir, ignore_errors=False)
Expand All @@ -585,15 +612,16 @@ def quit(self):
"permission error. files are still in use/locked. retying..."
)
except (RuntimeError, OSError) as e:
logger.debug(
"%s retying..." % e
)
logger.debug("%s retying..." % e)
else:
break
time.sleep(.25)
time.sleep(0.1)

def __del__(self):
logger.debug("Chrome.__del__")
try:
self.service.process.kill()
except:
pass
self.quit()

def __enter__(self):
Expand All @@ -608,23 +636,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
def __hash__(self):
return hash(self.options.debugger_address)

def find_elements_by_text(self, text: str):
for elem in self.find_elements_by_css_selector("*"):
try:
if text.lower() in elem.text.lower():
yield elem
except Exception as e:
logger.debug("find_elements_by_text: %s" % e)

def find_element_by_text(self, text: str, selector=None):
if not selector:
selector = "*"
for elem in self.find_elements_by_css_selector(selector):
try:
if text.lower() in elem.text.lower():
return elem
except Exception as e:
logger.debug("find_elements_by_text: {}".format(e))


def find_chrome_executable():
Expand Down