目标
- 查询用户信息及容量
- 文件管理,支持创建/复制/转移/删除/重命名
- 文件查询
- 获取用户指定目录下的文档列表并下载到本地
实现逻辑
- 用户信息主要是用于确认登录的用户是否正确及是否还有容量存储;
- 用于快速操作文件,可以对指定的文件进行复制、移动、重命名和删除操作;
- 对于一些不清楚位置的文件进行快速的检索;
- 将一些需要的文件进行下载到本地,其中需要先从网盘获取到文件,通过查询文件信息获取文件的下载地址(该地址有效期只有8小时),通过下载地址将文件并发下载到本地;
代码实现
- 用于获取用户的基本信息,包括账号、头像地址、会员类型等
获取用户的网盘空间的使用情况,包括总空间大小,已用空间和剩余可用空间情况
# 用户信息 用户容量
def get_user_info(self):
api_instance = userinfo_api.UserinfoApi(self.client)
access_token = self._access_token # str |
checkexpire = 1 # 是否检查免费信息,0为不查,1为查,默认为0
checkfree = 1 # 是否检查过期信息,0为不查,1为查,默认为0
try:
userinfo = api_instance.xpannasuinfo(access_token)
# print(userinfo)
logging.info(f"用户名:{userinfo['baidu_name']}")
logging.info(f"授权状态:{userinfo['errmsg']}")
quota_info = api_instance.apiquota(access_token, checkexpire=checkexpire, checkfree=checkfree)
# print(quota_info)
logging.info(f"总容量为:{round(quota_info['total']/1024/1024/1024,2)}G")
logging.info(f"已使用容量为:{round(quota_info['used']/1024/1024/1024,2)}G")
return userinfo, quota_info
except openapi_client.ApiException as e:
print("获取用户信息失败: %s\n" % e)
- 用于对指定的文件进行复制、移动、重命名和删除操作
# 文件管理 创建 复制 转移 删除 重命名
# type copy move
# [{"path": "/test/123456.docx", "dest": "/test/abc", "newname": "11223.docx", "ondup": "fail"}]【copy / move示例】
# [{"path": "/test/123456.docx", "newname": "123.docx"}]【rename示例】
# ["/test/123456.docx"]【delete示例】
# ondup 全局ondup,遇到重复文件的处理策略, fail(默认,直接返回失败)、newcopy(重命名文件)、overwrite、skip
def file_operate(self, type, path, newname=None, dest=None, ondup='overwrite'):
api_instance = filemanager_api.FilemanagerApi(self.client)
access_token = self._access_token # str |
_async = 1 # int | async 0 同步,1 自适应,2 异步
try:
if dest is not None: # 复制/移动
file_list = json.dumps([{"path": path, "dest":dest, 'newname':newname, 'ondup':ondup }])
if type == 'copy':
copy_response = api_instance.filemanagercopy(access_token,_async,file_list)
if copy_response['errno'] == 0:
logging.info(f'{path}文件复制到{dest}/{newname}')
else:
logging.error(f'复制失败:{copy_response}')
elif type == 'move':
move_response = api_instance.filemanagermove(access_token, _async, file_list)
if move_response['errno'] == 0:
logging.info(f'{path}文件移动到{dest}/{newname}')
else:
logging.error(f'移动失败:{move_response}')
elif newname is not None and type == 'rename': # 重命名
file_list = json.dumps([{'path':path, 'newname':newname}])
rename_response = api_instance.filemanagerrename(access_token,_async,file_list,ondup=ondup)
if rename_response['errno'] == 0:
logging.info(f'{path}文件重命名为{newname}')
else:
logging.error(f'重命名失败:{rename_response}')
elif path is not None and type == 'delete': # 删除
file_list = json.dumps([path])
delete_response = api_instance.filemanagerdelete(access_token,_async,file_list,ondup=ondup)
if delete_response['errno'] == 0:
logging.info(f'{path}删除成功')
else:
logging.error(f'删除失败:{delete_response}')
else:
logging.error(f'未知操作:{type},type:copy/move/rename/delete')
except openapi_client.ApiException as e:
logging.error("文件操作异常: %s\n" % e)
- 用于获取用户指定目录下,包含指定关键字的文件列表
# 文件查询
def search(self, key, dir='/', recursion='1', web='1',page='1'):
api_instance = fileinfo_api.FileinfoApi(self.client)
access_token = self._access_token
key = key # 搜索关键字,最大30字符(UTF8格式)
web = web # str | (optional) | 是否展示缩略图信息,带这个参数会返回缩略图信息,否则不展示缩略图信息
num = "500" # str | (optional) | 默认为500,不能修改
page = page # str | (optional) | 页数,从1开始,缺省则返回所有条目
dir = dir # str | (optional) | 搜索目录,默认根目录
recursion = recursion # str | (optional) | 是否递归,带这个参数就会递归,否则不递归
try:
api_response = api_instance.xpanfilesearch(
access_token, key, web=web, num=num, page=page, dir=dir, recursion=recursion)
if api_response['errno'] == 0:
type_list = api_response['list']
logging.info(f'{dir}目录下搜索关键词为{key}的文件有{len(type_list)}个')
return type_list
else:
logging.error(f'{dir}目录下搜索关键词为{key}文件查询失败:{api_response}')
# return api_response['']
except openapi_client.ApiException as e:
logging.error(f"文件搜索失败: {e}")
- 用于获取用户网盘中指定目录下的文件列表。返回的文件列表支持排序、分页等操作
# 指定目录下文件下载到本地
def get_dir_file2local(self, dir, local_dir='./tmp/'):
if not os.path.exists(local_dir):
os.makedirs(local_dir)
file_list = self.get_file_list(dir,type='file')
fs_ids = [file.get('fs_id') for file in file_list]
# for file in file_list:
# fs_ids.append(file.get('fs_id'))
info_list = self.file_meatas(fs_ids)
download_infos = []
for info in info_list:
dlink = info.get('dlink')
filename = info.get('filename')
# size = info.get('size')
local_path = local_dir+filename
download_infos.append({'path':local_path, 'url':f'{dlink}&access_token={self._access_token}'})
# 并发下载
self.thread_download(download_infos)
# 获取用户指定目录下的文档列表
# type doc image file
def get_file_list(self, dir, type, recursion='1', page=1, num=20, order="time", desc='1', web='1',folder='0',showempty=1):
api_instance = fileinfo_api.FileinfoApi(self.client)
access_token = self._access_token
parent_path = dir # str | (optional)|目录名称,以/开头的绝对路径, 默认为/路径包含中文时需要UrlEncode编码 给出的示例的路径是/测试目录的UrlEncode编码。
recursion = recursion # 是否需要递归,0为不需要,1为需要,默认为0 递归是指:当目录下有文件夹,使用此参数,可以获取到文件夹下面的文档
page = page # 页码,从1开始, 如果不指定页码,则为不分页模式,返回所有的结果。如果指定page参数,则按修改时间倒序排列
num = num # 一页返回的文档数, 默认值为1000,建议最大值不超过1000
order = order # 排序字段:time按修改时间排序,name按文件名称排序(注意,此处排序是按字符串排序的,如果用户有剧集排序需求,需要自行开发),size按文件大小排序,默认为time
desc = desc # 0为升序,1为降序,默认为1
web = web # 为1时返回文档预览地址lodocpreview
try:
if type == 'doc':
api_response = api_instance.xpanfiledoclist(
access_token, parent_path=parent_path, recursion=recursion, page=page, num=num, order=order,
desc=desc, web=web)
if api_response['errno'] == 0:
type_list = api_response['info']
logging.info(f'{dir}目录下{type}类型的文件有{len(type_list)}个')
return type_list
else:
logging.error(f'{dir}目录下{type}类型文件查询失败:{api_response}')
# pprint(api_response)
elif type == 'image':
api_response = api_instance.xpanfileimagelist(
access_token, parent_path=parent_path, recursion=recursion, page=page, num=num, order=order,
desc=desc, web=web)
if api_response['errno'] == 0:
type_list = api_response['info']
logging.info(f'{dir}目录下{type}类型的文件有{len(type_list)}个')
return type_list
else:
logging.error(f'{dir}目录下{type}类型文件查询失败:{api_response}')
elif type == 'file':
# dir 需要list的目录,以/开头的绝对路径, 默认为/路径包含中文时需要UrlEncode编码 给出的示例的路径是/测试目录的UrlEncode编码
# folder 是否只返回文件夹,0 返回所有,1 只返回文件夹,且属性只返回path字段
# start 起始位置,从0开始
# limit 查询数目,默认为1000,建议最大不超过1000
# order 排序字段:默认为name; time表示先按文件类型排序,后按修改时间排序; name表示先按文件类型排序,后按文件名称排序;(注意,此处排序是按字符串排序的,如果用户有剧集排序需求,需要自行开发) size表示先按文件类型排序,后按文件大小排序。
# web 值为1时,返回dir_empty属性和缩略图数据
# showempty 是否返回dir_empty属性,0 不返回,1 返回
api_response = api_instance.xpanfilelist(
access_token, dir=dir, folder=folder, start=str(page), limit=num, order=order, desc=int(desc), web=web,
showempty=showempty)
if api_response['errno'] == 0:
type_list = api_response['list']
logging.info(f'{dir}目录下{type}类型的文件有{len(type_list)}个')
return type_list
else:
logging.error(f'{dir}目录下{type}类型文件查询失败:{api_response}')
elif type == 'listall':
# 递归获取指定目录下的文件列表。当目录下存在文件夹,并想获取到文件夹下的子文件时,可以设置 recursion 参数为1,即可获取到更深目录层级的文件
api_instance = multimediafile_api.MultimediafileApi(self.client)
api_response = api_instance.xpanfilelistall(
access_token, dir, int(recursion), web=web, start=page, limit=num, order=order, desc=int(desc))
if api_response['errno'] == 0:
type_list = api_response['list']
logging.info(f'{dir}目录下递归获取文件有{len(type_list)}个')
return type_list
else:
logging.error(f'{dir}目录下递归获取文件查询失败:{api_response}')
except openapi_client.ApiException as e:
logging.error(f"获取{type}列表失败:{e}")
# 用于获取用户指定文件的meta信息。支持查询多个或一个文件的meta信息,meta信息包括文件名字、文件创建时间、文件的下载地址等
def file_meatas(self,fsids):
api_instance = multimediafile_api.MultimediafileApi(self.client)
access_token = self._access_token
fsids = json.dumps(fsids) # str |
# path # 查询共享目录或专属空间内文件时需要。 共享目录格式: /uk-fsid 其中uk为共享目录创建者id, fsid对应共享目录的fsid 专属空间格式:/_pcs_.appdata/xpan/
thumb = "1" # 是否需要缩略图地址,0为否,1为是,默认为0
extra = "1" # 图片是否需要拍摄时间、原图分辨率等其他信息,0 否、1 是,默认0
dlink = "1" # 是否需要下载地址,0为否,1为是,默认为0。获取到dlink后,参考下载文档进行下载操作
needmedia = 1 # 视频是否需要展示时长信息,needmedia=1时,返回 duration 信息时间单位为秒 (s),转换为向上取整。 0 否、1 是,默认0
detail = 1 # 视频是否需要展示长,宽等信息。 0 否、1 是,默认0
try:
api_response = api_instance.xpanmultimediafilemetas(
access_token, fsids, thumb=thumb, extra=extra, dlink=dlink, needmedia=needmedia)
# pprint(api_response)
if api_response['errno'] == 0:
info_list = api_response['list']
logging.info(f'通过{fsids}获取文件信息有{len(info_list)}个')
return info_list
else:
logging.error(f'{dir}目录下递归获取文件查询失败:{api_response}')
except openapi_client.ApiException as e:
print("Exception when calling MultimediafileApi->xpanmultimediafilemetas: %s\n" % e)
# 并发下载 将文件下载到本地 dlink有效期为8小时,过期后,dlink失效
def thread_download(self, download_infos, max_workers=10):
# 记录下载了多少个url
file_paths = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_results = {}
for download_info in download_infos:
path = download_info.get('path')
url = download_info.get('url')
fs = executor.submit(ConcurrentDownload.download_file, url, os.path.dirname(path),file_name=path.rsplit('/')[-1],threads=max_workers)
future_results[fs] = url
for future in as_completed(future_results):
file_paths.append(future.result())
logging.info('总共{}个,下载了{}个'.format(len(download_infos),len(file_paths)))
return file_paths
总结
上述一些能力都是一些常用的功能,对于大文件来说,一般下载操作都比较耗时,所以我们这边是进行了一个并发下载的操作,可以快速实现下载。以上功能可以方便后期可以快速的和网盘进行交互,从而将我们的各种应用对接到网盘,实现我们最终的目的。