微信公众号网页批量保存图片浏览器控制台命令代码

V wwwroot  (UID: 4691) [复制链接]
帖子链接已复制到剪贴板
帖子已经有人评论啦,不支持删除!

52 0

lanzoum.com/igBA53zz1eze

代码:

(async function () {
    const MAX_IMAGES = 999;
    const ZIP_NAME = "rich_pages_images.zip";
    const REMOVE_DUPLICATES = false;
  
    // 兼容:
    // <img class="rich_pages wxw-img">
    // .rich_pages 内部的 .wxw-img
    // <wxw-img> 自定义标签
    const elements = [
      ...document.querySelectorAll(
        "img.rich_pages.wxw-img, .rich_pages.wxw-img, .rich_pages .wxw-img, wxw-img"
      )
    ];
  
    function getImageUrl(element) {
      const innerImage =
        element.tagName === "IMG"
          ? element
          : element.querySelector("img");
  
      const targets = [element, innerImage].filter(Boolean);
  
      for (const target of targets) {
        const sources = [
          target.getAttribute("data-src"),
          target.getAttribute("data-original"),
          target.getAttribute("original-src"),
          target.getAttribute("data-backsrc"),
          target.getAttribute("src"),
          target.currentSrc,
          target.src
        ];
  
        for (const source of sources) {
          if (!source) continue;
  
          const value = source.trim();
  
          // 跳过透明占位图
          if (
            value.startsWith("data:image/gif") &&
            value.length < 500
          ) {
            continue;
          }
  
          try {
            const parsedUrl = new URL(value, location.href);
  
            // 删除 #imgIndex=24 等锚点
            parsedUrl.hash = "";
  
            return parsedUrl.href;
          } catch {
            return value.split("#")[0];
          }
        }
      }
  
      return "";
    }
  
    let imageUrls = elements
      .map(getImageUrl)
      .filter(Boolean);
  
    if (REMOVE_DUPLICATES) {
      imageUrls = [...new Set(imageUrls)];
    }
  
    imageUrls = imageUrls.slice(0, MAX_IMAGES);
  
    if (!imageUrls.length) {
      console.error("没有找到符合条件的图片。");
      alert("没有找到符合条件的图片。");
      return;
    }
  
    const progressBox = document.createElement("div");
  
    Object.assign(progressBox.style, {
      position: "fixed",
      right: "20px",
      bottom: "20px",
      zIndex: "2147483647",
      padding: "12px 18px",
      background: "rgba(0,0,0,.85)",
      color: "#fff",
      borderRadius: "8px",
      fontSize: "14px",
      lineHeight: "1.6",
      fontFamily: "Arial, sans-serif",
      maxWidth: "380px",
      whiteSpace: "pre-wrap",
      wordBreak: "break-all",
      boxShadow: "0 4px 20px rgba(0,0,0,.3)"
    });
  
    document.body.appendChild(progressBox);
  
    function setProgress(text) {
      progressBox.textContent = text;
      console.log(text);
    }
  
    function getExtension(url, contentType) {
      const mimeMap = {
        "image/jpeg": "jpg",
        "image/jpg": "jpg",
        "image/png": "png",
        "image/webp": "webp",
        "image/gif": "gif",
        "image/bmp": "bmp",
        "image/svg+xml": "svg",
        "image/avif": "avif",
        "image/tiff": "tif"
      };
  
      const mime = String(contentType || "")
        .split(";")[0]
        .trim()
        .toLowerCase();
  
      if (mimeMap[mime]) {
        return mimeMap[mime];
      }
  
      try {
        const pathname = new URL(url).pathname;
        const match = pathname.match(
          /\.([a-zA-Z0-9]+)$/
        );
  
        if (match) {
          let extension = match[1].toLowerCase();
  
          if (extension === "jpeg") extension = "jpg";
          if (extension === "tiff") extension = "tif";
  
          if (
            [
              "jpg",
              "png",
              "webp",
              "gif",
              "bmp",
              "svg",
              "avif",
              "tif"
            ].includes(extension)
          ) {
            return extension;
          }
        }
      } catch {}
  
      return "jpg";
    }
  
    // ZIP 所需 CRC32
    const crcTable = new Uint32Array(256);
  
    for (let i = 0; i < 256; i++) {
      let value = i;
  
      for (let j = 0; j < 8; j++) {
        value =
          value & 1
            ? 0xedb88320 ^ (value >>> 1)
            : value >>> 1;
      }
  
      crcTable[i] = value >>> 0;
    }
  
    function crc32(bytes) {
      let crc = 0xffffffff;
  
      for (const byte of bytes) {
        crc =
          crcTable[(crc ^ byte) & 0xff] ^
          (crc >>> 8);
      }
  
      return (crc ^ 0xffffffff) >>> 0;
    }
  
    function uint16(value) {
      return new Uint8Array([
        value & 0xff,
        (value >>> 8) & 0xff
      ]);
    }
  
    function uint32(value) {
      return new Uint8Array([
        value & 0xff,
        (value >>> 8) & 0xff,
        (value >>> 16) & 0xff,
        (value >>> 24) & 0xff
      ]);
    }
  
    function joinArrays(arrays) {
      const totalLength = arrays.reduce(
        (total, array) => total + array.length,
        0
      );
  
      const result = new Uint8Array(totalLength);
      let offset = 0;
  
      for (const array of arrays) {
        result.set(array, offset);
        offset += array.length;
      }
  
      return result;
    }
  
    function getDosDateTime(date = new Date()) {
      const year = Math.max(1980, date.getFullYear());
  
      const dosTime =
        (date.getHours() << 11) |
        (date.getMinutes() << 5) |
        Math.floor(date.getSeconds() / 2);
  
      const dosDate =
        ((year - 1980) << 9) |
        ((date.getMonth() + 1) << 5) |
        date.getDate();
  
      return {
        time: dosTime,
        date: dosDate
      };
    }
  
    async function compressData(originalBytes) {
      // 浏览器支持时进行 ZIP Deflate 压缩
      if ("CompressionStream" in window) {
        try {
          const sourceStream =
            new Blob([originalBytes]).stream();
  
          const compressionStream =
            new CompressionStream("deflate-raw");
  
          const compressedStream =
            sourceStream.pipeThrough(compressionStream);
  
          const compressedBytes = new Uint8Array(
            await new Response(
              compressedStream
            ).arrayBuffer()
          );
  
          // 压缩后更小时才采用
          if (
            compressedBytes.length <
            originalBytes.length
          ) {
            return {
              method: 8,
              bytes: compressedBytes
            };
          }
        } catch (error) {
          console.warn(
            "浏览器不支持 deflate-raw,将使用原始方式打包:",
            error
          );
        }
      }
  
      return {
        method: 0,
        bytes: originalBytes
      };
    }
  
    async function createZip(files) {
      const localParts = [];
      const centralParts = [];
  
      let localOffset = 0;
      let centralSize = 0;
  
      for (
        let index = 0;
        index < files.length;
        index++
      ) {
        const file = files[index];
        const fileNameBytes =
          new TextEncoder().encode(file.name);
  
        const originalBytes = file.bytes;
        const checksum = crc32(originalBytes);
        const compressed =
          await compressData(originalBytes);
  
        const dos = getDosDateTime();
  
        setProgress(
          `正在压缩 ${index + 1}/${files.length}\n${file.name}`
        );
  
        const localHeader = joinArrays([
          uint32(0x04034b50),
          uint16(20),
          uint16(0x0800),
          uint16(compressed.method),
          uint16(dos.time),
          uint16(dos.date),
          uint32(checksum),
          uint32(compressed.bytes.length),
          uint32(originalBytes.length),
          uint16(fileNameBytes.length),
          uint16(0),
          fileNameBytes
        ]);
  
        localParts.push(
          localHeader,
          compressed.bytes
        );
  
        const centralHeader = joinArrays([
          uint32(0x02014b50),
          uint16(20),
          uint16(20),
          uint16(0x0800),
          uint16(compressed.method),
          uint16(dos.time),
          uint16(dos.date),
          uint32(checksum),
          uint32(compressed.bytes.length),
          uint32(originalBytes.length),
          uint16(fileNameBytes.length),
          uint16(0),
          uint16(0),
          uint16(0),
          uint16(0),
          uint32(0),
          uint32(localOffset),
          fileNameBytes
        ]);
  
        centralParts.push(centralHeader);
        centralSize += centralHeader.length;
  
        localOffset +=
          localHeader.length +
          compressed.bytes.length;
      }
  
      const endRecord = joinArrays([
        uint32(0x06054b50),
        uint16(0),
        uint16(0),
        uint16(files.length),
        uint16(files.length),
        uint32(centralSize),
        uint32(localOffset),
        uint16(0)
      ]);
  
      return new Blob(
        [
          ...localParts,
          ...centralParts,
          endRecord
        ],
        {
          type: "application/zip"
        }
      );
    }
  
    async function fetchImage(url) {
      const parsedUrl = new URL(url, location.href);
      parsedUrl.hash = "";
  
      /*
       * 关键修改:
       * credentials: "omit"
       *
       * 不向微信图片 CDN 发送当前网页 Cookie,
       * 避免 Access-Control-Allow-Credentials 报错。
       */
      const response = await fetch(parsedUrl.href, {
        method: "GET",
        mode: "cors",
        credentials: "omit",
        cache: "force-cache",
        referrerPolicy: "no-referrer"
      });
  
      if (!response.ok) {
        throw new Error(
          `HTTP ${response.status} ${response.statusText}`
        );
      }
  
      return response;
    }
  
    const downloadedFiles = [];
    const failedUrls = [];
  
    setProgress(
      `共找到 ${imageUrls.length} 张图片\n开始获取……`
    );
  
    for (
      let index = 0;
      index < imageUrls.length;
      index++
    ) {
      const url = imageUrls[index];
  
      setProgress(
        `正在获取 ${index + 1}/${imageUrls.length}\n` +
        `成功:${downloadedFiles.length}\n` +
        `失败:${failedUrls.length}`
      );
  
      try {
        const response = await fetchImage(url);
        const blob = await response.blob();
  
        if (!blob.size) {
          throw new Error("图片内容为空");
        }
  
        if (
          blob.type &&
          !blob.type.startsWith("image/")
        ) {
          console.warn(
            `第 ${index + 1} 个资源类型不是图片:`,
            blob.type,
            url
          );
        }
  
        const extension = getExtension(
          url,
          blob.type
        );
  
        const bytes = new Uint8Array(
          await blob.arrayBuffer()
        );
  
        // 按成功顺序连续命名:1、2、3……
        downloadedFiles.push({
          name: `${downloadedFiles.length + 1}.${extension}`,
          bytes
        });
      } catch (error) {
        console.error(
          `第 ${index + 1} 张图片获取失败:`,
          url,
          error
        );
  
        failedUrls.push(
          `${index + 1}. ${url}\n错误:${error.message}`
        );
      }
    }
  
    if (!downloadedFiles.length) {
      setProgress(
        "所有图片均获取失败。\n请查看控制台中的具体错误。"
      );
  
      alert(
        "所有图片均获取失败,请查看控制台中的错误信息。"
      );
  
      return;
    }
  
    const successCount = downloadedFiles.length;
  
    if (failedUrls.length) {
      downloadedFiles.push({
        name: "下载失败记录.txt",
        bytes: new TextEncoder().encode(
          "以下图片获取失败:\n\n" +
          failedUrls.join("\n\n")
        )
      });
    }
  
    setProgress(
      `图片获取完成\n` +
      `成功:${successCount} 张\n` +
      `失败:${failedUrls.length} 张\n` +
      `正在生成 ZIP……`
    );
  
    const zipBlob =
      await createZip(downloadedFiles);
  
    const downloadUrl =
      URL.createObjectURL(zipBlob);
  
    const link = document.createElement("a");
  
    link.href = downloadUrl;
    link.download = ZIP_NAME;
    link.style.display = "none";
  
    document.body.appendChild(link);
    link.click();
    link.remove();
  
    setTimeout(() => {
      URL.revokeObjectURL(downloadUrl);
    }, 30000);
  
    setProgress(
      `处理完成!\n` +
      `成功:${successCount} 张\n` +
      `失败:${failedUrls.length} 张\n` +
      `文件:${ZIP_NAME}`
    );
  
    setTimeout(() => {
      progressBox.remove();
    }, 10000);
  })();

ai写的,存个档省去下次再造轮子的问题(表情

这家伙太懒了,什么也没留下。
已有评论 ( 0 )
提示:您必须 登录 才能查看此内容。
域名市场
   域名载入中...
创建新帖
自助推广购买广告位
确认删除
确定要删除这篇帖子吗?删除后将无法恢复。
删除成功
帖子已成功删除,页面将自动刷新。
删除失败
删除帖子时发生错误,请稍后再试。