Greasy Fork is available in English.

小红书笔记导出

导出小红书列表数据,方便做数据分析

// ==UserScript==
// @name         小红书笔记导出
// @namespace    http://www.junxiaopang.com/
// @version      1.0.1
// @description  导出小红书列表数据,方便做数据分析
// @author       俊小胖
// @match        https://www.xiaohongshu.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=xiaohongshu.com
// @license      GPL
// @require      https://cdn.bootcdn.net/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// ==/UserScript==

(function() {
    'use strict';
    let data = [];
    let itemNum = 0 //当前加载笔记的数量
    let excel_title = '小红书数据'
    let likeNumLimit = 100 //导出的数据点赞数要求
    let keywords = ''//过滤的关键词

    // 定义一个函数来抓取数据
    function fetchData() {
        var itemNumElement = document.getElementById('itemNum');

        // 假设列表数据在某个具有特定类名的元素中
        const listElements = document.querySelectorAll('.note-item'); // 需要根据实际页面结构调整选择器
        const domain = 'https://www.xiaohongshu.com'
        listElements.forEach(item => {
            //console.log(item.querySelector('.title'));
            if(item.querySelector('.title')){
                let title = item.querySelector('.title').textContent; // 需要根据实际页面结构调整选择器
                let author = item.querySelector('.author').textContent; // 需要根据实际页面结构调整选择器
                let likeCount = item.querySelector('.like-wrapper').textContent; // 需要根据实际页面结构调整选择器
                let link = item.querySelector('a').getAttribute('href'); // 需要根据实际页面结构调整选择器
                let itemIndex = item.dataset.index?item.dataset.index:0

                likeCount = convertToNumber(likeCount?likeCount:0)
                link = domain + link

                let itemData = [title,author,likeCount,link]
                data[itemIndex] = itemData
            }

        });

        itemNum = data.length

        if (itemNumElement) {
            itemNumElement.textContent = itemNum; // 更新文本内容
        }

    }
    //过滤点赞数限制的数据
    function filterArrayByLikeNum(arr,limit) {
        var newArray = [];
        arr.forEach(function(subArray) {
            if (subArray[2] > limit) {
                newArray.push(subArray);
            }
        });
        return newArray;
    }
    function containsAnyKeyword(str, keywords) {
        const regexPattern = `(${keywords.join('|')})`;
        const regex = new RegExp(regexPattern, 'i'); // 'i' 表示不区分大小写
        return regex.test(str);
    }
    //过滤指定关键词
    function filterArrayByTitle(arr,keywords) {
        var newArray = [];
        arr.forEach(function(subArray) {
            if (!containsAnyKeyword(subArray[0],keywords)) {
                newArray.push(subArray);
            }
        });
        return newArray;
    }
    // 定义点击按钮时执行的函数
    function onButtonClick() {
        // 在这里添加你想要执行的代码
        exportArrayToExcel(data, excel_title);
        // fetchData()

    }

    //自定义页面样式
    function addStyle(){
        var style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = '.export-note{margin: 6px 0 12px 0;}.group-header-title{font-size: 16px;border-bottom: 1px solid #eee;padding: 10px 0;margin-bottom: 10px;color: rgba(51, 51, 51, 0.6);}.group-header-title a{float: right;color: #f6333b;}#itemNum{font-weight: bold;color: red;}.export-data {margin: 6px 0;display: flex;flex-wrap: wrap;flex-direction: row-reverse;border:solid 1px #eee;border-radius: 16px;padding:6px;line-height: 25px;}.export-button{text-align: center;padding:6px;border:border: 1px solid transparent;background-color:#ff2442;color:#ffffff;border-radius:5px;cursor:pointer}.input{margin: 0 4px;padding:2px 4px;border: 1px solid #ccc;border-radius: 4px;background-color: #eee;width: 60px;}';
        document.head.appendChild(style);
    }

    // 动态插入<script>标签
    function loadScript(url, callback) {
        // 创建script元素
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = url;

        // 绑定事件处理程序
        script.onreadystatechange = script.onload = function() {
            var state = this.readyState;
            if (!callback.done && (!state || /loaded|complete/.test(state))) {
                callback.done = true;
                callback();
            }
        };

        // 插入到DOM中
        document.getElementsByTagName('head')[0].appendChild(script);
    }

    // export-to-excel.js
    function exportArrayToExcel(dataArray, fileName) {
        var newDataArray = dataArray
        var keywordArray = []
        var limit = parseFloat(document.getElementById("likeNumLimit").value);
        var keywords_value = document.getElementById("keywords").value;
        if(limit > 0){
            newDataArray = filterArrayByLikeNum(dataArray,limit)
            if(keywords_value && keywords_value != ''){
                keywordArray = keywords_value.split(","); // 使用逗号作为分隔符
                newDataArray = filterArrayByTitle(newDataArray,keywordArray)
            }
        }
        newDataArray.unshift(['标题','作者','点赞数','链接']);
        // 创建一个工作簿
        const workbook = XLSX.utils.book_new();
        // 将数组转换为工作表
        const worksheet = XLSX.utils.aoa_to_sheet(newDataArray);
        // 将工作表添加到工作簿
        XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
        // 导出工作簿
        XLSX.writeFile(workbook, fileName + ".xlsx");
    }

    //转换点赞数为数字
    function convertToNumber(text) {
        // 移除空格
        text = text.replace(/\s+/g, '');
        // 检测单位
        if (text.endsWith('w')) {
            // 移除单位并转换为数字
            return parseFloat(text.slice(0, -1)) * 10000;
        } else {
            // 如果没有单位,直接返回转换后的数字
            return parseFloat(text);
        }
    }

    //监听鼠标滑动更新数据
    document.addEventListener('mousemove', function(event) {
        fetchData()
    });

    // 创建按钮元素
    const button = document.createElement('button');
    button.textContent = '导出excel';
    button.className = 'export-button'

    // 为按钮添加点击事件监听器
    button.addEventListener('click', onButtonClick);

    //载入导出按钮
    function loadExportButton(){
        addStyle()
        // 导出函数到全局
        window.exportArrayToExcel = exportArrayToExcel;
        // 创建一个 URL 对象
        var url = new URL(window.location.href);
        // 使用 URLSearchParams 获取参数
        var keyword = url.searchParams.get('keyword');
        if(keyword){
            keyword = decodeURIComponent(keyword)

            excel_title = keyword + ' - 小红书数据'
        }else if(document.title){
            excel_title = document.title;
        }

        // 找到具有特定类名的元素
        var targetElement = document.querySelector('.channel-list');

        // 创建一个新的 div 元素
        var exportDiv = document.createElement('div');
        // 设置 div 的内容
        exportDiv.innerHTML = '<div class="export-note"><div class="group-header-title">红薯笔记导出<a href="http://shang.junxiaopang.com" target="_blank">💰打赏开发者</a></div>支持导出页面上加载过的笔记列表<br>方便运营人员做数据分析提高工作效率<br>当前已加载<span id="itemNum">0</span>条数据<br>上下滑动鼠标可以加载更多<div class="group-header-title">导出设置</div>只导出点赞大于<input name="like-num" id="likeNumLimit" value="'+likeNumLimit+'" class="input" type="number"/>的数据<br>过滤标题中带有<input name="keywords" id="keywords" value="'+keywords+'" class="input" type="text"/>的内容</div>';
        // 添加类名或者样式
        exportDiv.className = 'export-data';
        // 如果要插入到特定位置,比如body的开头,可以直接使用以下代码:
        targetElement.appendChild(exportDiv);
        exportDiv.appendChild(button);

        //页面加载完以后,初始化读取
        fetchData()
    }

    // 等待页面加载完毕再执行
    window.addEventListener('load', loadExportButton);



})();