您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
【最终版】兼容所有发布类型(群发、单发、转载等),修复了因错误跳过记录而导致导出失败的终极BUG。
// ==UserScript== // @name 微信公众号文章&粉丝导出 // @namespace http://tampermonkey.net/ // @version 5.4 // @description 【最终版】兼容所有发布类型(群发、单发、转载等),修复了因错误跳过记录而导致导出失败的终极BUG。 // @author Gemini & Pz (A Collaborative Debugging Masterpiece) // @match https://mp.weixin.qq.com/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect mp.weixin.qq.com // @license MIT // ==/UserScript== (function() { 'use strict'; // --- 1. 样式定义 --- GM_addStyle(` .gemini-export-btn { margin-left: 20px; padding: 6px 16px; font-size: 14px; font-weight: 400; vertical-align: middle; background-color: #07c160; color: white; border: none; border-radius: 5px; cursor: pointer; transition: all 0.3s; } .gemini-export-btn:hover { background-color: #06ad56; } .gemini-export-btn:disabled { background-color: #ccc; cursor: not-allowed; } .gemini-page-input { width: 54px; /* 适配“全部”两字 */ margin-left: 8px; padding: 3px 6px; border: 1px solid #ccc; border-radius: 4px; text-align: center; vertical-align: middle; font-size: 14px; height: 28px; box-sizing: border-box; } .gemini-page-label { margin-left: 2px; /* 更贴合input */ font-size: 13px; vertical-align: middle; color: #888; padding-right: 4px; } `); // --- 2. 核心功能函数 --- function getToken() { try { const token = new URL(window.location.href).searchParams.get('token'); if (token) return token; if (window.wx && window.wx.commonData && window.wx.commonData.data && window.wx.commonData.data.t) { return window.wx.commonData.data.t; } return null; } catch (e) { return null; } } function fetchArticlePage(token, pageIndex) { return new Promise((resolve, reject) => { const begin = pageIndex * 10; const url = `/cgi-bin/appmsgpublish?sub=list&begin=${begin}&count=10&token=${token}&lang=zh_CN`; GM_xmlhttpRequest({ method: "GET", url: url, onload: r => resolve(r.responseText), onerror: reject }); }); } function extractDataFromScript(htmlText) { console.log('extractDataFromScript htmlText:', htmlText); const articles = []; // 1. 优先尝试 window.publish_page let publishPageObj = null; if (typeof window !== 'undefined' && window.publish_page && window.publish_page.publish_list) { publishPageObj = window.publish_page; } else { // 2. 退而求其次:用正则从htmlText中提取publish_page变量 const match = htmlText.match(/publish_page\s*=\s*(\{[\s\S]*?\});/); if (match && match[1]) { try { publishPageObj = JSON.parse(match[1]); } catch (e) { // console.error('【错误】publish_page JSON解析失败', e, match[1].slice(0, 500)); return []; } } else { // console.error('【错误】未找到publish_page变量'); return []; } } // 3. 遍历publish_list function decodeHtmlEntities(str) { const txt = document.createElement('textarea'); txt.innerHTML = str; return txt.value; } function pad(n) { return n.toString().padStart(2, '0'); } (publishPageObj.publish_list || []).forEach(item => { // 4. 还原publish_info的HTML实体并解析 let infoObj = null; try { let infoStr = item.publish_info; if (typeof infoStr === 'string') { infoStr = decodeHtmlEntities(infoStr); infoObj = JSON.parse(infoStr); } else if (typeof infoStr === 'object' && infoStr !== null) { infoObj = infoStr; } } catch (e) { // console.error('【错误】publish_info解析失败', item.publish_info, e); return; } if (!infoObj || !Array.isArray(infoObj.appmsg_info) || infoObj.appmsg_info.length === 0) { // console.warn('appmsg_info 不是数组或为空', infoObj); return; } // 5. 群发时间(备用) let masssendTimestamp = null; if (infoObj.sent_info && infoObj.sent_info.time) { masssendTimestamp = infoObj.sent_info.time; } else if (infoObj.create_time) { masssendTimestamp = infoObj.create_time; } // 6. 导出appmsg_info中的所有文章,优先用每篇文章自己的line_info.send_time infoObj.appmsg_info.forEach(appmsg => { // console.log('【调试】appmsg:', appmsg); let articleTimestamp = masssendTimestamp; if (appmsg.line_info && typeof appmsg.line_info === 'object') { let st = appmsg.line_info.send_time; if (st && !isNaN(Number(st))) { articleTimestamp = Number(st); } else { // console.warn('【调试】appmsg.line_info.send_time 无效:', st, appmsg.line_info); } } else { // console.warn('【调试】appmsg.line_info 不存在或不是对象:', appmsg.line_info); } const date = articleTimestamp ? new Date(articleTimestamp * 1000) : null; const formattedTime = date ? `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}` : ''; if (!formattedTime) { // console.warn('【调试】未能生成发布时间,articleTimestamp:', articleTimestamp, appmsg); } const articleObj = { type: infoObj.type || '', title: appmsg.title || 'N/A', link: appmsg.content_url || '', time: formattedTime, itemidx: appmsg.itemidx || '', // 群发位置 album: (appmsg.appmsg_album_info && appmsg.appmsg_album_info.title) ? appmsg.appmsg_album_info.title : '', // 合集名称 cover: appmsg.cover || appmsg.pic_cdn_url_1_1 || '', // 封面 digest: appmsg.digest || '', // 摘要 read: appmsg.read_num || 0, like: appmsg.like_num || 0, comment: appmsg.comment_num || 0, recommend: appmsg.old_like_num || 0, share: appmsg.share_num || 0, is_original: ( (typeof appmsg.copyright_type !== 'undefined' && appmsg.copyright_type == 1) || (typeof appmsg.copyright_status !== 'undefined' && appmsg.copyright_status == 11) ? '是' : '否' ) // 是否原创 }; articles.push(articleObj); // console.log('【调试】已加入articles的article对象:', articleObj); }); }); return articles; } function generateAndDownloadCSV(data) { if (data.length === 0) { alert('没有提取到任何文章数据!请按F12查看控制台中的【错误】信息。'); return; } const header = ['标题', '链接', '发布时间', '群发位置', '合集名称', '封面', '摘要', '是否原创', '阅读量', '点赞数', '评论数', '在看数', '分享数']; const rows = data.map(a => [ `"${a.title.replace(/"/g, '""')}"`, `"${a.link}"`, `"${a.time}"`, a.itemidx, `"${a.album}"`, `"${a.cover}"`, `"${a.digest.replace(/"/g, '""')}"`, a.is_original, a.read, a.like, a.comment, a.recommend, a.share ].join(',')); const csvContent = [header.join(','), ...rows].join('\n'); const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); const accountName = document.querySelector('.weui-desktop-account-nickname')?.textContent.trim() || '公众号'; link.download = `${accountName}_文章数据_${new Date().toLocaleDateString()}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); } // --- 新增:粉丝导出功能 --- // 自动分页接口获取所有粉丝数据 async function fetchAllFollowers(token, groupid = -2, limit = 20, maxCount = Infinity) { let allUsers = []; let begin_openid = ''; let begin_create_time = ''; let hasMore = true; let groupMap = {}; let firstGroupInfo = null; let totalFetched = 0; while (hasMore && totalFetched < maxCount) { const url = `https://mp.weixin.qq.com/cgi-bin/user_tag?action=get_user_list&groupid=${groupid}&begin_openid=${begin_openid}&begin_create_time=${begin_create_time}&limit=${limit}&offset=0&backfoward=1&token=${token}&lang=zh_CN&f=json&ajax=1&random=${Math.random()}`; const resp = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url, onload: resolve, onerror: reject }); }); let data; try { data = JSON.parse(resp.responseText); } catch (e) { alert('粉丝数据解析失败!'); break; } if (!firstGroupInfo && data.group_info && data.group_info.group_info_list) { firstGroupInfo = data.group_info.group_info_list; for (const g of firstGroupInfo) { groupMap[g.group_id] = g.group_name; } } const users = (data.user_list && data.user_list.user_info_list) ? data.user_list.user_info_list : []; if (users.length === 0) break; allUsers.push(...users); totalFetched += users.length; const last = users[users.length - 1]; begin_openid = last.user_openid; begin_create_time = last.user_create_time; hasMore = users.length === limit && totalFetched < maxCount; } return {allUsers, groupMap}; } // 获取单个粉丝详细信息 async function fetchFanDetail(token, openid) { const fingerprint = Math.random().toString(36).slice(2) + Date.now(); const url = 'https://mp.weixin.qq.com/cgi-bin/user_tag?action=get_fans_info'; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url, headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Referer': window.location.href }, data: `token=${token}&lang=zh_CN&f=json&ajax=1&fingerprint=${fingerprint}&user_openid=${encodeURIComponent(openid)}&identity_open_id=`, onload: r => { // console.log('【详情接口返回】', r.responseText); try { const data = JSON.parse(r.responseText); if (data && data.user_list && data.user_list.user_info_list && data.user_list.user_info_list.length > 0) { resolve(data.user_list.user_info_list[0]); } else { resolve(null); } } catch (e) { // console.error('【详情接口解析失败】', e, r.responseText); resolve(null); } }, onerror: e => { // console.error('【详情接口请求失败】', e); reject(e); } }); }); } function formatTime(ts) { if (!ts) return ''; const d = new Date(Number(ts) * 1000); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0') + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0') + ':' + String(d.getSeconds()).padStart(2, '0'); } function fenToYuan(fen) { if (!fen) return '0.00'; return (Number(fen) / 100).toFixed(2); } function generateAndDownloadFollowerCSV(users, groupMap) { if (!users.length) { alert('没有提取到任何粉丝数据!'); return; } const header = ['头像URL', '昵称', '备注', '标签', '签名', '城市', '省份', '国家', '消息数', '留言数', '精选留言数', '赞赏数', '赞赏金额', '付费数', '付费金额', '关注时间', '黑名单']; const rows = users.map(u => [ u.user_head_img, u.user_name, u.user_remark, (Array.isArray(u.user_group_id) ? u.user_group_id.map(id => groupMap[id] || id).join(';') : (groupMap[u.user_group_id] || u.user_group_id || '')), u.user_signature || '', u.user_city || '', u.user_province || '', u.user_country || '', u.user_msg_cnt || 0, u.user_comment_cnt || 0, u.user_selected_comment_cnt || 0, u.user_reward_cnt || 0, fenToYuan(u.user_reward_money), u.user_paysubscribe_count || 0, fenToYuan(u.user_paysubscribe_money), formatTime(u.user_create_time), u.user_in_blacklist ? '是' : '否' ]); const csv = [header, ...rows].map(row => row.map(cell => '"' + String(cell).replace(/"/g, '""') + '"').join(',')).join('\r\n'); const blob = new Blob(['\uFEFF' + csv], {type: 'text/csv'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); const accountName = document.querySelector('.weui-desktop-account-nickname')?.textContent.trim() || '公众号'; a.download = `${accountName}_粉丝数据_${new Date().toLocaleDateString()}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); } // 恢复文章导出分页通用函数 async function processExport(token, maxPages, type, fetchFunc, extractFunc, generateFunc) { let allData = []; let currentPage = 0; try { while (currentPage < maxPages) { const htmlText = await fetchFunc(token, currentPage); const parsedData = extractFunc(htmlText); if (parsedData.length === 0 && currentPage > 0) break; allData.push(...parsedData); currentPage++; await new Promise(res => setTimeout(res, 250)); } generateFunc(allData); } catch (error) { alert(`导出${type}失败,请按 F12 查看错误信息。`); } } // --- 3. 主函数 --- function main() { const path = window.location.pathname; let titleElement, buttonText, exportFunction; if (path.includes('/cgi-bin/appmsgpublish')) { titleElement = document.querySelector('.publish_listory_title'); buttonText = '导出文章数据'; exportFunction = async (token, maxPages) => { await processExport(token, maxPages, '文章', fetchArticlePage, extractDataFromScript, generateAndDownloadCSV); }; } else if (path.includes('/cgi-bin/user_tag')) { titleElement = document.querySelector('.weui-desktop-layout__main__hd h2'); buttonText = '导出粉丝'; exportFunction = async (token, maxCount, countInput) => { const btn = document.querySelector('.gemini-export-btn'); btn.disabled = true; btn.textContent = '正在请求用户信息...'; try { const {allUsers, groupMap} = await fetchAllFollowers(token, -2, 20, maxCount); if (!allUsers.length) { alert('没有获取到任何粉丝数据!'); btn.disabled = false; btn.textContent = '导出粉丝'; return; } let detailedUsers = []; for (let i = 0; i < allUsers.length; i++) { btn.textContent = `正在请求第${i + 1}个用户信息...`; const detail = await fetchFanDetail(token, allUsers[i].user_openid); const merged = detail ? {...allUsers[i], ...detail} : allUsers[i]; // console.log('【合并后用户数据】', JSON.stringify(merged)); detailedUsers.push(merged); await new Promise(res => setTimeout(res, 200)); } generateAndDownloadFollowerCSV(detailedUsers, groupMap); } catch (e) { alert('导出失败:' + e.message); } btn.disabled = false; btn.textContent = '导出粉丝'; }; } else { return; } if (!titleElement || document.querySelector('.gemini-export-btn')) return; const container = document.createElement('span'); titleElement.appendChild(container); const exportButton = document.createElement('button'); exportButton.textContent = buttonText; exportButton.className = 'gemini-export-btn'; if (path.includes('/cgi-bin/appmsgpublish')) { const pageInput = document.createElement('input'); pageInput.className = 'gemini-page-input'; pageInput.type = 'number'; pageInput.placeholder = '全部'; pageInput.min = '1'; const pageLabel = document.createElement('span'); pageLabel.className = 'gemini-page-label'; pageLabel.textContent = '页'; container.appendChild(exportButton); container.appendChild(pageInput); container.appendChild(pageLabel); exportButton.addEventListener('click', async () => { const token = getToken(); if (!token) { alert('无法获取Token,请刷新页面后重试。'); return; } const maxPages = pageInput.value ? parseInt(pageInput.value, 10) : Infinity; exportButton.disabled = true; let currentPage = 0; try { await processExport(token, maxPages, '文章', async (token, pageIdx) => { exportButton.textContent = `正在请求第${pageIdx + 1}页文章...`; return await fetchArticlePage(token, pageIdx); }, extractDataFromScript, (data) => { exportButton.textContent = '正在生成CSV...'; generateAndDownloadCSV(data); }); } catch (e) { exportButton.textContent = '导出文章数据'; exportButton.disabled = false; throw e; } exportButton.textContent = '导出文章数据'; exportButton.disabled = false; }); } else { // 只保留导出数量输入框 const countInput = document.createElement('input'); countInput.className = 'gemini-page-input'; countInput.type = 'number'; countInput.placeholder = '导出数量(留空为全部)'; countInput.min = '1'; countInput.style.width = '150px'; countInput.style.marginLeft = '8px'; container.appendChild(exportButton); container.appendChild(countInput); exportButton.addEventListener('click', async () => { const token = getToken(); if (!token) { alert('无法获取Token,请刷新页面后重试。'); return; } const maxCount = countInput.value ? parseInt(countInput.value, 10) : Infinity; await exportFunction(token, maxCount, countInput); }); } } // --- 4. 启动脚本 --- const observer = new MutationObserver(() => { const targetNode = document.querySelector('.publish_listory_title') || document.querySelector('.weui-desktop-layout__main__hd h2'); if (targetNode) { main(); } }); observer.observe(document.body, { childList: true, subtree: true }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址