diff --git a/DrissionPage/chromium_base.py b/DrissionPage/chromium_base.py index b20a277..212708d 100644 --- a/DrissionPage/chromium_base.py +++ b/DrissionPage/chromium_base.py @@ -931,6 +931,11 @@ class ChromiumBaseSetter(object): """返回用于设置页面加载策略的对象""" return PageLoadStrategy(self._page) + @property + def scroll(self): + """返回用于设置页面滚动设置的对象""" + return PageScrollSetter(self._page.scroll) + def timeouts(self, implicit=None, page_load=None, script=None): """设置超时时间,单位为秒 :param implicit: 查找元素超时时间 @@ -1089,16 +1094,30 @@ class ChromiumPageScroll(ChromiumScroll): self.t1 = 'window' self.t2 = 'document.documentElement' - def to_see(self, loc_or_ele): + def to_see(self, loc_or_ele, center=False): """滚动页面直到元素可见 :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串 + :param center: 是否尽量滚动到页面正中 :return: None """ ele = self._driver._ele(loc_or_ele) - ele.run_js('this.scrollIntoView({behavior: "auto", block: "nearest", inline: "nearest"});') + self._to_see(ele, center) + def _to_see(self, ele, center): + """执行滚动页面直到元素可见 + :param ele: 元素对象 + :param center: 是否尽量滚动到页面正中 + :return: None + """ + if center: + ele.run_js('this.scrollIntoViewIfNeeded();') + self._wait_scrolled() + return + + ele.run_js('this.scrollIntoViewIfNeeded(false);') if ele.states.is_covered: - ele.run_js('this.scrollIntoView({behavior: "auto", block: "center", inline: "center"});') + ele.run_js('this.scrollIntoViewIfNeeded();') + self._wait_scrolled() class Timeout(object): @@ -1149,3 +1168,27 @@ class PageLoadStrategy(object): def none(self): """设置页面加载策略为none""" self._page._page_load_strategy = 'none' + + +class PageScrollSetter(object): + def __init__(self, scroll): + self._scroll = scroll + + def wait_complete(self, on_off=True): + """设置滚动命令后是否等待完成 + :param on_off: 开或关 + :return: None + """ + if not isinstance(on_off, bool): + raise TypeError('on_off必须为bool。') + self._scroll._wait_complete = on_off + + def smooth(self, on_off=True): + """设置页面滚动是否平滑滚动 + :param on_off: 开或关 + :return: None + """ + if not isinstance(on_off, bool): + raise TypeError('on_off必须为bool。') + b = 'smooth' if on_off else 'auto' + self._scroll._driver.run_js(f'document.documentElement.style.setProperty("scroll-behavior","{b}");') diff --git a/DrissionPage/chromium_base.pyi b/DrissionPage/chromium_base.pyi index d6341b5..db85fd7 100644 --- a/DrissionPage/chromium_base.pyi +++ b/DrissionPage/chromium_base.pyi @@ -241,7 +241,9 @@ class ChromiumBaseWaiter(object): class ChromiumPageScroll(ChromiumScroll): def __init__(self, page: ChromiumBase): ... - def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement]) -> None: ... + def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: bool = False) -> None: ... + + def _to_see(self, ele: ChromiumElement, center: bool) -> None: ... class ChromiumBaseSetter(object): @@ -251,6 +253,9 @@ class ChromiumBaseSetter(object): @property def load_strategy(self) -> PageLoadStrategy: ... + @property + def scroll(self) -> PageScrollSetter: ... + def timeouts(self, implicit: float = None, page_load: float = None, script: float = None): ... def user_agent(self, ua: str, platform: str = None) -> None: ... @@ -286,3 +291,12 @@ class PageLoadStrategy(object): def eager(self) -> None: ... def none(self) -> None: ... + + +class PageScrollSetter(object): + def __init__(self, scroll: ChromiumPageScroll): + self._scroll: ChromiumPageScroll = ... + + def wait_complete(self, on_off: bool = True): ... + + def smooth(self, on_off: bool = True): ... diff --git a/DrissionPage/chromium_element.py b/DrissionPage/chromium_element.py index 1743eb2..ffdde3c 100644 --- a/DrissionPage/chromium_element.py +++ b/DrissionPage/chromium_element.py @@ -172,7 +172,7 @@ class ChromiumElement(DrissionElement): def scroll(self): """用于滚动滚动条的对象""" if self._scroll is None: - self._scroll = ChromiumScroll(self) + self._scroll = ChromiumElementScroll(self) return self._scroll @property @@ -429,6 +429,7 @@ class ChromiumElement(DrissionElement): while perf_counter() < end_time: try: result = self.page.run_cdp('Page.getResourceContent', frameId=frame, url=src) + break except CallMethodError: pass sleep(.1) @@ -477,7 +478,7 @@ class ChromiumElement(DrissionElement): while not self.run_js(js) and perf_counter() < end_time: sleep(.1) - self.page.scroll.to_see(self) + self.scroll.to_see(center=True) sleep(1) left, top = self.location width, height = self.size @@ -1648,7 +1649,7 @@ class Click(object): """ if not by_js: try: - self._ele.page.scroll.to_see(self._ele) + self._ele.scroll.to_see() if self._ele.states.is_in_viewport and not self._ele.states.is_covered: client_x, client_y = self._ele.locations.viewport_click_point self._click(client_x, client_y) @@ -1711,10 +1712,12 @@ class ChromiumScroll(object): """ self._driver = ele self.t1 = self.t2 = 'this' + self._wait_complete = False def _run_js(self, js): js = js.format(self.t1, self.t2, self.t2) self._driver.run_js(js) + self._wait_scrolled() def to_top(self): """滚动到顶端,水平位置不变""" @@ -1774,6 +1777,36 @@ class ChromiumScroll(object): """ self._run_js(f'{{}}.scrollBy({pixel}, 0);') + def _wait_scrolled(self): + if not self._wait_complete: + return + + page = self._driver.page if isinstance(self._driver, ChromiumElement) else self._driver + r = page.run_cdp('Page.getLayoutMetrics') + x = r['layoutViewport']['pageX'] + y = r['layoutViewport']['pageY'] + + while True: + sleep(.1) + r = page.run_cdp('Page.getLayoutMetrics') + x1 = r['layoutViewport']['pageX'] + y1 = r['layoutViewport']['pageY'] + + if x == x1 and y == y1: + break + + x = x1 + y = y1 + + +class ChromiumElementScroll(ChromiumScroll): + def to_see(self, center=False): + """滚动页面直到元素可见 + :param center: 是否尽量滚动到页面正中 + :return: None + """ + self._driver.page.scroll.to_see(self._driver, center=center) + class ChromiumSelect(object): """ChromiumSelect 类专门用于处理 d 模式下 select 标签""" diff --git a/DrissionPage/chromium_element.pyi b/DrissionPage/chromium_element.pyi index c244539..e093617 100644 --- a/DrissionPage/chromium_element.pyi +++ b/DrissionPage/chromium_element.pyi @@ -27,7 +27,7 @@ class ChromiumElement(DrissionElement): self._backend_id: str = ... self._doc_id: str = ... self._ids: ChromiumElementIds = ... - self._scroll: ChromiumScroll = ... + self._scroll: ChromiumElementScroll = ... self._click: Click = ... self._select: ChromiumSelect = ... self._wait: ChromiumElementWaiter = ... @@ -89,7 +89,7 @@ class ChromiumElement(DrissionElement): def sr(self) -> Union[None, ChromiumShadowRoot]: ... @property - def scroll(self) -> ChromiumScroll: ... + def scroll(self) -> ChromiumElementScroll: ... @property def click(self) -> Click: ... @@ -163,7 +163,7 @@ class ChromiumElement(DrissionElement): def _find_elements(self, loc_or_str: Union[Tuple[str, str], str], timeout: float = None, single: bool = True, relative: bool = False, raise_err: bool = False) \ -> Union[ChromiumElement, ChromiumFrame, str, NoneElement, - List[Union[ChromiumElement, ChromiumFrame, str]]]: ... + List[Union[ChromiumElement, ChromiumFrame, str]]]: ... def style(self, style: str, pseudo_ele: str = '') -> str: ... @@ -453,6 +453,7 @@ class ChromiumScroll(object): self.t1: str = ... self.t2: str = ... self._driver: Union[ChromiumPage, ChromiumElement, ChromiumFrame] = ... + self._wait_complete: bool = ... def _run_js(self, js: str): ... @@ -476,6 +477,13 @@ class ChromiumScroll(object): def right(self, pixel: int = 300) -> None: ... + def _wait_scrolled(self) -> None: ... + + +class ChromiumElementScroll(ChromiumScroll): + + def to_see(self, center: bool = False) -> None: ... + class ChromiumSelect(object): def __init__(self, ele: ChromiumElement): diff --git a/DrissionPage/chromium_frame.py b/DrissionPage/chromium_frame.py index 80ca28e..024373c 100644 --- a/DrissionPage/chromium_frame.py +++ b/DrissionPage/chromium_frame.py @@ -428,48 +428,55 @@ class ChromiumFrame(ChromiumBase): return super().get_screenshot(path=path, as_bytes=as_bytes, as_base64=as_base64, full_page=full_page, left_top=left_top, right_bottom=right_bottom) - else: - if as_bytes: - if as_bytes is True: - pic_type = 'png' - else: - if as_bytes not in ('jpg', 'jpeg', 'png', 'webp'): - raise ValueError("只能接收 'jpg', 'jpeg', 'png', 'webp' 四种格式。") - pic_type = 'jpeg' if as_bytes == 'jpg' else as_bytes - - elif as_base64: - if as_base64 is True: - pic_type = 'png' - else: - if as_base64 not in ('jpg', 'jpeg', 'png', 'webp'): - raise ValueError("只能接收 'jpg', 'jpeg', 'png', 'webp' 四种格式。") - pic_type = 'jpeg' if as_base64 == 'jpg' else as_base64 - + if as_bytes: + if as_bytes is True: + pic_type = 'png' else: - if not path: - path = f'{self.title}.jpg' - path = get_usable_path(path) - pic_type = path.suffix.lower() - if pic_type not in ('.jpg', '.jpeg', '.png', '.webp'): - raise TypeError(f'不支持的文件格式:{pic_type}。') - pic_type = 'jpeg' if pic_type == '.jpg' else pic_type[1:] + if as_bytes not in ('jpg', 'jpeg', 'png', 'webp'): + raise ValueError("只能接收 'jpg', 'jpeg', 'png', 'webp' 四种格式。") + pic_type = 'jpeg' if as_bytes == 'jpg' else as_bytes - self.scroll.to_see(ele) - cx, cy = ele.locations.viewport_location - w, h = ele.size - img_data = f'data:image/{pic_type};base64,{self.frame_ele.get_screenshot(as_base64=True)}' - body = self.page('t:body') - first_child = body('c::first-child') - js = f''' - haskell = document.createElement('img'); - haskell.src = "{img_data}"; - arguments[0].insertBefore(haskell, this); - return haskell;''' - new_ele = first_child.run_js(js, body) - r = self.page.get_screenshot(path=path, as_bytes=as_bytes, as_base64=as_base64, - left_top=(cx, cy), right_bottom=(cx + w, cy + h)) - self.page.remove_ele(new_ele) - return r + elif as_base64: + if as_base64 is True: + pic_type = 'png' + else: + if as_base64 not in ('jpg', 'jpeg', 'png', 'webp'): + raise ValueError("只能接收 'jpg', 'jpeg', 'png', 'webp' 四种格式。") + pic_type = 'jpeg' if as_base64 == 'jpg' else as_base64 + + else: + if not path: + path = f'{self.title}.jpg' + path = get_usable_path(path) + pic_type = path.suffix.lower() + if pic_type not in ('.jpg', '.jpeg', '.png', '.webp'): + raise TypeError(f'不支持的文件格式:{pic_type}。') + pic_type = 'jpeg' if pic_type == '.jpg' else pic_type[1:] + + self.frame_ele.scroll.to_see(center=True) + self.scroll.to_see(ele, center=True) + cx, cy = ele.locations.viewport_location + w, h = ele.size + img_data = f'data:image/{pic_type};base64,{self.frame_ele.get_screenshot(as_base64=True)}' + body = self.page('t:body') + first_child = body('c::first-child') + if not isinstance(first_child, ChromiumElement): + first_child = first_child.frame_ele + js = f''' + img = document.createElement('img'); + img.src = "{img_data}"; + img.style.setProperty("z-index",9999999); + img.style.setProperty("position","fixed"); + arguments[0].insertBefore(img, this); + return img;''' + new_ele = first_child.run_js(js, body) + new_ele.scroll.to_see(True) + top = int(self.frame_ele.style('border-top').split('px')[0]) + left = int(self.frame_ele.style('border-left').split('px')[0]) + r = self.page.get_screenshot(path=path, as_bytes=as_bytes, as_base64=as_base64, + left_top=(cx + left, cy + top), right_bottom=(cx + w + left, cy + h + top)) + self.page.remove_ele(new_ele) + return r def _find_elements(self, loc_or_ele, timeout=None, single=True, relative=False, raise_err=None): """在frame内查找单个元素 @@ -621,14 +628,16 @@ class ChromiumFrameScroll(ChromiumPageScroll): """ self._driver = frame.doc_ele self.t1 = self.t2 = 'this.documentElement' + self._wait_complete = False - def to_see(self, loc_or_ele): + def to_see(self, loc_or_ele, center=False): """滚动页面直到元素可见 :param loc_or_ele: 元素的定位信息,可以是loc元组,或查询字符串 + :param center: 是否尽量滚动到页面正中 :return: None """ ele = loc_or_ele if isinstance(loc_or_ele, ChromiumElement) else self._driver._ele(loc_or_ele) - ele.run_js('this.scrollIntoView({behavior: "auto", block: "center", inline: "center"});') + self._to_see(ele, center) class ChromiumFrameSetter(ChromiumBaseSetter): diff --git a/DrissionPage/chromium_frame.pyi b/DrissionPage/chromium_frame.pyi index 080571d..3ef2abe 100644 --- a/DrissionPage/chromium_frame.pyi +++ b/DrissionPage/chromium_frame.pyi @@ -196,6 +196,8 @@ class ChromiumFrameIds(object): class ChromiumFrameScroll(ChromiumPageScroll): def __init__(self, frame: ChromiumFrame) -> None: ... + def to_see(self, loc_or_ele: Union[str, tuple, ChromiumElement], center: bool = False) -> None: ... + class ChromiumFrameSetter(ChromiumBaseSetter): _page: ChromiumFrame = ...