【WebDav】坚果云使用WebDav访问文件夹内文档大于750份无法返回问题

发布于:2025-02-26 ⋅ 阅读:(17) ⋅ 点赞:(0)

问题

坚果云使用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