2026/10/9 17:08:46

用Playwright实现夸克网盘分享文件自动下载:登录态与提取码处理实践

用Playwright实现夸克网盘分享文件自动下载:登录态与提取码处理实践 我先说明一下这类自动化脚本的使用边界必须注意只用于处理你自己账号权限范围内的、被合法分享的文件不能用来批量爬取他人资源或钻平台规则空子。我用 Playwright 做夸克网盘分享文件下载纯粹是为了省掉每天重复点击下载、输提取码、等上传的那几分钟顺便把登录态复用和提取码处理这两块最烦的逻辑跑通。看完这篇文章你自己也能复现一套类似流程改一改选择器和链接解析逻辑就能适配各种网盘。1. 项目动机与方案选型1.1 为什么会有这个需求先说说我为什么盯上这个场景。每天固定要从几个夸克网盘分享链接下拉文件一次两次手动来无所谓次数一多就非常烦躁打开链接运气好不用输提取码运气不好得翻聊天记录找密码进去之后还要等页面加载、选文件、点下载、确认下载方式一套下来少说两分钟。如果再加上登录态时不时失效要重新扫码这体验只能用折磨形容。于是我就想能不能让电脑自己把这些事干了。需求拆开看其实就那么几点能打开分享链接、能处理提取码校验、能保持登录状态不用天天扫码、能把目标文件下载到本地指定目录。这四点全做完这个脚本就有了实用价值而不是玩具代码。这个项目适合谁参考一是天天要跟网盘分享链接打交道、想省点重复劳动的普通用户二是刚接触 Playwright、想找一个真实项目的 Python 开发者。前者可以直接用我梳理的逻辑跑通流程后者能学到登录态持久化、显式等待、iframe 处理这些在实战里高频出现的知识点。1.2 为什么不用 requests 直接请求接口最早我试过用 requests 硬怼接口毕竟网盘有 Web API理论上拿到 cookie 和文件 ID 就能直接调下载接口。但这个方案实际跑起来有三个大坑。第一个坑接口协议不公开。夸克网盘的页面交互涉及多个内部接口字段加密和签名逻辑经常变今天能跑通的参数过两天平台更新可能就不能用了。你得像追版本号一样天天盯接口改动这不是普通脚本该干的活。第二个坑cookie 过期机制复杂。登录态涉及多个 cookie 字段且有效期和绑定信息有关用 requests 管理这些 cookie 的生命周期要写一堆额外代码。第三个坑下载地址通常带时效签名比如几十分钟就失效requests 拿到地址如果没及时下载链接就废了。所以后来我把思路完全换掉不用猜接口直接让浏览器替我去点击、去下载。这就是 Playwright 出现的意义。1.3 为什么选 Playwright 而不是 Selenium既然决定走浏览器自动化的路那就要在 Playwright 和 Selenium 之间做选择。说下我的实测感受。Selenium 是老牌选手生态成熟但它的缺点在当下越来越明显需要额外维护 driver 版本Chrome 一更新 driver 就容易崩内置等待方法写得别扭显式等待代码量很大对 Shadow DOM 和 iframe 的处理也不够顺手。Playwright 这边driver 装一次就完事auto-waiting 机制让它自带人类级别的等待能力而且官方封装了 webdriver 屏蔽逻辑很多网站在识别自动化时没那么容易直接拒绝。这个区别在我的实际脚本里感受非常明显Selenium 写出来要反复试Playwright 写出来慢一点点就能稳定跑。另外 Playwright 的下载事件模型是我最喜欢的一点它能监听下载行为、拿到下载文件名、控制保存路径这里面的每一步都有官方 API 支撑不用像 Selenium 那样去处理浏览器的下载弹窗。实话实说Selenium 想处理好下载这步要改浏览器 profile 的下载设置否则弹窗会卡死自动化流程。而 Playwright 直接给你 download API简单直接。2. 开发环境准备与基础配置2.1 Python 环境与依赖安装我用的 Python 版本是 3.10其实 3.8 以上基本都行。建议你直接用虚拟环境来装别把依赖混到系统 Python 里。先创建虚拟环境再装包python -m venv venv source venv/bin/activate # Windows 上是 venv\Scripts\activate pip install playwright如果你在装 playwright 这个包的时候遇到网络超时优先换国内镜像源pip install playwright -i https://pypi.tuna.tsinghua.edu.cn/simple这一步装的是 Python 侧的库先把 Playwright 的 API 装进来。2.2 安装 Playwright 浏览器内核装完库还要装浏览器内核这一步是新手最容易卡住的地方。我用的是 Chromium执行playwright install chromium如果这一步下载失败通常是网络原因。解决办法是先手动设置环境变量指定下载源再执行 install。我自己在部署到服务器时碰到过playwright install直接失败的问题用镜像源就解决了。装完之后可以跑一下验证命令看是否能正常启动浏览器python -c from playwright.sync_api import sync_playwright; psync_playwright().start(); bp.chromium.launch(headlessTrue); print(ok); b.close(); p.stop()看到 ok 就说明环境没问题。如果你跑在 Linux 服务器上还需要额外装一些系统依赖比如字体库和 GUI 库缺了它们浏览器会报libnss3之类的错误。我贴一下 CentOS / Ubuntu 常见的依赖安装命令但这取决于你的系统环境按需执行# Ubuntu / Debian # sudo apt install libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2建议直接用官方文档里的playwright install-deps命令它会自动判断系统缺失的库一个命令装完。2.3 目录设计与运行前的必要准备我建议把脚本拆成几个模块别把所有功能堆在一个文件里。我的目录结构是这样的quark_auto_download/ ├── main.py # 主流程 ├── config.py # 链接、提取码、下载目录配置 ├── login.py # 登录与登录态持久化 ├── downloader.py # 页面交互与下载逻辑 └── downloads/ # 下载文件存放目录目录结构越清晰后面排查问题越快。同时注意一点脚本跑起来的路径里别带中文和空格有些场景下 Playwright 对特殊字符处理会出幺蛾子虽然不是必然问题但规避掉能省很多事。3. 登录态持久化的两种方案3.1 方案一storage_state 保存与复用登录态持久化是整套脚本里最影响体验的一环。如果不做这一步你每次跑脚本都要扫一次码那自动化的价值就少了一半。第一种做法用 Playwright 的storage_state。核心逻辑是第一次运行时浏览器打开登录页你手动扫码登录登录成功后调用context.storage_state()把 cookie 和 localStorage 数据导出一个 JSON 文件。之后的每次运行都用browser.new_context(storage_statestate.json)创建上下文浏览器会自动带上之前的登录态。代码形如from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) context browser.new_context() page context.new_page() page.goto(https://pan.quark.cn/) # 手动扫码登录 input(登录完成后按回车...) context.storage_state(pathstate.json) browser.close()之后每次启动新上下文时传入该文件即可。这个方案最大的优点是可移植性JSON 文件就几十 KB换机器部署也不用重新导出直接把这个文件拷过去就行。缺点是它只保存了登录相关的存储数据一旦登录态在服务端失效比如密码改了、异地登录检测这个 JSON 就废了需要手动重新跑一遍登录流程。3.2 方案二user_data_dir 持久化用户目录第二种思路更加简单粗暴直接让浏览器使用一个固定的用户数据目录相当于把整个浏览器的小书包都保存下来里面既有 cookie 也有 localStorage还有各种缓存和指纹信息。context browser.new_context( user_data_dir./profile )用这种方式首次登录一次后profile目录就生成了。以后每次运行都走这个目录登录态天然保留连导出导入storage_state的步骤都省了。这个方案在某些场景下比 storage_state 更稳因为它是浏览器级的持久化不仅 cookie 还在网站可能会生成的一些本地标记也在像fingerprint这类数据就不会丢。代价是 profile 目录体积会慢慢变大几十 MB 到几百 MB 都可能而且换机器部署时要把整个目录一起拷过去多多少少有点笨重。3.3 两种方案对比与我的选择对比项storage_stateuser_data_dir存储内容仅 cookie 与 localStorage完整浏览器用户数据文件体积几十 KB几十 MB 到数百 MB换机器迁移拷贝单文件即可需要拷贝整个目录稳定性受登录态失效影响更接近真实浏览器状态适用场景快速部署、少量状态长期固定环境反复使用我推荐你优先用storage_state方案因为你可能最后会把脚本放到服务器上跑单文件迁移要方便得多。但是如果你脚本只在自己的电脑上跑那就直接用user_data_dir省心且稳定性好。两条路我都跑通过哪个适合你取决于你的部署方式。4. 提取码处理与核心页面交互4.1 分享链接的两种形态夸克网盘的分享链接我在实际使用中遇到过两种形态。一种是链接直接带着提取码长这样的https://pan.quark.cn/s/xxxx#提取码: abcd这种最简单你只需要从链接字符串里解析出提取码部分就行了。另一种是链接不带提取码提取码单独发给你https://pan.quark.cn/s/xxxx 提取码abcd这种的话建议你在配置阶段就把提取码单独存一个字段避免每次手动输入。逻辑上脚本先打开链接然后检测页面是否出现提取码输入框如果出现就自动填入没出现就直接进入文件列表。4.2 提取码输入与确认逻辑提取码的页面交互核心是判断提取码输入框是否存在。可以用一个函数来尝试定位如果定位到了就输入没有就不管。此时要特别注意一点输入完后要确认按钮的点击逻辑有的页面输入完会直接跳转有的需要点一个“确认”按钮。为了稳我建议输完提取码后先等待网络响应再判断页面是否跳转到文件列表页而不是盲目点击确认按钮。我实际去测的时候发现提取码输入框可能需要一点时间去渲染如果脚本一开页面就去定位输入框会因为元素还没出现而报错。所以这里要使用显式等待locator page.locator(input[placeholder*提取码]) locator.wait_for(statevisible, timeout10000)注意提取码可能有大小写如果平台规定不区分大小写就无所谓。但我在处理时还是会加一层统一转大写或小写的逻辑避免边界情况。4.3 定位下载按钮不用睡死等时间改用显式等待页面加载完成之后就是核心的下载环节。这里我特别想强调一个概念**不要乱用time.sleep**。网速快的时候 sleep 太长浪费时间网速慢的时候 sleep 太短元素还没出来就报错。Playwright 的定位器自带 auto-waitlocator.click()这个动作本身就内置了等待元素可见、可交互的逻辑你对它的信任程度应该高于手动 sleep。比如下载按钮的点击我会写成这样download_button page.locator(text下载) download_button.first.wait_for(statevisible, timeout15000) download_button.first.click()但这里有个坑页面上可能会有多个“下载”字样所以我通常用locator.first或更具体的 CSS 定位来缩小范围。你当然也不知道网盘的 DOM 结构什么时候改版所以选择器这部分要写成易于修改的常量而不是散落各处。4.4 文件保存路径与重名处理下载文件怎么落地这个环节容易翻车。Playwright 里的下载事件非常好用你在点击下载按钮之前就注册好监听文件下载完成后直接保存。with page.expect_download() as download_info: page.locator(text下载).first.click() download download_info.value download.save_as(fdownloads/{download.suggested_filename})这段代码有个细节expect_download触发下载时会等待下载事件发生拿到 download 对象后可以调用save_as指定的绝对或相对路径。但要注意如果目标目录不存在save_as 不会自动创建目录所以最好提前os.makedirs一下。重名文件也要处理我习惯是在保存前检查同名文件是否存在存在的话自动在文件名后加时间戳。5. 完整代码框架与实现细节5.1 主流程代码我直接贴一版可以跑通全流程的代码保留了注释方便你理解。这一版的逻辑是启动浏览器加载登录态如果链接里带提取码就解析出来打开链接检测是否需要输入提取码需要就输入进入文件列表后定位下载按钮监听下载事件并保存文件到本地。import os import re import time from playwright.sync_api import sync_playwright # 配置区 SHARE_URL https://pan.quark.cn/s/xxxx EXTRACT_CODE abcd # 如果不带提取码传 None STATE_FILE state.json DOWNLOAD_DIR downloads def parse_extract_code(url): 从链接中解析提取码优先从 # 号后解析其次用配置的提取码 match re.search(r提取码[:]\s*([A-Za-z0-9]), url) if match: return match.group(1) return EXTRACT_CODE def ensure_download_dir(): if not os.path.exists(DOWNLOAD_DIR): os.makedirs(DOWNLOAD_DIR) def handle_extract_code(page, code): 检测是否有提取码输入框有则填入并确认 if not code: return try: input_locator page.locator(input[placeholder*提取码]) input_locator.wait_for(statevisible, timeout8000) input_locator.fill(code) # 点击确认按钮选择器可能需要根据实际页面调整 confirm_btn page.locator(button:has-text(确认)) if confirm_btn.count() 0: confirm_btn.click() # 等待跳转到文件列表页 page.wait_for_load_state(networkidle) except Exception as e: print(f提取码处理异常可能页面不需要提取码: {e}) def download_first_file(page): 监听下载事件并保存 ensure_download_dir() with page.expect_download(timeout60000) as download_info: page.locator(text下载).first.click() download download_info.value filename download.suggested_filename # 处理重名 target os.path.join(DOWNLOAD_DIR, filename) base, ext os.path.splitext(filename) counter 1 while os.path.exists(target): target os.path.join(DOWNLOAD_DIR, f{base}_{counter}{ext}) counter 1 download.save_as(target) print(f下载完成: {target}) def main(): with sync_playwright() as p: browser p.chromium.launch(headlessFalse) # 首次调试建议用有头模式 context browser.new_context(storage_stateSTATE_FILE) page context.new_page() code parse_extract_code(SHARE_URL) page.goto(SHARE_URL, wait_untildomcontentloaded, timeout60000) handle_extract_code(page, code) # 等文件列表加载 page.wait_for_timeout(3000) download_first_file(page) browser.close() if __name__ __main__: main()这段代码不是银弹但结构上你看完就能理解整套流程的脉络。注意page.wait_for_load_state(networkidle)并不是所有网站上都能顺利等到网络请求太多时可能会超时。我在实际测试中发现适当的page.wait_for_timeout比 networkidle 更稳。5.2 等待策略的细节等待策略这块我一定要多讲几句。新手最容易犯的错是打开链接后直接time.sleep(5)然后马上就去点按钮。结果网络快的时候一执行就报错因为 sleep 5 秒才跑完的事情你 3 秒就做完了元素还没渲染好。我的建议是能用wait_for_selector或locator.wait_for就用它们实在没有对应元素出现逻辑的地方才用固定 sleep 兜底。固定 sleep 的价值在于给页面渲染留出缓冲但不能作为主力等待策略。具体到这个项目的等待关键点有三个一是打开链接后等待文件列表渲染二是点击下载后等待下载事件触发三是下载完成后等待文件写出。这三个阶段我用timeout参数来控制上限避免无限等待把脚本卡死。5.3 下载完成的判定下载完成怎么判定有两种方式。第一种是用expect_download的 timeout 机制下载事件超过设定时间没触发就抛异常你可以在异常里做重试。第二种是下载对象拿到后轮询本地文件大小直到文件大小稳定不再增长时判定下载完成。import time def wait_file_complete(path, check_interval1, timeout60): start time.time() last_size -1 while time.time() - start timeout: if os.path.exists(path): size os.path.getsize(path) if size last_size and size 0: return True last_size size time.sleep(check_interval) return False这个函数处理大文件时特别有用因为大文件下载可能持续几秒甚至几分钟只靠下载事件判断不了是不是真的完整落地。用文件大小稳定法来兜底稳很多。6. 常见问题排查与实操避坑6.1 安装失败与超时问题我在第一小节说过playwright install chromium可能失败这里再展开说说细节。失败原因绝大多数发生在下载浏览器二进制文件的过程中卡在Downloading Chromium这行就再也不动了。解决办法是配置镜像环境变量export PLAYWRIGHT_DOWNLOAD_HOSThttps://cdn.npmmirror.com/binaries/playwright playwright install chromiumWindows 用户直接在命令行里set PLAYWRIGHT_DOWNLOAD_HOST...再执行 install 就行。还有个隐藏坑是公司网络或代理环境导致的下载失败这种情况你多尝试几次或者用手机热点都可能解决但我不展开讲代理配置了。6.2 元素定位不到iframe 与 Shadow DOM夸克网盘页面的下载按钮有时候不在主文档里而是藏在 iframe 或 Shadow DOM 里。这是 Playwright 学习里最值得注意的点之一。iframe 处理方式Playwright 封装得很顺手frame page.frame(nameiframe名称) # 或者通过 url 模糊匹配 frame page.frame(url*download*)如果按钮在 iframe 里那所有的选择器都要加上 frame 前缀。Shadow DOM 则稍微麻烦一点需要穿透page.locator(css#shadow-host css#inner-button)定位不到元素时最有效的排查手段是打开浏览器的开发者工具在 Elements 面板确认目标元素到底在不在主文档、在不在 iframe 里。我多次遇到类似问题最后发现是页面上有多层嵌套选择器写浅了。6.3 登录态失效后的自动重登登录态迟早会过期这是绕不过去的。我做的处理是每次打开页面后检测是否跳转到登录页如果 URL 特征匹配到登录页就提示需要手动登录。这里有两种处理策略。策略一脚本退出你打开浏览器手动登录一次再重新生成 state.json。策略二脚本自动打开有头浏览器等待你扫码登录完成后自动继续。第二种体验更好所以我实际写的是第二种逻辑。核心代码大概是这样page.goto(SHARE_URL, wait_untildomcontentloaded) if login in page.url: print(检测到登录页请手动登录...) page.pause() # 或者等待输入回车page.pause()这个 API 在调试浏览器自动化脚本时特别方便会自动打开一个 Playwright Inspector 面板你可以手动操作浏览器然后再恢复脚本执行。虽然生产环境不建议这么干但在首次配置登录态时非常好用。6.4 下载触发但文件没落地这个问题我踩过最多次。表现为脚本没有报错页面里可能也看到下载速度在跑但本地目录里没有文件。大概率原因是文件名和下载目录路径没对或者 save_as 的目录不存在。我在前面代码里特意加了ensure_download_dir函数就是啃过这个亏之后补上的。另外有头模式第一次跑下载时会弹系统下载提示框但那不是浏览器默认下载弹窗而是 Playwright 自己控制的不需要手动确认。如果你运行脚本的机器没有桌面环境比如服务器必须用 headless 模式并且确保下载路径可写。6.5 常见错误速查表错误现象可能原因解决思路playwright install下载失败网络问题配置PLAYWRIGHT_DOWNLOAD_HOST镜像后重试找不到元素选择器iframe / Shadow DOM / 页面改版用开发者工具确认元素位置穿透定位每次都要求扫码登录storage_state 未正确保存检查 state.json 是否生成重新导出下载事件不触发没有点击到正确的下载按钮切换更具体的选择器检查是否有多个下载按钮下载文件重名被覆盖缺少重名处理保存前检查文件是否存在自动加序号页面打开后一直空白浏览器内核版本问题重新playwright install chromium6.6 关于下载大文件和断点续传的补充如果你的场景涉及大文件下载比如几十 GB 的视频资源那 Playwright 自带的 download 事件虽然能拿到文件流但万一中途断了没有断点续传机制。我目前做过的方案里最稳的是用浏览器直接下载到默认下载目录下载完成后再用 Python 的shutil.move从浏览器默认下载目录挪到目标目录。这个思路适合夸克网盘分享的那种超大文件场景缺点是代码会更复杂一些。如果只是下载几个百 MB 的压缩包直接download.save_as就够了。7. 写在最后的几个经验这套脚本我前前后后改了很多版本从最初 200 行进化成现在这样模块化的结构最大的体会是浏览器自动化的核心难点永远不是代码本身而是对页面状态变化的敏感程度。哪些页面元素要等哪些操作会触发异步请求这些细节只有自己反复跑几遍才能摸清楚。另外想说个实用技巧调试阶段不要开 headless 模式让浏览器窗口弹出来你能直观看到脚本每一步的操作配合page.pause()手动干预排错效率能提升好几倍。等到全部流程稳定之后再把headlessTrue打开放到服务器上挂着跑。这一步是最朴实但也最好用的调试习惯。如果你准备自己动手改这套代码建议从两个方向下手一是把选择器全都抽到配置文件里方便页面改版后统一替换二是把链接解析的逻辑写成支持批量任务的方式读一个文本文件里的多个链接顺序处理。这两个方向改完这个脚本从“能跑”变成“好用”就更近了一步。