Bilibili专栏原图链接提取2024改版

PC端B站专栏图片默认是经压缩过的webp。此脚本帮助用户点击按钮后获取哔哩哔哩专栏中所有原图的直链,方便使用其他工具批量下载原图。

  1. // ==UserScript==
  2. // @name Bilibili专栏原图链接提取2024改版
  3. // @namespace https://github.com/shangxueink
  4. // @version 3.3
  5. // @description PC端B站专栏图片默认是经压缩过的webp。此脚本帮助用户点击按钮后获取哔哩哔哩专栏中所有原图的直链,方便使用其他工具批量下载原图。
  6. // @author shangxueink
  7. // @license GPLv3
  8. // @match https://www.bilibili.com/read/cv*
  9. // @match https://www.bilibili.com/opus/*
  10. // @match https://t.bilibili.com/*
  11. // @match https://space.bilibili.com/*/dynamic
  12. // @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
  13. // @grant none
  14. // @acknowledgement 原始脚本由Hui-Shao开发,本脚本在其基础上进行了修改和增强。(https://gf.qytechs.cn/zh-CN/scripts/456497-bilibili%E4%B8%93%E6%A0%8F%E5%8E%9F%E5%9B%BE%E9%93%BE%E6%8E%A5%E6%8F%90%E5%8F%96 -> https://gf.qytechs.cn/zh-CN/scripts/521666-bilibili%E4%B8%93%E6%A0%8F%E5%8E%9F%E5%9B%BE%E9%93%BE%E6%8E%A5%E6%8F%90%E5%8F%962024%E6%94%B9%E7%89%88)
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. const iconExtractLink = 'https://i0.hdslb.com/bfs/article/7a0cc21280e2ba013d2681cff4dee947312276085.png'; // 提取链接图标
  21. const iconCopyLink = 'https://i0.hdslb.com/bfs/article/cecac694c99629afbe764eb2b2066a46312276085.png'; // 复制链接图标
  22. const iconDownloadEach = 'https://i0.hdslb.com/bfs/article/0896498c861585719a122e0fc6ef5689312276085.png'; // 逐张下载图标
  23.  
  24. function createButton(targetContainer, showDownloadEach, isHorizontal) {
  25. var buttonsContainer = document.createElement("div");
  26. buttonsContainer.style.display = "flex";
  27. buttonsContainer.style.flexDirection = isHorizontal ? "row" : "column";
  28. buttonsContainer.style.alignItems = "center";
  29.  
  30. var existingItem = targetContainer.querySelector('.toolbar-item, .side-toolbar__action, .bili-dyn-item__action');
  31. var height = existingItem ? existingItem.clientHeight : 40;
  32. var width = existingItem ? existingItem.clientWidth : 40;
  33.  
  34. var buttonDownload = document.createElement("button");
  35. buttonDownload.id = "btn001";
  36. buttonDownload.className = "toolbar-item";
  37. buttonDownload.style = setButtonStyle("#C7EDCC", width, height, iconExtractLink);
  38. buttonDownload.onclick = function () {
  39. buttonDownload.disabled = true;
  40. buttonDownload.style.backgroundColor = "#FDE6E0";
  41. buttonDownload.style.backgroundImage = "none"; // Remove icon
  42. buttonDownload.innerHTML = "正在处理,请稍候...";
  43. urlGetAllModes(buttonDownload, true);
  44. };
  45. buttonsContainer.appendChild(buttonDownload);
  46.  
  47. var buttonCopy = document.createElement("button");
  48. buttonCopy.id = "btn002";
  49. buttonCopy.className = "toolbar-item";
  50. buttonCopy.style = setButtonStyle("#E3EDCD", width, height, iconCopyLink);
  51. buttonCopy.onclick = function () {
  52. buttonCopy.disabled = true;
  53. buttonCopy.style.backgroundColor = "#FDE6E0";
  54. buttonCopy.style.backgroundImage = "none"; // Remove icon
  55. buttonCopy.innerHTML = "正在处理,请稍候...";
  56. urlGetAllModes(buttonCopy, false);
  57. };
  58. buttonsContainer.appendChild(buttonCopy);
  59.  
  60. if (showDownloadEach) {
  61. var buttonDownloadEach = document.createElement("button");
  62. buttonDownloadEach.id = "btn003";
  63. buttonDownloadEach.className = "toolbar-item";
  64. buttonDownloadEach.style = setButtonStyle("#FFD700", width, height, iconDownloadEach);
  65. buttonDownloadEach.onclick = function () {
  66. buttonDownloadEach.disabled = true;
  67. buttonDownloadEach.style.backgroundColor = "#FDE6E0";
  68. buttonDownloadEach.style.backgroundImage = "none"; // Remove icon
  69. buttonDownloadEach.innerHTML = "正在下载,请稍候...";
  70. urlGetAllModes(buttonDownloadEach, 'each');
  71. };
  72. buttonsContainer.appendChild(buttonDownloadEach);
  73. }
  74.  
  75. targetContainer.appendChild(buttonsContainer);
  76. }
  77.  
  78. function setButtonStyle(backgroundColor, width, height, icon) {
  79. return `
  80. border-radius: 6px;
  81. margin: 5px 0;
  82. height: ${height}px;
  83. width: ${width}px;
  84. padding: 0px;
  85. background-color: ${backgroundColor};
  86. color: black;
  87. font-weight: bold;
  88. overflow: hidden;
  89. text-align: center;
  90. box-shadow: inset 0 0 20px rgba(255, 255, 255, 1);
  91. background-image: url(${icon});
  92. background-size: 30px 30px; /* 150% of 20px */
  93. background-repeat: no-repeat;
  94. background-position: center;
  95. `;
  96. }
  97.  
  98. function urlGetAllModes(button, mode) {
  99. let modes = [1, 2, 3, 4];
  100. let url_list = [];
  101. let mode_found = false;
  102.  
  103. for (let m of modes) {
  104. let { selector, attribute } = getSelectorAndAttributeByMode(m);
  105. let img_list = document.querySelectorAll(selector);
  106. if (img_list.length > 0) {
  107. mode_found = true;
  108. img_list.forEach(item => {
  109. let text = item.getAttribute(attribute);
  110. if (text && (text.includes('.jpg') || text.includes('.png') || text.includes('.webp') || text.includes('.jpeg') || text.includes('.gif') || text.includes('.bmp'))) {
  111. if (text.startsWith('//')) {
  112. text = 'https:' + text;
  113. }
  114. text = text.split('@')[0];
  115. if (!text.includes('/face') && !text.includes('/garb')) {
  116. url_list.push(text);
  117. }
  118. }
  119. });
  120. }
  121. }
  122.  
  123. // 去重处理
  124. url_list = Array.from(new Set(url_list));
  125.  
  126. if (!mode_found) {
  127. alert("在正文中似乎并没有获取到图片……");
  128. button.textContent = "无图片:点击无效,请刷新重试";
  129. } else {
  130. let url_str = url_list.join("\n");
  131. if (mode === true) {
  132. download_txt("bili_img_urls", url_str);
  133. button.innerHTML = `已提取${url_list.length}张`;
  134. } else if (mode === false) {
  135. copyToClipboard(url_str);
  136. button.innerHTML = `已复制${url_list.length}张`;
  137. } else if (mode === 'each') {
  138. downloadEachImage(url_list);
  139. button.innerHTML = `正在下载`;
  140. }
  141. }
  142. }
  143.  
  144.  
  145. function getSelectorAndAttributeByMode(mode) {
  146. switch (mode) {
  147. case 1:
  148. return { selector: "#article-content img[data-src].normal-img", attribute: "data-src" };
  149. case 2:
  150. return { selector: "#article-content p.normal-img img", attribute: "src" };
  151. case 3:
  152. return { selector: "div.opus-module-content img", attribute: "src" };
  153. case 4:
  154. return { selector: ".dyn-card-opus__pics img", attribute: "src" };
  155. default:
  156. alert("传入模式参数错误!");
  157. return { selector: "", attribute: "" };
  158. }
  159. }
  160.  
  161. function download_txt(filename, text) {
  162. let pom = document.createElement('a');
  163. pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  164. pom.setAttribute('download', filename);
  165. pom.click();
  166. }
  167.  
  168. function downloadEachImage(urls) {
  169. urls.forEach((url, index) => {
  170. setTimeout(() => {
  171. let link = document.createElement('a');
  172. fetch(url)
  173. .then(response => response.blob())
  174. .then(blob => {
  175. let blobUrl = URL.createObjectURL(blob);
  176. link.href = blobUrl;
  177. link.download = url.split('/').pop();
  178. document.body.appendChild(link);
  179. link.click();
  180. document.body.removeChild(link);
  181. URL.revokeObjectURL(blobUrl);
  182. })
  183. .catch(e => console.error('Download failed:', e));
  184. }, index * 100); // 逐张下载,每张间隔0.1秒
  185. });
  186. }
  187.  
  188. function copyToClipboard(text) {
  189. navigator.clipboard.writeText(text).then(() => {
  190. console.log('Text copied to clipboard');
  191. }).catch(err => {
  192. console.error('Failed to copy text: ', err);
  193. });
  194. }
  195.  
  196. function handleDynamicItem(item) {
  197. const footer = item.querySelector('.bili-dyn-item__footer');
  198. const action = footer ? footer.querySelector('.bili-dyn-item__action') : null;
  199. if (footer && action && !footer.querySelector('button')) {
  200. const buttonWidth = action.clientWidth;
  201. const buttonHeight = action.clientHeight;
  202.  
  203. createButton(footer, false, true);
  204.  
  205. const buttonDownload = footer.querySelector("#btn001");
  206. const buttonCopy = footer.querySelector("#btn002");
  207.  
  208. buttonDownload.onclick = () => {
  209. buttonDownload.disabled = true;
  210. buttonDownload.style.backgroundColor = "#FDE6E0";
  211. buttonDownload.style.backgroundImage = "none";
  212. extractDynamicImages(item, true, buttonDownload);
  213. };
  214.  
  215. buttonCopy.onclick = () => {
  216. buttonCopy.disabled = true;
  217. buttonCopy.style.backgroundColor = "#FDE6E0";
  218. buttonCopy.style.backgroundImage = "none";
  219. extractDynamicImages(item, false, buttonCopy);
  220. };
  221. }
  222. }
  223.  
  224. function extractDynamicImages(item, download, button) {
  225. const imgList = item.querySelectorAll('.bili-album__preview__picture__img img, img');
  226. const urlSet = new Set();
  227.  
  228. imgList.forEach(img => {
  229. let src = img.getAttribute('src');
  230. if (src.startsWith('//')) {
  231. src = 'https:' + src;
  232. }
  233. const baseUrl = src.split('@')[0];
  234. if (!baseUrl.includes('/face') && !baseUrl.includes('/garb')) {
  235. urlSet.add(baseUrl);
  236. }
  237. });
  238.  
  239. const urlStr = Array.from(urlSet).join("\n");
  240. if (download) {
  241. download_txt("bili_dyn_img_urls", urlStr);
  242. //button.innerHTML = `${urlSet.size}张`; // 会多一张
  243. button.innerHTML = `已整理`;
  244. } else {
  245. copyToClipboard(urlStr);
  246. //button.innerHTML = `${urlSet.size}张`; // 会多一张
  247. button.innerHTML = `已复制`;
  248. }
  249. }
  250.  
  251. function observeDocument() {
  252. const observer = new MutationObserver(mutations => {
  253. mutations.forEach(mutation => {
  254. mutation.addedNodes.forEach(node => {
  255. if (node.nodeType === 1 && node.matches('.bili-dyn-list__item')) {
  256. handleDynamicItem(node);
  257. }
  258. });
  259. });
  260. });
  261.  
  262. observer.observe(document.body, { childList: true, subtree: true });
  263. }
  264.  
  265. if (window.location.href.includes("space.bilibili.com") && window.location.href.includes("dynamic")) {
  266. observeDocument();
  267. } else if (window.location.href.includes("read/cv")) {
  268. var observer = new MutationObserver(function (mutations, me) {
  269. var toolbar = document.querySelector(".side-toolbar");
  270. if (toolbar) {
  271. createButton(toolbar, false, false); // 不显示第三个按钮
  272. me.disconnect();
  273. return;
  274. }
  275. });
  276. observer.observe(document.body, { childList: true, subtree: true });
  277. } else if (window.location.href.includes("opus/")) {
  278. var observerOpus = new MutationObserver(function (mutations, me) {
  279. var toolbarOpus = document.querySelector(".side-toolbar__box");
  280. if (toolbarOpus) {
  281. var showDownloadEach = window.location.href.includes("opus/");
  282. createButton(toolbarOpus, showDownloadEach, false); // 根据 URL 决定是否显示第三个按钮
  283. me.disconnect();
  284. return;
  285. }
  286. });
  287. observerOpus.observe(document.body, { childList: true, subtree: true });
  288. } else if (window.location.href.includes("t.bilibili.com")) {
  289. var observerT = new MutationObserver(function (mutations, me) {
  290. var toolbarT = document.querySelector(".side-toolbar__box");
  291. if (toolbarT) {
  292. createButton(toolbarT, true, false);
  293. me.disconnect();
  294. return;
  295. }
  296. });
  297. observerT.observe(document.body, { childList: true, subtree: true });
  298. }
  299. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址