坚果云分页多次加载解决办法
- 问题
- 坚果云使用WebDav访问限制
- 现象
- PropFind请求返回数据少于文件夹内数据
- 坚果云请求响应体
- 坚果云请求响应头
- 结论
- 文档遍历实现
- python循环方式实现
问题
坚果云使用WebDav访问限制
在批量请求时使用大部份的WebDav库请求坚果云时都会出现仅请求到前750条数据,当询问客服后得到回复是如下图所示限制:
现象
PropFind请求返回数据少于文件夹内数据
WebDav协议使用的请求是PropFind方法(释意如下):
主要用于获取指定资源的属性集合。它属于WebDAV(Web Distributed Authoring and Versioning)协议的一部分,允许客户端查询与给定资源相关的元数据、配置和状态信息。
坚果云请求响应体
若直接请求目录会仅返回750个文件,若文件数量超过750个无法再次返回
响应体(仅返回750个数据):
坚果云请求响应头
观察响应头(值得注意的是响应头中返回了一个link字段,该字段的值为一个链接):
使用PropFind方法再次访问此链接(链接需要通过url编码变换后进行访问,可以看出返回了另外750行数据):
结论
由上述现象可以发现,请求时若数据过多(超过750条)会在响应头中带上link字段(即下一组数据的访问链接),故多页多次加载最简单的实现方式仅需要使用递归请求(展平为循环请求)即可拿到文件夹内所有的数据。
- 1.QPM数限制:请求时注意请求次数限制1500/30min的请求次数,超过请求限制,需要等待解封获取数据时间变长。
- 2.按需请求:请求时应注意避免过度请求数据导致数据返回时间长,可计算后按需请求,例如仅需要文件夹内前850份数据单文件内有2800份数据,那仅需请求两次即可,无需请求四次拿到全量数据。
文档遍历实现
python循环方式实现
class DavClient:
"""
默认:1500/30min
"""
def __init__(self, dav_url, username, password, req_frequency=100):
auth_str = f'{username}:{password}'
auth_bytes = auth_str.encode('ascii')
auth_base = base64.b64encode(auth_bytes).decode('ascii')
self.headers = {'Authorization': f'Basic {auth_base}'}
self.dav_url = dav_url
self.req_frequency = req_frequency
def _req_data(self, step_url) -> tuple[str, str, str]:
response = requests.request('PROPFIND', step_url, headers=self.headers, verify=False)
# 获取响应头link及rel
next_link = ""
rel = ''
if "Link" in response.headers:
link_str = response.headers.get("Link")
next_link, rel = re.findall(pattern="<(.*)>; rel=\"(.*)\"", string=link_str)[0]
response.raise_for_status()
return unquote(next_link), rel, response.text
def _analysis_res_data(self, response_text):
tree = ElementTree.fromstring(response_text)
namespace = {'d': 'DAV:'}
file_data_list = [{"fileName": response.find('.//d:displayname', namespace).text, "href": unquote(response.find('d:href', namespace).text)} for response in tree.findall('d:response', namespace)]
return file_data_list
def ls(self, path, detail: bool=False):
"""
QPM: 控制请求速率,防止失败 50qpm
"""
document_list = []
current_link = f'{self.dav_url}{path}'
req_count = 0
std_qpm = self.req_frequency / 60
start_time = time.perf_counter()
while current_link:
end_time = time.perf_counter()
delta_time = end_time - start_time
print("QPM", std_qpm, req_count, delta_time, req_count / delta_time)
if delta_time == 0 or (std_qpm > (req_count / delta_time)):
req_count += 1
next_link, rel, res_text = self._req_data(step_url=current_link)
current_link = next_link
file_list = self._analysis_res_data(res_text)
document_list.extend(file_list)
else:
print(f'停一下{req_count}')
time.sleep(1)
return document_list