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

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

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

QingJ © 2025

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