// ==UserScript==
// @name Pixiv 图片批量下载器
// @namespace pixiv-batch-downloader
// @version 2.1
// @description 在Pixiv作品页面添加下载按钮,选择文件夹后逐张下载所有图片到同一目录
// @author WorkBuddy
// @match https://www.pixiv.net/artworks/*
// @grant GM_xmlhttpRequest
// @connect i.pximg.net
// @connect www.pixiv.net
// @license MIT
// ==/UserScript==
(function() {
'use strict';
function createButton() {
const btn = document.createElement('button');
btn.id = 'pxiv-dl-btn';
btn.innerHTML = '📥 下载全部图片';
btn.style.cssText = `
position: fixed;
top: 80px;
right: 20px;
z-index: 99999;
padding: 12px 24px;
background: #0096fa;
color: white;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0,150,250,0.4);
transition: all 0.2s;
`;
btn.onmouseenter = () => btn.style.background = '#0078d4';
btn.onmouseleave = () => btn.style.background = '#0096fa';
return btn;
}
function createProgress() {
const div = document.createElement('div');
div.id = 'pxiv-dl-progress';
div.style.cssText = `
position: fixed;
top: 130px;
right: 20px;
z-index: 99999;
padding: 12px 20px;
background: rgba(0,0,0,0.85);
color: white;
border-radius: 8px;
font-size: 13px;
display: none;
min-width: 240px;
max-width: 340px;
line-height: 1.6;
`;
return div;
}
function sanitizeFilename(name) {
return name.replace(/[<>:"/\\|?*]/g, '_').trim() || 'pixiv_artwork';
}
async function downloadAll() {
const btn = document.getElementById('pxiv-dl-btn');
const progress = document.getElementById('pxiv-dl-progress');
btn.disabled = true;
btn.style.opacity = '0.6';
progress.style.display = 'block';
progress.innerHTML = '';
// 进度文本行
const statusLine = document.createElement('div');
progress.appendChild(statusLine);
// 进度条
const barWrapper = document.createElement('div');
barWrapper.style.cssText = 'margin-top:8px;width:100%;height:6px;background:rgba(255,255,255,0.2);border-radius:3px;overflow:hidden;';
const bar = document.createElement('div');
bar.style.cssText = 'height:100%;background:#00e676;width:0%;border-radius:3px;transition:width 0.3s;';
barWrapper.appendChild(bar);
progress.appendChild(barWrapper);
try {
const match = window.location.href.match(/artworks\/(\d+)/);
if (!match) {
statusLine.textContent = '❌ 无法获取作品ID';
return;
}
const illustId = match[1];
let title = illustId;
const h1 = document.querySelector('h1');
if (h1 && h1.textContent.trim()) {
title = h1.textContent.trim();
}
title = sanitizeFilename(title);
// 获取图片列表
statusLine.textContent = '⏳ 获取图片列表...';
const resp = await fetch('https://www.pixiv.net/ajax/illust/' + illustId + '/pages', {
credentials: 'include'
});
const data = await resp.json();
if (data.error || !data.body) {
statusLine.textContent = '❌ 获取失败: ' + (data.message || '未知错误');
return;
}
const pages = data.body;
const total = pages.length;
if (total === 0) {
statusLine.textContent = '❌ 没有找到图片';
return;
}
// 让用户选择文件夹
statusLine.textContent = '📁 请在弹出的对话框中选择保存文件夹...';
let dirHandle;
try {
dirHandle = await window.showDirectoryPicker({
mode: 'readwrite',
id: 'pixiv-downloads',
startIn: 'downloads'
});
} catch (e) {
statusLine.textContent = '❌ 未选择文件夹,已取消';
return;
}
// 在选定文件夹内创建以作品名命名的子文件夹
const subDir = await dirHandle.getDirectoryHandle(title, { create: true });
statusLine.textContent = '📦 共 ' + total + ' 张,开始下载到 ' + title + '/ 文件夹';
let successCount = 0;
let failCount = 0;
for (let i = 0; i < total; i++) {
const pageNum = i + 1;
const url = pages[i].urls.original;
const ext = url.split('.').pop();
const filename = String(pageNum).padStart(3, '0') + '.' + ext;
statusLine.textContent = '⏳ 下载中 ' + pageNum + '/' + total;
bar.style.width = Math.round((i / total) * 100) + '%';
try {
// 用 GM_xmlhttpRequest 下载图片(绕过CORS,带Referer)
const blob = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Referer': 'https://www.pixiv.net/'
},
responseType: 'blob',
onload: function(response) {
resolve(response.response);
},
onerror: function(err) {
reject(err);
},
ontimeout: function() {
reject(new Error('timeout'));
}
});
});
// 写入文件夹
const fileHandle = await subDir.getFileHandle(filename, { create: true });
const writable = await fileHandle.createWritable();
await writable.write(blob);
await writable.close();
successCount++;
// 适当延迟,避免下载过快
await new Promise(r => setTimeout(r, 300));
} catch (err) {
console.error('Failed:', url, err);
failCount++;
statusLine.textContent = '⚠️ 第 ' + pageNum + ' 张失败,跳过继续...';
await new Promise(r => setTimeout(r, 800));
}
}
bar.style.width = '100%';
let resultMsg = '✅ 完成!成功 ' + successCount + '/' + total;
if (failCount > 0) {
resultMsg += '(失败 ' + failCount + ' 张)';
}
statusLine.innerHTML = resultMsg + '
图片已保存到你选择的文件夹下的「' + title + '」子文件夹中';
setTimeout(() => {
progress.style.display = 'none';
}, 8000);
} catch (e) {
statusLine.textContent = '❌ 出错: ' + e.message;
console.error(e);
} finally {
btn.disabled = false;
btn.style.opacity = '1';
}
}
function init() {
if (document.getElementById('pxiv-dl-btn')) return;
const btn = createButton();
const progress = createProgress();
btn.onclick = downloadAll;
document.body.appendChild(btn);
document.body.appendChild(progress);
}
setInterval(() => {
if (window.location.href.includes('/artworks/') && !document.getElementById('pxiv-dl-btn')) {
init();
}
}, 1000);
})();