在开始阅读前,您需要先了解,本贴搬运自我的博客:我如何用Python完整移植了一个Pixiv批量下载工具
如果你想要这个程序,请访问官网哦:Pixiv画师批量下载工具 - 官方主页
一个用了三年的脚本
PixivUserBatchDownload这个用户脚本我用了很久了。说实话功能是真的强,配合Aria2批量下载画师作品,速度飞快,还能断点续传。但每次用都得先打开Pixiv网页,等脚本加载,然后在浏览器里操作。我就在想,能不能把它变成一个独立的exe,双击就能运行。
于是我就开始动手了。
五千行代码的逆向工程
原脚本有五千多行JavaScript,我花了好几天才把逻辑理清楚。核心流程是这样的:OAuth2登录获取token,用token调用Pixiv的App API获取用户作品列表,然后把图片地址通过JSON-RPC发给Aria2下载。
数据结构主要是三块。用户信息存了ID、昵称、头像、作品总数、收藏总数。作品信息存了ID、标题、类型、页数、创建日期、标签、图片URL。还有一个下载方案的配置,包括Aria2地址、代理、保存路径模板、掩码列表。
登录这块最麻烦。Pixiv用的是OAuth2的PKCE流程,需要先随机生成一个code_verifier,SHA256之后变成code_challenge,然后打开浏览器让用户登录。用户登录后会在地址栏返回一个pixiv://开头的链接,里面带了一个code。程序拿到这个code之后,用code_verifier去换取access_token。
原脚本是用GM_xmlhttpRequest发请求的,我全部换成了requests库。请求头里有个X-Client-Hash,是根据当前时间和一个固定salt算出来的MD5。如果这个算错了,所有API请求都会返回403。我对着原脚本的逻辑,用hashlib重新写了一遍。
还有一个坑是Referer和User-Agent必须完全匹配Pixiv的Android客户端,不然会被拒绝。原脚本里这些值都是写死的,我照抄过来了。
图片下载的坑
第一次测试的时候,我发现下载下来的全是缩略图,几百KB的那种,根本不是原图。查了半天才发现,我错误地用了illust.image_urls里的large和medium字段,那些只是预览图。
真正的原图在illust.meta_single_page.original_image_url里,如果是多页漫画,在illust.meta_pages[].image_urls.original里。原脚本里有一个parseIllustUrl函数专门解析这个,我移植的时候漏掉了。
修复之后,下载的图片变成了这样的URL: https://i.pximg.net/img-original/img/2026/04/30/14/12/31/144170598-78a8464c6d3d870d006c53de58cfea60_p0.jpg 文件名是144170598-78a8464c6d3d870d006c53de58cfea60_p0.jpg,包含了作品ID、hash值和页码。
文件名格式的自由化
原脚本有一套掩码系统,我觉得这个设计特别好。用户可以自定义文件名格式,比如写%{illust.id}%{illust.title}%{page}.%{illust.extention},程序就会自动替换成对应的值。
我完整移植了这套系统。变量大概有这些:作品ID、作品标题、作品类型、页码、文件扩展名、画师ID、画师昵称、创建日期、当前日期等等。用户还可以自定义掩码,比如定义一个叫"作品信息"的掩码,内容是%{illust.id}%{illust.title},然后在文件名格式里引用%{作品信息}%{page}.%{illust.extention}。
默认提供了两个方案。方案一生成的文件名是12345678春の風景0.jpg,方案二是12345678_0.jpg。用户可以在GUI里实时预览效果。
GUI的反复折腾
GUI是我花时间最多的部分。一开始用tkinter的默认控件,做出来的界面丑得没法看,窗口缩放还各种问题,在高DPI屏幕上文字都是模糊的。
我重新设计了整个界面,用了ttk的自定义样式,深色主题,卡片式的布局。把tkinter默认的标题栏去掉了,自己画了一个自定义标题栏,带最小化和关闭按钮。窗口加上了圆角,用win32的CreateRoundRectRgn实现的。
DPI适配用ctypes调用SetProcessDpiAwareness,这样在高分屏上字体才不会糊。字体方面,我把MiSans-Medium.ttf放在程序目录下,打包的时候一起打包进去。tkinter不直接支持加载外部ttf,需要在系统里注册或者复制到字体目录。
登录流程前面加了一个免责声明弹窗。用户必须把滚动条拉到最底部,同意按钮才会变成可点击状态。这是为了防止用户没看协议就直接点同意。
筛选功能的逐步完善
原脚本的下载过滤器是纯文本的,用户可以写illust.page_count > 5这种表达式。我用eval实现了,但加了很多限制,只允许访问特定的变量,不能调系统模块。
然后我加了标签筛选。Pixiv每个作品都有一组标签,每个标签有name和translated_name两个字段。用户指定包含标签列表和排除标签列表,还有匹配模式,是包含任一标签还是包含全部标签。程序遍历作品的所有标签,判断是否符合条件。
作品名筛选也类似,用户指定包含关键词和排除关键词,直接在作品标题里匹配。
我还加了自定义筛选表达式的功能,用户可以直接写Python表达式。可用的变量有tags(标签列表)、title(标题)、id(作品ID)、page_count(页数)、create_date(创建日期)。这样用户就能写同时包含萝莉和白丝这种复杂的条件了。
最后加了一个功能,用户可以只下载包含指定标签的作品。比如想看芙宁娜的图,就在包含标签里填芙宁娜,模式选包含任一标签,程序就只会下载带芙宁娜标签的作品。
OS:你咋知道我喜欢芙宁娜
网络问题的处理
Pixiv的图片服务器在某些地区访问很慢。我加了图片代理功能,用户可以选择通过i.pximg.org、i.yuki.sh、pixiv.pics、pixiv.cat这些代理服务器下载。
原理很简单,就是把图片URL的域名替换成代理服务器的域名,路径保持不变。比如https://i.pximg.net/img-original/xxx.jpg变成https://i.pximg.org/img-original/xxx.jpg。用户可以在下拉框里直接选,不用手动输入。
连接测试功能会向[www.pixiv.net](http://www.pixiv.net%E5%8F%91%E4%B8%80%E4%B8%AA%E8%AF%B7%E6%B1%82%EF%BC%8C%E6%A3%80%E6%B5%8B%E7%BD%91%E7%BB%9C%E6%98%AF%E4%B8%8D%E6%98%AF%E9%80%9A%E7%9A%84%EF%BC%8C%E9%A1%BA%E4%BE%BF%E6%98%BE%E7%A4%BA%E5%BB%B6%E8%BF%9F%E6%97%B6%E9%97%B4%E3%80%82)发一个请求,检测网络是不是通的,顺便显示延迟时间。
命令行和打包
程序支持两种启动方式。无参数启动就是GUI,有参数就进入命令行模式。命令行支持user-id、illust-id、save-folder、max-concurrent这些参数,还有include-tags、exclude-tags、tag-mode、include-titles、exclude-titles这些筛选参数。
打包用的是PyInstaller。字体文件要一起打包,在spec文件里用datas指定。打包之后,exe默认会带一个控制台窗口,我在启动GUI的时候用hide_console把它藏起来了,命令行模式的时候保留,这样用户用参数启动时能看到输出。
最后的东西
到现在程序支持的功能我都列不全了。批量下载画师作品、下载收藏的作品、单独下载一个作品、断点续传、文件去重、作品列表缓存、Aria2支持、直接下载模式、三种发送模式、多套下载方案、掩码系统、自定义掩码、保存路径模板、下载过滤器、代理、OAuth2登录、登录辅助工具、Refresh Token登录、Token自动刷新、动图支持、标签筛选、作品名筛选、自定义筛选表达式、图片代理、连接测试、自定义标题栏、深色主题、圆角窗口、自适应DPI。
整个移植花了好几个月,中间踩了无数个坑。但最后能用上自己做的桌面程序,还是挺有成就感的。