您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
simple script to download multiple arts from pixiv illustration
// ==UserScript== // @name pixiv bulk downloader // @description simple script to download multiple arts from pixiv illustration // @version 0.0.2 // @namespace owowed.moe // @author owowed // @license GPL-3.0-or-later // @match *://www.pixiv.net/* // @require https://update.gf.qytechs.cn/scripts/488160/1335044/make-mutation-observer.js // @require https://update.gf.qytechs.cn/scripts/488161/1335046/wait-for-element.js // @grant GM_getValue // @grant GM_setValue // @grant GM_download // @run-at document-end // @copyright All rights reserved. Licensed under GPL-3.0-or-later. View license at https://spdx.org/licenses/GPL-3.0-or-later.html // ==/UserScript== ;3 ;3 ;3 !async function() { // main async function /* Pixiv Website Navigation Event */ const navigationEvent = new EventTarget(); const charcoal = await waitForElement(".charcoal-token > div > div[style]:not([class])"); // Charcoal Navigation let lastHrefDispatched; makeMutationObserverOptions( { target: charcoal, childList: true, attributes: true }, () => { if (lastHrefDispatched != window.location.href) { navigationEvent.dispatchEvent(new Event("charcoal-navigate")); lastHrefDispatched = window.location.href; } } ); setTimeout(() => { if (document.readyState == "loading") { document.addEventListener("DOMContentLoaded", () => { dispatchCharcoalNavigateEvent(); }); } else { dispatchCharcoalNavigateEvent(); } }); // Illustration Navigation navigationEvent.addEventListener("charcoal-navigate", async () => { if (!window.location.href.includes("/artworks/")) return; navigationEvent.dispatchEvent(new Event("illust-open")); navigationEvent.dispatchEvent(new Event("illust-navigate")); const illustAnchor = await waitForElement("figure:has(~ figcaption)"); let lastWindowHref = window.location.href; const observer = makeMutationObserverOptions({ target: illustAnchor, childList: true, subtree: true }, () => { if (lastWindowHref == window.location.href) return; navigationEvent.dispatchEvent(new Event("illust-navigate")); lastWindowHref = window.location.href; }); navigationEvent.addEventListener("charcoal-navigate", () => { observer.disconnect(); navigationEvent.dispatchEvent(new Event("illust-close")); }, { once: true }); }); /* Pixiv Bulk Downloader */ // Downloader Box const filenameTemplateVariablesGuide = "" + "/illust-id/ Numeric id for the illustration\n" + "/illust-title/ Illustration title\n" + "/illust-tags-short/ Short tags derived from the illustration title\n" + "/illust-original/ If the illustration is tagged original, then it is written 'original', otherwise it is 'non-original'\n" + "/illust-author-name/ Author name\n" + "/illust-author-id/ Numeric id for the author\n" + "/illust-like-num/ Illustration like count\n" + "/illust-bookmark-num/ Illustration bookmark count\n" + "/illust-view-num/ Illustration view count\n" + "/illust-datetime/ Illustration posting date and time in long format\n" + "/illust-datetime-hours-24/ Illustration posting 24-hour format\n" + "/illust-datetime-hours/ Illustration posting 12-hour format\n" + "/illust-datetime-minutes/ Illustration posting minutes\n" + "/illust-datetime-seconds/ Illustration posting seconds\n" + "/illust-datetime-ampm/ Illustration posting AM/PM\n" + "/illust-datetime-date/ Illustration posting date\n" + "/illust-datetime-day/ Illustration posting day of the week as a number\n" + "/illust-datetime-month/ Illustration posting month as a number\n" + "/illust-datetime-year/ Illustration posting year\n" + "/illust-datetime-day-name/ Illustration posting day of the week as a name\n" + "/illust-datetime-month-name/ Illustration posting month as a name\n" + "/illust-datetime-timestamp/ Illustration posting timestamp\n" + "/current-datetime/ Current date and time in long format\n" + "/current-datetime-{...}/ Same as 'illust-datetime-{...}'\n" + "/artwork-quality/ Selected artwork quality\n" + "/artwork-part/ Selected artwork part\n" + "/artwork-parts-num/ Artwork total part count\n" + "/file-extension/ File extension for the image\n" + "/file-url-name/ File name from the source URL\n" + "/file-url-datetime/ File URL's associated date and time in long format\n" + "/file-url-datetime-{...}/ Same as 'illust-datetime-{...}'\n" + "/website-title/ Website title when it was downloaded\n" + "/website-lang/ Website language from the website URL"; const defaultFilenameTemplate = "/illust-title/ by /illust-author-name/ #/artwork-part/ (/illust-tags-short/) [pixiv /illust-id/]./file-extension/"; const { parent, shadow } = createShadowDom(` <button id="pbd-btn" class="btn-green expander closed"> [+] pbd </button> <div id="pbd-box" class="popup" hidden> <h1>pivix bulk downloader</h1> <span class="note">userscript made by owowed</span> <div> <h2>Filename Template</h2> <div>Naming format for the filename. Include artwork info: author name, creation date, etc.</div> <textarea id="filename-template" cols="70" spellcheck="false">${GM_getValue("filename-template", defaultFilenameTemplate)}</textarea> </div> <div> <h2>Artwork Quality</h2> <div>Select artwork quality to download.</div> <select id="artwork-quality-selector"> <option value="original">Original (best)</option> <option value="regular">Regular</option> <option value="small">Small</option> <option value="thumb_mini">Thumbnail Mini</option> </select> </div> <div id="selected-artwork-part-entry"> <h2>Selected Artwork Part</h2> <div>Illustration can have multiple artworks (comic, doujin, etc.), you can manully select or bulk download them.</div> <select id="artwork-part-selector"></select> <div id="bulk-range" hidden> From: <select id="bulk-from"></select> To: <select id="bulk-to"></select> </div> </div> <!-- Author Page Exclusive --> <div class="artist-filter" hidden> <h2>Illustration Date</h2> <div>Select illustrations to download from the posting date</div> From: <input type="date" /> To: <input type="date" /> </div> <div class="artist-filter" hidden> <h2>Illustration Tags</h2> <div>Select illustrations to download from inclusion/exclusion of tags</div> <div class="tags-row"> <div> Inclusion: <div class="tag-list"> <div class="added-tags"> <input type="text" value="touhou-project" readonly/> <input type="text" value="satori-komeiji" readonly/> </div> <input type="text"/> </div> </div> <div> Exclusion: <div class="tag-list"> <div class="added-tags"> <input type="text" value="touhou-project" readonly/> <input type="text" value="satori-komeiji" readonly/> </div> <input type="text"/> </div> </div> </div> </div> <style> :not([data-page="artist"]) .artist-filter { display: none; } </style> <div> <button id="btn-download">Start Download</button> </div> <div class="popup-footer"> <button id="logs-btn" class="btn-green" hidden>[?] Logs</button> <button id="filename-template-variable-list-btn" class="btn-green">[?] Filename Template Variable List</button> </div> <div id="filename-template-variable-list-guide" class="popup" hidden> <pre class="guide-title">Filename Template Variables</pre> <pre class="guide-body">${filenameTemplateVariablesGuide}</pre> </div> </div> <style> #pbd-btn { margin: 12px 0 4px 0; } .popup { background: #E3E0D1; font-family: arial,helvetica,sans-serif; color: black; text-align: center; border: 2px solid grey; padding: 10px; margin: 4px 0; resize: both; overflow: auto; z-index: 100; } .popup > *:not(:first-child) { margin: 10px 0; } .popup-footer { text-align: left; } .expander { display: inline-block; padding: 5px; resize: none; } .expander.closed { font-weight: bold; } .btn-green { background-color: #edebdf; color: black; border: 2px solid grey; cursor: pointer; } pre.guide-title { text-align: center; } pre.guide-body { margin-left: 2.4cm; text-align: start; } .tags-row { display: flex; flex-direction: row; justify-content: center; gap: 14px; } .tag-list { display: flex; flex-direction: column; width: min-content; /* margin: auto; */ } .tag-list .added-tags { max-height: 100px; overflow: auto; } h1, h2, h3, h4 { all: unset; display: block; } h1 { font-weight: bold; font-style: italic; font-size: 14pt; } h2 { font-size: 12pt; } .note { font-style: italic; } </style> `); const ftvlButton = shadow.getElementById("filename-template-variable-list-btn"); const ftvlContainer = shadow.getElementById("filename-template-variable-list-guide"); ftvlButton.addEventListener("click", () => { ftvlContainer.hidden = !ftvlContainer.hidden; }); const pbdButton = shadow.getElementById("pbd-btn"); const pbdBox = shadow.getElementById("pbd-box"); pbdButton.addEventListener("click", () => { pbdBox.hidden = !pbdBox.hidden; pbdBox.classList.toggle("closed"); pbdButton.textContent = `[${pbdBox.hidden ? "+" : "-"}] pbd`; }); const filenameTemplateTextarea = shadow.getElementById("filename-template"); GM_setValue("filename-template", filenameTemplateTextarea.value) filenameTemplateTextarea.addEventListener("change", () => { GM_setValue("filename-template", filenameTemplateTextarea.value) }); navigationEvent.addEventListener("illust-navigate", async () => { selectedArtworkPartEntry.hidden = true; const caption = await waitForElement("figure ~ figcaption > div:has(div footer)", { parent: charcoal }); const column = caption.children[0]; column.append(parent); }); // Artwork Selector & Artwork Quality const selectedArtworkPartEntry = shadow.getElementById("selected-artwork-part-entry"); const artworkPartSelector = shadow.getElementById("artwork-part-selector"); const bulkRangeContainer = shadow.getElementById("bulk-range"); const bulkFromSelector = shadow.getElementById("bulk-from"); const bulkToSelector = shadow.getElementById("bulk-to"); const artworkQualitySelector = shadow.getElementById("artwork-quality-selector"); let artworkPartSelectorDict = {}; navigationEvent.addEventListener("illust-navigate", async () => { pbdBox.dataset.page = "illust"; const pixivIllustPagesUrl = `https://www.pixiv.net/ajax/illust/${window.location.pathname.split("/").slice(-1)}/pages?lang=en`; artworkPartSelector.replaceChildren(); // remove all children bulkFromSelector.replaceChildren(); bulkToSelector.replaceChildren(); artworkPartSelectorDict = {}; bulkRangeContainer.hidden = true; const illustPages = await fetch(pixivIllustPagesUrl, { headers: { Accept: "application/json", Referer: window.location.href } }).then(i => i.json()); let counter = 0; for (const { urls, width, height } of illustPages.body) { const artworkPartOption = Object.assign(document.createElement("option"), { textContent: `p${counter}: ${width}x${height}`, value: urls.original }); artworkPartSelector.append(artworkPartOption); artworkPartSelectorDict[urls.original] = { urls, width, height }; const bulkFromToNumOption = Object.assign(document.createElement("option"), { textContent: counter, value: counter }); bulkFromSelector.append(bulkFromToNumOption); bulkToSelector.append(bulkFromToNumOption.cloneNode(true)); counter++; } if (counter > 1) { selectedArtworkPartEntry.hidden = false; } bulkToSelector.value = counter; const bulkDownloadOption = Object.assign(document.createElement("option"), { id: "bulk-download", textContent: "Bulk Download", value: "bulk-download", }); artworkPartSelector.append(bulkDownloadOption); }); // Download Button const downloadButton = shadow.getElementById("btn-download"); downloadButton.addEventListener("click", () => { const filenameTemplate = filenameTemplateTextarea.value; if (artworkPartSelector.value == "bulk-download") { for (const imageUrl of Object.keys(artworkPartSelectorDict)) { downloadIllust(imageUrl, filenameTemplate); } } else { downloadIllust(artworkPartSelector.value, filenameTemplate); } }); function downloadIllust(imageUrl, filenameTemplate) { const downloadUrl = artworkPartSelectorDict[imageUrl].urls[artworkQualitySelector.value]; const dictionary = { ...getIllustDictionary(), ...getDownloadDictionary({ url: new URL(downloadUrl), artworkQuality: artworkQualitySelector.value, artworkPart: Object.keys(artworkPartSelectorDict).findIndex(i => i == imageUrl), artworkPartTotal: Object.keys(artworkPartSelectorDict).length }) }; GM_download({ url: downloadUrl, name: formatTemplate(filenameTemplate, dictionary), headers: { Referer: window.location.href }, saveAs: false }); } // Artist Filter navigationEvent.addEventListener("charcoal-navigate", () => { pbdBox.dataset.page = "artist"; }); }(); // main async function function createShadowDom(innerHTML) { const parent = document.createElement("div"); const shadow = parent.attachShadow({ mode: "closed" }); shadow.innerHTML = innerHTML; return { parent, shadow }; } // Filename Template Functions function formatTemplate(template, dictionary, { matcher = "/{{@.-}}/" } = {}) { let formatted = template; for (const [k, v] of Object.entries(dictionary)) { formatted = formatted.replace(matcher.replace("{{@.-}}", k), v); } return formatted; } function getIllustDictionary() { const authorAsideProfile = document.querySelector("aside h2 > div [data-gtm-value]:has([title])"); const authorName = authorAsideProfile.querySelector("[title]").getAttribute("title"); const illustPostingDate = new Date(document.querySelector("time[title='Posting date']").dateTime); return { // illustration info "illust-id": window.location.pathname.split("/").slice(-1)[0], "illust-title": document.title.split("/").slice(1).join("/").slice(1).split(" - pixiv")[0], "illust-tags-short": document.title.split("/")[0].slice(0,-1), "illust-original": document.querySelector("figure ~ figcaption [href*='オリジナル']") ? "original" : "non-original", "illust-author-name": authorName, "illust-author-id": authorAsideProfile.dataset.gtmValue, // illustration social stats "illust-like-num": document.querySelector("dd[title='Like']").textContent, "illust-bookmark-num": document.querySelector("dd[title='Bookmarks']").textContent, "illust-view-num": document.querySelector("dd[title='Views']").textContent, // illustration posting datetime ...getDateTimeDictionary("illust", illustPostingDate), // current datetime ...getDateTimeDictionary("current", new Date), // website info "website-title": document.title, "website-lang": window.location.pathname.split("/")[1], }; } function getDownloadDictionary(context) { const fileUrlName = context.url.pathname.split("/").slice(-1)[0]; const urlDateArray = context.url.pathname.split("/img/")[1].split("/").slice(0, 6); const urlDate = new Date(urlDateArray.slice(0,3).join("-") + "T" + urlDateArray.slice(3).join(":") + "+09:00"); return { // artwork "artwork-quality": context.artworkQuality, "artwork-part": context.artworkPart, "artwork-parts-num": context.artworkPartTotal, // file info "file-extension": fileUrlName.split(".").slice(-1)[0], "file-url-name": fileUrlName, ...getDateTimeDictionary("file-url", urlDate), }; } function getDateTimeDictionary(namespace, date) { return { [`${namespace}-datetime`]: date.toLocaleString("default", { dateStyle: "long" }), [`${namespace}-datetime-hours-24`]: date.getHours(), [`${namespace}-datetime-hours`]: Math.abs(date.getHours() % 12 || 12), [`${namespace}-datetime-minutes`]: date.getMinutes(), [`${namespace}-datetime-seconds`]: date.getSeconds(), [`${namespace}-datetime-ampm`]: date.getHours() >= 12 ? "PM" : "AM", [`${namespace}-datetime-date`]: date.getDate(), [`${namespace}-datetime-day`]: date.getDay(), [`${namespace}-datetime-month`]: date.getMonth() + 1, [`${namespace}-datetime-year`]: date.getFullYear(), [`${namespace}-datetime-day-name`]: date.toLocaleString("default", { weekday: "long" }), [`${namespace}-datetime-month-name`]: date.toLocaleString("default", { month: "long" }), [`${namespace}-datetime-timestamp`]: date.getTime(), }; }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址