From 72c143e77d100a122325a0070621376bae07a32e Mon Sep 17 00:00:00 2001 From: g1879 Date: Fri, 15 Apr 2022 15:02:03 +0800 Subject: [PATCH] =?UTF-8?q?2.5.8=20=E4=BC=98=E5=8C=96s=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E8=BF=9E=E6=8E=A5=E7=9A=84=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=9Bini=E6=96=87=E4=BB=B6=E6=B7=BB=E5=8A=A0=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E5=BC=B9=E7=AA=97=E5=8F=82=E8=B5=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DrissionPage/base.py | 13 ++ DrissionPage/configs.ini | 2 +- DrissionPage/driver_page.py | 22 +- DrissionPage/mix_page.py | 2 +- DrissionPage/session_page.py | 35 ++- docs/使用方法/下载文件.md | 436 ++++++++++++++++++----------------- docs/版本历史.md | 4 + 7 files changed, 259 insertions(+), 255 deletions(-) diff --git a/DrissionPage/base.py b/DrissionPage/base.py index 35026e9..2f5e497 100644 --- a/DrissionPage/base.py +++ b/DrissionPage/base.py @@ -7,6 +7,7 @@ from abc import abstractmethod from re import sub from typing import Union, Tuple, List +from urllib.parse import quote from lxml.html import HtmlElement from selenium.webdriver.remote.webelement import WebElement @@ -330,6 +331,18 @@ class BasePage(BaseParser): """返回当前访问的url有效性""" return self._url_available + def _before_connect(self, url: str, retry: int, interval: float) -> tuple: + """连接前的准备 \n + :param url: 要访问的url + :param retry: 重试次数 + :param interval: 重试间隔 + :return: 重试次数和间隔组成的tuple + """ + self._url = quote(url, safe='/:&?=%;#@+!') + retry = retry if retry is not None else self.retry_times + interval = interval if interval is not None else self.retry_interval + return retry, interval + # ----------------以下属性或方法由后代实现---------------- @property def url(self): diff --git a/DrissionPage/configs.ini b/DrissionPage/configs.ini index 9a7467e..75d19dc 100644 --- a/DrissionPage/configs.ini +++ b/DrissionPage/configs.ini @@ -5,7 +5,7 @@ tmp_path = [chrome_options] debugger_address = 127.0.0.1:9222 binary_location = -arguments = ['--no-sandbox', '--disable-gpu', '--ignore-certificate-errors', '--disable-infobars'] +arguments = ['--no-sandbox', '--disable-gpu', '--ignore-certificate-errors', '--disable-infobars', '--disable-popup-blocking'] extensions = [] experimental_options = {'prefs': {'profile.default_content_settings.popups': 0, 'profile.default_content_setting_values': {'notifications': 2}, 'plugins.plugins_list': [{'enabled': False, 'name': 'Chrome PDF Viewer'}]}, 'useAutomationExtension': False, 'excludeSwitches': ['enable-automation']} timeouts = {'implicit': 10.0, 'pageLoad': 30.0, 'script': 30.0} diff --git a/DrissionPage/driver_page.py b/DrissionPage/driver_page.py index 6110a1a..8f2f46d 100644 --- a/DrissionPage/driver_page.py +++ b/DrissionPage/driver_page.py @@ -9,7 +9,6 @@ from os import sep from pathlib import Path from time import sleep, perf_counter from typing import Union, List, Any, Tuple -from urllib.parse import quote from selenium.common.exceptions import NoAlertPresentException from selenium.webdriver.chrome.webdriver import WebDriver @@ -75,15 +74,8 @@ class DriverPage(BasePage): :param interval: 重试间隔(秒) :return: 目标url是否可用,返回None表示不确定 """ - to_url = quote(url, safe='/:&?=%;#@+!') - retry = retry if retry is not None else self.retry_times - interval = interval if interval is not None else self.retry_interval - - if not url: - raise ValueError('没有传入url。') - - self._url = to_url - self._url_available = self._try_to_connect(to_url, times=retry, interval=interval, show_errmsg=show_errmsg) + retry, interval = self._before_connect(url, retry, interval) + self._url_available = self._d_connect(self._url, times=retry, interval=interval, show_errmsg=show_errmsg) try: self._driver.execute_script('Object.defineProperty(navigator,"webdriver",{get:() => undefined,});') @@ -173,11 +165,11 @@ class DriverPage(BasePage): self._timeout = second self._wait_object = None - def _try_to_connect(self, - to_url: str, - times: int = 0, - interval: float = 1, - show_errmsg: bool = False) -> Union[bool, None]: + def _d_connect(self, + to_url: str, + times: int = 0, + interval: float = 1, + show_errmsg: bool = False) -> Union[bool, None]: """尝试连接,重试若干次 \n :param to_url: 要访问的url :param times: 重试次数 diff --git a/DrissionPage/mix_page.py b/DrissionPage/mix_page.py index 7b6efc7..4d79369 100644 --- a/DrissionPage/mix_page.py +++ b/DrissionPage/mix_page.py @@ -315,7 +315,7 @@ class MixPage(SessionPage, DriverPage, BasePage): # 使用requests访问url并判断可用性 if by_requests: self.cookies_to_session() - r = self._make_response(self.url)[0] + r = self._make_response(self.url, retry=0)[0] return r.ok if r else False def close_driver(self) -> None: diff --git a/DrissionPage/session_page.py b/DrissionPage/session_page.py index 0953a37..1e6dfa4 100644 --- a/DrissionPage/session_page.py +++ b/DrissionPage/session_page.py @@ -7,7 +7,7 @@ from re import search from time import sleep from typing import Union, List, Tuple -from urllib.parse import urlparse, quote +from urllib.parse import urlparse from requests import Session, Response from requests.structures import CaseInsensitiveDict @@ -70,7 +70,7 @@ class SessionPage(BasePage): :param kwargs: 连接参数 :return: url是否可用 """ - return self._connect(url, 'get', None, show_errmsg, retry, interval, **kwargs) + return self._s_connect(url, 'get', None, show_errmsg, retry, interval, **kwargs) def ele(self, loc_or_ele: Union[Tuple[str, str], str, SessionElement], @@ -173,16 +173,16 @@ class SessionPage(BasePage): :param kwargs: 连接参数 :return: url是否可用 """ - return self._connect(url, 'post', data, show_errmsg, retry, interval, **kwargs) + return self._s_connect(url, 'post', data, show_errmsg, retry, interval, **kwargs) - def _connect(self, - url: str, - mode: str, - data: Union[dict, str] = None, - show_errmsg: bool = False, - retry: int = None, - interval: float = None, - **kwargs) -> bool: + def _s_connect(self, + url: str, + mode: str, + data: Union[dict, str] = None, + show_errmsg: bool = False, + retry: int = None, + interval: float = None, + **kwargs) -> bool: """执行get或post连接 \n :param url: 目标url :param mode: 'get' 或 'post' @@ -193,15 +193,8 @@ class SessionPage(BasePage): :param kwargs: 连接参数 :return: url是否可用 """ - to_url = quote(url, safe='/:&?=%;#@+!') - retry = retry if retry is not None else self.retry_times - interval = interval if interval is not None else self.retry_interval - - if not url: - raise ValueError('没有传入url。') - - self._url = to_url - self._response, info = self._make_response(to_url, mode, data, retry, interval, show_errmsg, **kwargs) + retry, interval = self._before_connect(url, retry, interval) + self._response, info = self._make_response(self._url, mode, data, retry, interval, show_errmsg, **kwargs) if self._response is None: self._url_available = False @@ -267,7 +260,7 @@ class SessionPage(BasePage): except Exception as e: err = e - # if r and (r.content != b'' or r.status_code in (403, 404)): + # if r and r.status_code in (403, 404): # break if i < retry: diff --git a/docs/使用方法/下载文件.md b/docs/使用方法/下载文件.md index 93a4246..d2b9386 100644 --- a/docs/使用方法/下载文件.md +++ b/docs/使用方法/下载文件.md @@ -1,217 +1,219 @@ -selenium 缺乏对浏览器下载文件的有效管理,难以进行检测下载状态、重命名、失败管理。 -使用 requests 下载文件能较好实现以上功能,但代码较为繁琐。 -因此 DrissionPage 提供了高效可靠的下载工具,整合了两者优点,可从 selenium 获取登录信息,用 requests 进行下载。弥补了 selenium 的不足,使下载简洁高效。 - -?> 为了增强灵活性,该工具现在独立打包成一个库,叫 DownloadKit,详细用法见:[DownloadKit](https://gitee.com/g1879/DownloadKit) - -# 功能 - -- 支持多线程同时下载多个文件 -- 自动管理下载列表,只要用`add()`方法添加任务,该工具会自动在多个线程中调度任务,无须等待 -- 支持 d 模式下用 requests 下载文件 -- 可指定下载路径,若路径不存在会自动创建文件夹 -- 重命名文件,可不填写扩展名,程序自动补充 -- 存在同名文件时,可选择重命名、覆盖、跳过等处理方式 -- 显示下载进度 -- 支持 post 方式 -- 支持自定义连接参数 -- 任务失败自动重试 - -# 单线程下载方式 - -`MixPage`对象的`download`属性是一个`DownloadKit`对象,为尽量与旧版兼容,该属性可直接调用。如`page.download(url, path)` -?> 使用这个方式时效果与旧版一致,下载一个文件时会阻塞程序,因此更加建议用后文讲述的多线程方法。 - -参数: - -- file_ur:文件 url -- goal_path:存放路径,填写到文件夹,不填写文件名 -- rename:重命名文件,可不写扩展名,不输入则用网络文件原名 -- file_exists:若存在同名文件,可选择`'rename'`,`'overwrite'`,`'skip'`方式处理,若选择重命名,会在文件名后面添加序号 -- post_data:post 方式的数据,这个参数不为`None`时自动转成 post 方式 -- show_msg:是否显示下载信息和进度 -- **kwargs;连接参数,与 requests 的一致 - -返回:下载是否成功(`bool`)和状态信息(成功时信息为文件路径)的元组,跳过时第一位返回 None - -```python -from DrissionPage import MixPage - -page = MixPage() -# 文件 url -url = 'https://www.baidu.com/img/flexible/logo/pc/result.png' -# 存放路径 -save_path = r'C:\download' - -# 重命名为img.png,存在重名时自动在文件名末尾加上序号,显示下载进度 -res = page.download(url, save_path, 'img', 'rename', show_msg=True) -# 打印结果 -print(res) -``` - -显示: - -```shell -url:https://www.baidu.com/img/flexible/logo/pc/result.png -文件名:img.png -目标路径:C:\download -100% 下载完成 C:\download\img.png - -(True, 'C:\\download\\img.png') -``` - -# 多线程并发下载方式 - -你可以往`DownloadKit`对象添加个数不限的下载任务,它会自动调配线程去完成这些任务。 -默认为 10 个线程。 - -## 添加任务 - -使用`add()`方法添加任务 - -参数: - -- file_ur:文件 url -- goal_path:存放路径,填写到文件夹,不填写文件名 -- session:可指定使用的`Session`对象,默认使用`MixPage`内置的`Session`对象 -- rename:重命名文件,可不写扩展名,不输入则用网络文件原名 -- file_exists:若存在同名文件,可选择`'rename'`,`'overwrite'`,`'skip'`方式处理,若选择重命名,会在文件名后面添加序号 -- post_data:post 方式的数据,这个参数不为`None`时自动转成 post 方式 -- **kwargs;连接参数,与 requests 的一致 - -返回:任务对象,可通过任务对象查看任务状态和结果 - -```python -from DrissionPage import MixPage - -page = MixPage('s') -# 文件 url -url = 'https://www.baidu.com/img/flexible/logo/pc/result.png' -# 存放路径 -save_path = r'C:\download' - -# 返回一个任务对象 -mission = page.download.add(url, save_path) - -# 通过任务对象查看状态 -print(mission.rate, mission.info) -``` - -输出: - -```shell -90% '下载中' -``` - -## 实时查看所有线程进度 - -多线程方式不会实时显示下载进度,可用`show()`方法把下载进度打印出来。 - -!> **注意:**
若使用 pyCharm 运行,须在运行配置里勾选“模拟输出控制台中的终端”才能正常显示输出。 - -参数: - -- asyn:是否异步进行 -- keep:任务全部完成后是否保持显示 - -返回:None - -```python -url = 'https://example.com/file/abc.zip' -mission = page.download.add(url, r'.\files') -page.download.show() -``` - -输出: - -```shell -等待任务数:0 -线程0:97.41% D:\files\abc.zip -线程1:空闲 -线程2:空闲 -..... -``` - -?> **Tips:**
`keep`参数为`True`时,即使所有任务都已结束仍会一直打印进度,可以按`enter`结束。 - - - -## 等待任务结束 - -有时须要等待任务结束,以便获取结果,可用`wait()`方法。 当传入任务时,等待该任务结束并返回结果。不传入参数时等待所有任务结束,与`show()`方法一致。 - -参数: - -- mission:任务对象或任务`id`,为`None`时等待所有任务结束 -- show:是否显示进度 - -返回: - -- 指定任务时,返回任务结果和信息组成的两位 tuple。`True`表示成功,`False`表示失败,`None`表示跳过。 -- 不指定任务时,返回`None` - -!> **注意:**
若使用 pyCharm 运行,须在运行配置里勾选“模拟输出控制台中的终端”才能正常显示输出。 - -```python -url = 'https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png' -mission = page.download.add(url, save_path) -page.download.wait(mission) -``` - -输出: - -```shell -url:https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png -文件名:PCfb_5bf082d29588c07f842ccde3f97243ea_4.png -目标路径:D:\files -100% 下载完成 D:\files\PCfb_5bf082d29588c07f842ccde3f97243ea_4.png -``` - -## 获取某个任务结果 - -从`add()`方法返回的`Mission`对象,可查看该任务执行情况。 - -`Mission`对象属性: - -- id:任务 id -- file_name:要保存的文件名 -- path:要保存的路径 -- data:任务数据 -- state:任务状态,有`'waiting'`、`'running'`、`'done'`三种 -- rate:任务进度,以百分比显示 -- info:任务信息,成功会返回文件绝对路径,失败会显示原因 -- result:任务结果,`True`表示成功,`False`表示失败,`None`表示跳过 - -```python -mission = page.download.add(url) -print(mission.state) -``` - -输出: - -```python -running -``` - -## 下载设置 - -可使用以下属性进行配置: - -```python -# 设置线程数,只能在没有任务在运行的时候进行 -page.download.size = 20 - -# 设置保存路径,设置后每个任务会使用这个路径,也可添加任务时单独设置 -page.download.goal_path = r'D:\tmp' - -# 设置重试次数,初始为继承 page 的 retry_times 属性 -page.download.retry = 5 - -# 设置失败重试间隔,初始为继承 page 的 retry_interval 属性 -page.download.interval = 2 - -# 设置存在文件名冲突时的处理方式,可选 'skip', 'overwrite', 'rename' -page.download.file_exists = 'skip' -``` - -?> **Tips:**
重试次数和间隔在初始化时继承页面对象的`retry_times`和`retry_interval`属性,可用上面例子的方法对下载的重试次数和间隔进行设置,设置后不会影响页面对象的设置。 +selenium 缺乏对浏览器下载文件的有效管理,难以进行检测下载状态、重命名、失败管理。 +使用 requests 下载文件能较好实现以上功能,但代码较为繁琐。 +因此 DrissionPage 提供了高效可靠的下载工具,整合了两者优点,可从 selenium 获取登录信息,用 requests 进行下载。弥补了 selenium 的不足,使下载简洁高效。 + +?> 为了增强灵活性,该工具现在独立打包成一个库,叫 DownloadKit,详细用法见:[DownloadKit](https://gitee.com/g1879/DownloadKit) + +# 功能 + +- 支持多线程同时下载多个文件 +- 大文件自动分块使用多线程下载 +- 自动任务调度,简易的任务添加方式 +- 支持 d 模式下用 requests 下载文件 +- 自动创建目标路径 +- 自动去除路径中的非法字符 +- 下载时支持文件重命名 +- 自动处理文件名冲突 +- 显示下载进度 +- 支持 post 方式 +- 支持自定义连接参数 +- 任务失败自动重试 + +# 单线程下载方式 + +`MixPage`对象的`download`属性是一个`DownloadKit`对象,为尽量与旧版兼容,该属性可直接调用。如`page.download(url, save_path)` +?> 使用这个方式时效果与旧版一致,下载一个文件时会阻塞程序,因此更加建议用后文讲述的多线程方法。 + +参数: + +- file_ur:文件 url +- goal_path:存放路径,填写到文件夹,不填写文件名 +- rename:重命名文件,可不写扩展名,不输入则用网络文件原名 +- file_exists:若存在同名文件,可选择`'rename'`,`'overwrite'`,`'skip'`方式处理,若选择重命名,会在文件名后面添加序号 +- post_data:post 方式的数据,这个参数不为`None`时自动转成 post 方式 +- show_msg:是否显示下载信息和进度 +- **kwargs:连接参数,与 requests 的一致 + +返回:下载是否成功和状态信息的元组。 + +```python +from DrissionPage import MixPage + +page = MixPage() +# 文件 url +url = 'https://www.baidu.com/img/flexible/logo/pc/result.png' +# 存放路径 +save_path = r'C:\download' + +# 重命名为img.png,存在重名时自动在文件名末尾加上序号,显示下载进度 +res = page.download(url, save_path, 'img', 'rename', show_msg=True) +# 打印结果 +print(res) +``` + +显示: + +```shell +url:https://www.baidu.com/img/flexible/logo/pc/result.png +文件名:img.png +目标路径:C:\download +100% 下载完成 C:\download\img.png + +(True, 'C:\\download\\img.png') +``` + +# 多线程并发下载方式 + +你可以往`DownloadKit`对象添加个数不限的下载任务,它会自动调配线程去完成这些任务。 +默认为 10 个线程。 + +## 添加任务 + +使用`add()`方法添加任务 + +参数: + +- file_ur:文件 url +- goal_path:存放路径,填写到文件夹,不填写文件名 +- session:可指定使用的`Session`对象,默认使用`MixPage`内置的`Session`对象 +- rename:重命名文件,可不写扩展名,不输入则用网络文件原名 +- file_exists:若存在同名文件,可选择`'rename'`,`'overwrite'`,`'skip'`方式处理,若选择重命名,会在文件名后面添加序号 +- post_data:post 方式的数据,这个参数不为`None`时自动转成 post 方式 +- **kwargs:连接参数,与 requests 的一致 + +返回:任务对象,可通过任务对象查看任务状态和结果 + +```python +from DrissionPage import MixPage + +page = MixPage('s') +# 文件 url +url = 'https://www.baidu.com/img/flexible/logo/pc/result.png' +# 存放路径 +save_path = r'C:\download' + +# 返回一个任务对象 +mission = page.download.add(url, save_path) + +# 通过任务对象查看状态 +print(mission.rate, mission.info) +``` + +输出: + +```shell +90% '下载中' +``` + +## 实时查看所有线程进度 + +多线程方式不会实时显示下载进度,可用`show()`方法把下载进度打印出来。 + +!> **注意:**
若使用 pyCharm 运行,须在运行配置里勾选“模拟输出控制台中的终端”才能正常显示输出。 + +参数: + +- asyn:是否异步进行 +- keep:任务全部完成后是否保持显示 + +返回:None + +```python +url = 'https://example.com/file/abc.zip' +mission = page.download.add(url, r'.\files') +page.download.show() +``` + +输出: + +```shell +等待任务数:0 +线程0:97.41% D:\files\abc.zip +线程1:空闲 +线程2:空闲 +..... +``` + +?> **Tips:**
`keep`参数为`True`时,即使所有任务都已结束仍会一直打印进度,可以按`enter`结束。 + +## 等待任务结束 + +有时须要等待任务结束,以便获取结果,可用`wait()`方法。 当传入任务时,等待该任务结束并返回结果。不传入参数时等待所有任务结束,与`show()`方法一致。 + +参数: + +- mission:任务对象或任务`id`,为`None`时等待所有任务结束 +- show:是否显示进度 + +返回: + +- 指定任务时,返回任务结果和信息组成的两位 tuple。`True`表示成功,`False`表示失败,`None`表示跳过。 +- 不指定任务时,返回`None` + +!> **注意:**
若使用 pyCharm 运行,须在运行配置里勾选“模拟输出控制台中的终端”才能正常显示输出。 + +```python +url = 'https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png' +mission = page.download.add(url, save_path) +page.download.wait(mission) +``` + +输出: + +```shell +url:https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png +文件名:PCfb_5bf082d29588c07f842ccde3f97243ea_4.png +目标路径:D:\files +100% 下载完成 D:\files\PCfb_5bf082d29588c07f842ccde3f97243ea_4.png +``` + +## 获取某个任务结果 + +从`add()`方法返回的`Mission`对象,可查看该任务执行情况。 + +`Mission`对象属性: + +- id:任务 id +- file_name:要保存的文件名 +- path:要保存的路径 +- data:任务数据 +- state:任务状态,有`'waiting'`、`'running'`、`'done'`三种 +- rate:任务进度,以百分比显示 +- info:任务信息,成功会返回文件绝对路径,失败会显示原因 +- result:任务结果,`True`表示成功,`False`表示失败,`None`表示跳过 + +```python +mission = page.download.add(url) +print(mission.state) +``` + +输出: + +```python +running +``` + +## 下载设置 + +可使用以下属性进行配置: + +```python +# 设置线程数,只能在没有任务在运行的时候进行 +page.download.roads = 20 + +# 大文件分块大小,默认 20MB page.downloadd.block_size = '50M' + +# 设置保存路径,设置后每个任务会使用这个路径,也可添加任务时单独设置 +page.download.goal_path = r'D:\tmp' + +# 设置重试次数,初始为继承 page 的 retry_times 属性 +page.download.retry = 5 + +# 设置失败重试间隔,初始为继承 page 的 retry_interval 属性 +page.download.interval = 2 + +# 设置存在文件名冲突时的处理方式,可选 'skip', 'overwrite', 'rename' +page.download.file_exists = 'skip' +``` + +?> **Tips:**
重试次数和间隔在初始化时继承页面对象的`retry_times`和`retry_interval`属性,可用上面例子的方法对下载的重试次数和间隔进行设置,设置后不会影响页面对象的设置。 diff --git a/docs/版本历史.md b/docs/版本历史.md index 7a41714..833c98c 100644 --- a/docs/版本历史.md +++ b/docs/版本历史.md @@ -1,3 +1,7 @@ +# 2.5.8 + +- 优化 s 模式创建连接的逻辑 + # v2.5.7 - 列表元素`select()`、`deselect()`等方法添加`timeout`参数,可等待列表元素加载