吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7897|回复: 27
上一主题 下一主题
收起左侧

[其他转载] [油猴脚本] 小红书一键导出收藏+一键下载单个笔记内容

   关闭 [复制链接]
跳转到指定楼层
楼主
ovioni 发表于 2023-9-26 13:40 回帖奖励
本帖最后由 bootislands 于 2023-9-26 13:42 编辑

该脚本在电脑端使用。功能主要分两部分:
1. 「导出小红书账号里的收藏」。使用方法可以看作者录制的教程视频:https://www.bilibili.com/video/BV1Fo4y177pv/
主要是导出你自己的收藏。如果别人的收藏公开可见的话,也可以导出别人的。
2. 「一键下载单条笔记的内容(包括图片 视频 笔记文字,但不包括评论区)」。使用方法:点进单个笔记的页面,右下角就会出现下载按钮

楼主个人很喜欢这个脚本,因为理论上它够安全,不像一些爬虫大批量发送请求(这样很容易被封号),相反,功能1只抓取收藏页面的现成数据,不额外再发请求,功能2也只是下载单条笔记内容,因此很安全(只是理论上,万一你中奖了别找楼主负责)

注:这个脚本不能批量爬取某个博主发的笔记。如果你需要这种高风险功能,可以看看另一个项目:https://github.com/xisuo67/XHS-Spider 它功能更强,相应地被封号概率也更高

安装地址:https://greasyfork.org/zh-CN/scripts/464664



脚本代码:
// ==UserScript==
// @name         小红书转发
// @namespace    https://mundane.ink/redbook
// @version      2.3
// @description  在浏览小红书收藏时将数据转发到https://mundane.ink/redbook/index.html,方便收藏的管理和导出
// @match        https://www.xiaohongshu.com/*
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";
  console.log("小红书脚本生效了");
  const baseUrl = "https://mundane.ink";
  // const baseUrl = "http://localhost:8088";
  document.body.addEventListener("click", (e) => {
    if (
      e.target.tagName === "A" &&
      e.target.classList.value.includes("cover ld mask")
    ) {
      setTimeout(() => {
        const href = e.target.href;
        const noteId = href.match(/\/([^/]+)$/)[1];
        if (noteId) {
          createDownloadMdButton(noteId);
          createMediaButton(noteId);
        }
      }, 1000);
    }
  });

  // 创建下载md按钮
  function createDownloadMdButton(noteId) {
    const mask = document.querySelector("div.note-detail-mask");
    const button = document.createElement("button");
    button.textContent = "下载md文件";
    button.style.position = "fixed";
    button.style.bottom = "65px";
    button.style.right = "20px";
    button.style.padding = "10px 20px";
    button.style.border = "none";
    button.style.backgroundColor = "#056b00";
    button.style.color = "#fff";
    button.style.fontFamily = "Arial, sans-serif";
    button.style.fontSize = "16px";
    button.style.fontWeight = "bold";
    button.style.cursor = "pointer";
    button.addEventListener("click", function () {
      exportMd(noteId);
    });
    mask.appendChild(button);
  }

  // 创建下载图片和视频按钮
  function createMediaButton(noteId) {
    const mask = document.querySelector("div.note-detail-mask");
    const button = document.createElement("button");
    button.textContent = "下载图片和视频";
    button.style.position = "fixed";
    button.style.bottom = "20px";
    button.style.right = "20px";
    button.style.padding = "10px 20px";
    button.style.border = "none";
    button.style.backgroundColor = "#056b00";
    button.style.color = "#fff";
    button.style.fontFamily = "Arial, sans-serif";
    button.style.fontSize = "16px";
    button.style.fontWeight = "bold";
    button.style.cursor = "pointer";
    button.addEventListener("click", function () {
      getMedia(noteId);
    });
    mask.appendChild(button);
  }

  function getMedia(noteId) {
    fetch(`${baseUrl}/mail/redbook/note/getMediaInfo`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ noteId }),
    })
      .then((response) => {
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        // 解析响应数据为 JSON 格式
        return response.json();
      })
      .then((resp) => {
        if (resp.code === 200) {
          exportMedia(resp.data);
        }
      })
      .catch((error) => console.error(error));
  }

  function exportMedia(data) {
    const { title, videoUrl, imageUrls } = data;
    exportImages(title, imageUrls);
    if (videoUrl) {
      exportVideo(title, videoUrl);
    }
  }

  function exportVideo(title, videoUrl) {
    fetch(videoUrl)
      .then((response) => {
        return response.blob();
      })
      .then((blob) => {
        // 创建一个下载链接
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = title + ".mp4";

        document.body.appendChild(a);

        // 模拟点击下载链接
        a.click();

        // 清理对象 URL
        URL.revokeObjectURL(url);
      })
      .catch((error) => console.error(error));
  }

  function exportImages(title, imageUrls) {
    for (let i = 0; i < imageUrls.length; i++) {
      const imageUrl = imageUrls[i];
      fetch(imageUrl)
        .then((response) => response.blob())
        .then((blob) => {
          const imageURL = URL.createObjectURL(blob);
          const a = document.createElement("a");
          a.href = imageURL;
          a.download = title + "-" + (i + 1) + ".png";
          a.click();
          URL.revokeObjectURL(imageURL);
        });
    }
  }

  function exportMd(noteId) {
    fetch(`${baseUrl}/mail/redbook/note/exportNoteMd`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ noteId }),
    })
      .then((response) => {
        const contentDisposition = response.headers.get("Content-Disposition");
        const filenameMatch = decodeURIComponent(
          contentDisposition.match(/filename\=(.*)/)[1]
        );
        const filename = filenameMatch || "filename.md";
        response.blob().then((blob) => {
          const url = window.URL.createObjectURL(blob);
          const a = document.createElement("a");
          a.href = url;
          a.download = filename;
          document.body.appendChild(a);
          a.click();
          a.remove();
        });
      })
      .catch((error) => console.error(error));
  }

  // 创建按钮元素
  const btnScroll = document.createElement("button");
  btnScroll.innerHTML = "自动滚动";
  const btnJump = document.createElement("button");
  btnJump.innerHTML = "去下载";
  const btnTest = document.createElement("button");
  btnTest.innerHTML = "测试";

  // 设置按钮样式
  btnScroll.style.position = "fixed";
  btnScroll.style.top = "160px";
  btnScroll.style.right = "20px";
  btnScroll.style.backgroundColor = "#056b00";
  btnScroll.style.color = "#fff";
  btnScroll.style.padding = "8px";
  btnScroll.style.borderRadius = "6px";
  btnScroll.style.zIndex = "1000";

  btnJump.style.position = "fixed";
  btnJump.style.top = "210px";
  btnJump.style.right = "20px";
  btnJump.style.backgroundColor = "#056b00";
  btnJump.style.color = "#fff";
  btnJump.style.padding = "8px";
  btnJump.style.borderRadius = "6px";
  btnJump.style.zIndex = "1000";

  btnTest.style.position = "fixed";
  btnTest.style.top = "260px";
  btnTest.style.right = "20px";
  btnTest.style.backgroundColor = "#056b00";
  btnTest.style.color = "#fff";
  btnTest.style.padding = "8px";
  btnTest.style.borderRadius = "6px";
  btnTest.style.zIndex = "1000";

  // 添加按钮到页面中
  document.body.appendChild(btnScroll);
  document.body.appendChild(btnJump);
  // document.body.appendChild(btnTest);

  let isScrolling = false;
  let timerId;

  function getUserId() {
    const arr = window.location.href.match(/\/user\/profile\/(\w+)/);
    if (!arr) {
      return "";
    }
    if (arr.length < 2) {
      return "";
    }
    return arr[1];
  }

  function simulateScroll() {
    window.scrollBy(0, 200);
  }

  function startScroll() {
    if (isScrolling) {
      return;
    }
    isScrolling = true;
    btnScroll.innerHTML = "停止滚动";
    btnScroll.style.backgroundColor = "#ff2442";
    timerId = setInterval(simulateScroll, 200);
  }

  function cancelScroll() {
    if (!isScrolling) {
      return;
    }
    isScrolling = false;
    btnScroll.style.backgroundColor = "#056b00";
    btnScroll.innerHTML = "自动滚动";
    if (timerId) {
      clearInterval(timerId);
    }
  }

  // 给按钮添加点击事件
  btnScroll.addEventListener("click", function () {
    if (isScrolling) {
      cancelScroll();
    } else {
      startScroll();
    }
  });

  btnJump.addEventListener("click", function () {
    const userId = getUserId();
    window.open(
      `https://mundane.ink/redbook/index.html?userId=${userId}`,
      "_blank"
    );
  });

  btnTest.addEventListener("click", function () {
    let tab = document.querySelectorAll(".tab-content-item")[1];
    const elements = tab.querySelectorAll("a.cover.ld.mask");
    elements[0].click();
    let timeId = setInterval(function () {
      let closeButton = document.querySelector("div.close-circle div.close");
      if (closeButton) {
        closeButton.click();
        clearInterval(timeId);
      }
    }, 500);
  });

  const originOpen = XMLHttpRequest.prototype.open;
  const collectUrl = "//edith.xiaohongshu.com/api/sns/web/v2/note/collect";
  const feedUrl = "//edith.xiaohongshu.com/api/sns/web/v1/feed";
  let patchIndex = 0;
  XMLHttpRequest.prototype.open = function (_, url) {
    const xhr = this;
    if (url.startsWith(collectUrl) || url.startsWith(feedUrl)) {
      const getter = Object.getOwnPropertyDescriptor(
        XMLHttpRequest.prototype,
        "response"
      ).get;
      Object.defineProperty(xhr, "responseText", {
        get: () => {
          let result = getter.call(xhr);
          // console.log("result =", result);
          let myUrl = "";
          let requestData = "";
          if (url.startsWith(collectUrl)) {
            const params = new URLSearchParams(url.split("?")[1]);
            const cursor = params.get("cursor");
            if (!cursor) {
              patchIndex = 0;
            }
            myUrl = `${baseUrl}/mail/redbook/collect/save`;
            const userId = getUserId();
            requestData = JSON.stringify({ result, userId, patchIndex });
          } else if (url.startsWith(feedUrl)) {
            myUrl = `${baseUrl}/mail/redbook/note/save`;
            requestData = JSON.stringify({ result });
          }
          try {
            // 将result发送到服务器
            GM_xmlhttpRequest({
              method: "POST",
              url: myUrl,
              headers: {
                "Content-Type": "application/json",
              },
              data: requestData,
              onload: function (response) {
                console.log("Result sent to server successfully!");
              },
            });
            if (url.startsWith(collectUrl)) {
              patchIndex++;
              const obj = JSON.parse(result);
              if (!obj.data.has_more) {
                cancelScroll();
                patchIndex = 0;
                console.log("没有更多了!!!");
                alert("小红书收藏已发送完毕,没有更多了");
              }
            }
          } catch (e) {
            console.error(e);
          }
          return result;
        },
      });
    }
    originOpen.apply(this, arguments);
  };
})();

免费评分

参与人数 5吾爱币 +11 热心值 +5 收起 理由
吴沈金 + 1 + 1 佬,udemy课程已有人来结贴,不必麻烦你了,感谢
wwbzmt + 1 + 1 谢谢@Thanks!
wyr12388 + 1 + 1 谢谢@Thanks!
aabbcc123123 + 1 + 1 谢谢@Thanks!
wushaominkk + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
yourbestrun 发表于 2023-9-26 16:01
请问导出的内容,是按照笔记标题生成不同文件夹吗 ?

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
ovioni + 1 + 1 热心回复!

查看全部评分

3#
marsjojo 发表于 2023-9-26 13:54
感谢分享,正好想保存几篇笔记,下来试试

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
ovioni + 1 + 1 热心回复!

查看全部评分

4#
ithero0512 发表于 2023-9-26 14:30
谢谢楼主   不喜欢批量的 就喜欢这种脚本  

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
ovioni + 1 + 1 热心回复!

查看全部评分

5#
keber 发表于 2023-9-26 14:32
感谢楼主的分享我来试用一下!
6#
山丁 发表于 2023-9-26 14:33
感谢分享
7#
醉酒听风 发表于 2023-9-26 14:45
不错的脚本,先收藏一下
8#
庄家 发表于 2023-9-26 16:18
哥们 点赞收藏评论呢??这些数据
9#
ufoboyxj 发表于 2023-9-26 16:19
感谢楼主分享
10#
Gyy66 发表于 2023-9-26 17:00
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 15:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表