维基百科纯净文本提取器

自动提取维基百科条目正文的纯净文本(去除所有链接、注释等干扰信息)

目前为 2025-02-24 提交的版本。查看 最新版本

// ==UserScript==
// @name         维基百科纯净文本提取器
// @name         Wikipedia Plain Text Extractor
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  自动提取维基百科条目正文的纯净文本(去除所有链接、注释等干扰信息)
// @description  Automatically extract clean text from Wikipedia entries (remove all links, annotations, and other distracting information)
// @author       Yuze
// @copyright    2025, Yuze (https://greasyfork.org/users/Yuze Guitar)
// @license      MIT
// @match        https://*.wikipedia.org/wiki/*
// @match        https://*.m.wikipedia.org/wiki/*
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // 立即执行的翻译阻止代码
    (function preventTranslation() {
        // 1. 添加元标记
        const meta = document.createElement('meta');
        meta.name = 'google';
        meta.content = 'notranslate';
        document.documentElement.appendChild(meta);

        // 2. 添加HTML属性
        document.documentElement.setAttribute('translate', 'no');
        document.documentElement.setAttribute('class', 'notranslate');

        // 3. 添加CSS规则阻止翻译界面
        const css = `
            .skiptranslate,
            #google_translate_element,
            .goog-te-banner-frame,
            .goog-te-gadget,
            .goog-te-spinner-pos,
            .goog-tooltip,
            .goog-tooltip:hover,
            .goog-text-highlight,
            #goog-gt-tt,
            .VIpgJd-ZVi9od-l4eHX-hSRGPd,
            .VIpgJd-ZVi9od-ORHb-OEVmcd,
            .VIpgJd-ZVi9od-SmfZ-OEVmcd-tJHJj {
                display: none !important;
                visibility: hidden !important;
                height: 0 !important;
                width: 0 !important;
                opacity: 0 !important;
                pointer-events: none !important;
            }

            body {
                position: static !important;
                top: 0 !important;
                min-height: auto !important;
            }
        `;

        const style = document.createElement('style');
        style.textContent = css;
        document.documentElement.appendChild(style);

        // 4. 定期检查并移除翻译元素
        const removeTranslateElements = () => {
            const elements = document.querySelectorAll(`
                .skiptranslate,
                #google_translate_element,
                .goog-te-banner-frame,
                .goog-te-gadget,
                .goog-te-spinner-pos,
                .goog-tooltip,
                #goog-gt-tt,
                .VIpgJd-ZVi9od-l4eHX-hSRGPd,
                .VIpgJd-ZVi9od-ORHb-OEVmcd,
                .VIpgJd-ZVi9od-SmfZ-OEVmcd-tJHJj
            `);
            elements.forEach(el => el.remove());

            // 移除由谷歌翻译添加的iframe
            const iframes = document.getElementsByTagName('iframe');
            for (let i = iframes.length - 1; i >= 0; i--) {
                const iframe = iframes[i];
                if (iframe.src.includes('translate.google') ||
                    iframe.id.includes('goog') ||
                    iframe.className.includes('goog')) {
                    iframe.remove();
                }
            }
        };

        // 立即执行一次
        removeTranslateElements();

        // 设置定期检查
        setInterval(removeTranslateElements, 1000);

        // 5. 阻止翻译相关的脚本加载
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.tagName === 'SCRIPT' &&
                        (node.src.includes('translate.google') ||
                         node.src.includes('translate.googleapis'))) {
                        node.remove();
                    }
                });
            });
        });

        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    })();

    // 检查是否为移动版并重定向 - 最优先执行
    function redirectToDesktop() {
        const currentURL = window.location.href;
        if (currentURL.includes('.m.wikipedia.org')) {
            const desktopURL = currentURL.replace('.m.wikipedia.org', '.wikipedia.org');
            window.location.replace(desktopURL);
            return true;
        }
        return false;
    }

    // 如果是移动版立即重定向并返回
    if (redirectToDesktop()) {
        return; // 终止后续执行
    }

    // 添加自定义CSS来隐藏广告和通知,同时优化资源加载
    const customCSS = `
        /* 确保左侧导航栏显示 */
        .vector-sticky-pinned-container,
        .vector-toc-pinned-container,
        #mw-panel,
        #mw-sidebar-button,
        .vector-menu-tabs,
        .vector-page-toolbar,
        .vector-sticky-header-visible,
        #vector-toc-collapsed-button,
        .vector-menu-portal,
        .vector-menu-content,
        .vector-menu-portal-container,
        #mw-navigation {
            display: block !important;
            visibility: visible !important;
            opacity: 1 !important;
            position: static !important;
            width: auto !important;
            height: auto !important;
            pointer-events: auto !important;
            z-index: 3 !important;
        }

        /* 优化左侧导航栏样式 */
        .vector-sticky-pinned-container {
            position: sticky !important;
            top: 0 !important;
            max-height: 100vh !important;
            overflow-y: auto !important;
            padding-right: 10px !important;
        }

        /* 确保父容器显示 */
        .mw-page-container,
        .mw-page-container-inner,
        .vector-column-start,
        .mw-content-container,
        .vector-sidebar {
            display: block !important;
            visibility: visible !important;
            position: relative !important;
            width: auto !important;
            height: auto !important;
        }

        /* 隐藏不必要的元素,但保留导航相关元素 */
        .banner-container,
        #siteNotice,
        .mw-indicators,
        .mw-editsection,
        #footer-places,
        #footer,
        #p-logo,
        .mw-parser-output > div:first-child:not(#toc):not(.infobox),
        [class*="banner"]:not([class*="vector"]),
        [class*="noprint"]:not(.vector-sticky-pinned-container):not(.vector-toc-pinned-container):not([class*="vector"]),

        /* 隐藏页面底部元素 */
        .mw-footer-container,
        #footer-info,
        #footer-places,
        #footer,
        .footer-info,
        .footer-places,
        #mw-footer,
        #footer-info-lastmod,
        #footer-info-copyright,
        #footer-icons,
        .printfooter,
        .mw-footer,
        li[id^="footer-"],
        div[class*="footer"],
        .minerva-footer-logo {
            display: none !important;
        }

        /* 优化页面布局 */
        #content {
            margin: 0 auto !important;
            max-width: 1000px !important;
            padding: 20px !important;
        }

        /* 确保目录可见 */
        #vector-toc-collapsed-button,
        .vector-toc,
        .vector-toc-text,
        .vector-toc-toggle,
        .vector-toc-contents {
            display: block !important;
            visibility: visible !important;
        }

        /* 修复可能的层级问题 */
        .vector-sticky-pinned-container {
            z-index: 100 !important;
        }
    `;

    // 尽早添加样式到页面
    const style = document.createElement('style');
    style.textContent = customCSS;
    document.documentElement.appendChild(style);

    // 优化资源加载
    function optimizeResourceLoading() {
        // 移除不必要的脚本
        const scripts = document.querySelectorAll('script[src*="analytics"], script[src*="tracking"]');
        scripts.forEach(script => script.remove());

        // 移除预加载的资源
        const links = document.querySelectorAll('link[rel="preload"]');
        links.forEach(link => link.remove());
    }

    // 添加MutationObserver持续监听并移除新添加的图片
    const imageObserver = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
                if(node.nodeName === 'IMG' ||
                   node.nodeName === 'PICTURE' ||
                   node.nodeName === 'FIGURE') {
                    node.remove();
                }
            });
        });
    });

    // 添加新的MutationObserver来监听并移除捐赠相关元素
    const donateObserver = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
                if (node.nodeType === 1) { // 元素节点
                    if (node.matches('[href*="donate.wikimedia.org"], [data-mw="interface"], .mw-portlet-personal, #p-personal, #mw-panel-sponsors')) {
                        node.remove();
                    }
                    // 检查新添加元素的子元素
                    const donateLinks = node.querySelectorAll('[href*="donate"], [class*="donate"], [id*="donate"], [class*="sponsor"], [id*="sponsor"]');
                    donateLinks.forEach(el => el.remove());
                }
            });
        });
    });

    // 检查是否为维基百科首页
    function isWikiMainPage() {
        const currentURL = window.location.href;
        // 检查多语言首页的常见模式
        const mainPagePatterns = [
            '/wiki/Wikipedia:首页',
            '/wiki/Wikipedia:%E9%A6%96%E9%A1%B5', // URL编码的"首页"
            '/wiki/Main_Page',
            '/wiki/Wikipedia:首頁',    // 繁体中文
            '/wiki/Wikipedia:대문',    // 韩文
            '/wiki/Wikipedia:メインページ', // 日文
            '/wiki/Wikipédia:Accueil_principal', // 法文
            '/wiki/Wikipedia:Hauptseite',  // 德文
            'Special:首页',
            'Special:MainPage'
        ];

        return mainPagePatterns.some(pattern => currentURL.includes(pattern));
    }

    // 主程序入口
    function init() {
        // 如果是首页,直接返回不执行任何操作
        if (isWikiMainPage()) {
            return;
        }

        // 禁用自动翻译
        disableAutoTranslate();

        // 优化资源加载
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', optimizeResourceLoading);
        } else {
            optimizeResourceLoading();
        }

        // 等待主要内容加载完成后再执行提取
        if (document.readyState === 'complete') {
            initializeExtractor();
        } else {
            window.addEventListener('load', initializeExtractor);
        }

        // 启动图片监听
        imageObserver.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 启动捐赠元素监听
        donateObserver.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 确保导航栏显示
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', ensureNavigationVisible);
        } else {
            ensureNavigationVisible();
        }

        // 添加MutationObserver来确保导航栏始终可见
        const navigationObserver = new MutationObserver((mutations) => {
            ensureNavigationVisible();
        });

        navigationObserver.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true
        });
    }

    // 初始化提取器
    function initializeExtractor() {
        const content = document.getElementById('mw-content-text');
        if (content) {
            const cleanText = processWikiText();
            if (cleanText) createUI(cleanText);
        } else {
            // 如果还没加载完,使用MutationObserver继续监听
            const observer = new MutationObserver((mutations, obs) => {
                if (document.getElementById('mw-content-text')) {
                    obs.disconnect();
                    const cleanText = processWikiText();
                    if (cleanText) createUI(cleanText);
                }
            });

            observer.observe(document, {
                childList: true,
                subtree: true
            });
        }
    }

    // 智能文本净化处理器
    function processWikiText() {
        // 定位主要内容区域
        const content = document.getElementById('mw-content-text');
        if (!content) return null;

        // 创建克隆对象避免污染原始页面
        const cleanContent = content.cloneNode(true);

        // 智能清理不需要的元素
        const removables = [
            '.reference',         // 参考文献
            '.navbox',            // 导航框
            '.infobox',           // 信息框
            '.mw-editsection',    // 编辑按钮
            '.metadata',          // 元数据
            '.hatnote',           // 顶部提示
            '.mw-empty-elt',      // 空元素
            'img',                // 图片
            'table',              // 表格
            'sup',                // 上标注释
            '.catlinks',          // 分类链接
            '.ambox',            // 新增:信息框样式
            '.side-box',         // 新增:侧边栏
            '.plainlist',        // 新增:无样式列表
            'link',              // 新增:样式表链接
            'style',             // 新增:内联样式
            'img[src*="CentralAutoLogin"]', // 特定隐藏图片
            '#footer',           // 页脚内容
            '.printfooter',      // 打印页脚
            '.mw-footer',        // 媒体页脚
            '.mw-indicators',    // 页面指示器
            '.mw-authority-control', // 权威控制
            '.mw-redirect',      // 重定向链接
            '.dablink',          // 消歧义链接
            '.navigation-box',   // 导航框补充
            '.external',         // 外部链接
            '.noprint',          // 不打印内容
            '.reflist',           // 新增:参考文献列表
            '.mw-references',     // 新增:参考文献容器
            '.mw-hidden-catlinks',// 隐藏分类链接
            '.mw-jump-link',      // 跳转链接
            '.nomobile'          // 移动端隐藏内容
        ];

        removables.forEach(selector => {
            cleanContent.querySelectorAll(selector).forEach(el => el.remove());
        });

        // 深度清理嵌套链接
        cleanContent.querySelectorAll('a').forEach(link => {
            const parent = link.parentNode;
            while (link.firstChild) {
                parent.insertBefore(link.firstChild, link);
            }
            parent.removeChild(link);
        });

        // 获取最终文本并进行智能处理
        let text = cleanContent.textContent;

        // 移除参看和参考资料部分
        text = text.replace(/参看[\s\S]*?(?=\n\n|$)/g, '')  // 移除参看部分
                 .replace(/参考资料[\s\S]*?(?=\n\n|$)/g, '') // 移除参考资料部分
                 .replace(/延伸阅读[\s\S]*?(?=\n\n|$)/g, '') // 移除延伸阅读部分
                 .replace(/参见[\s\S]*?(?=\n\n|$)/g, '')     // 移除参见部分
                 .replace(/外部链接[\s\S]*?(?=\n\n|$)/g, '') // 移除外部链接部分
                 .replace(/注释[\s\S]*?(?=\n\n|$)/g, '')     // 移除注释部分
                 .replace(/参考文献[\s\S]*?(?=\n\n|$)/g, '') // 移除参考文献部分
                 .replace(/分类[\s\S]*?(?=\n\n|$)/g, '')     // 移除分类部分
                 .replace(/目录[\s\S]*?(?=\n\n|$)/g, '')     // 移除目录部分
                 // 增加对引用文献的过滤
                 .replace(/<[^>]+>[^.\n]*?<[^>]+>[^\n]*?\n/g, '') // 移除引用文献行
                 .replace(/\[\d+\]/g, '')                    // 移除引用标记 [1] [2] 等
                 .replace(/<[^>]+>/g, ' ')                   // 清除所有HTML标签
                 // 转换中文括号为英文括号
                 .replace(/\u0028/g, '(')                    // 将中文左圆括号转为英文
                 .replace(/\u0029/g, ')')                    // 将中文右圆括号转为英文
                 .replace(/【/g, '[')                        // 将中文左方括号转为英文
                 .replace(/】/g, ']')                        // 将中文右方括号转为英文
                 .replace(/「/g, '"')                        // 将中文左引号转为英文双引号
                 .replace(/」/g, '"')                        // 将中文右引号转为英文双引号
                 .replace(/『/g, "'")                        // 将中文左书名号转为英文单引号
                 .replace(/』/g, "'")                        // 将中文右书名号转为英文单引号
                 .replace(/\u300a/g, '"')                    // 将中文左书名号转为英文双引号
                 .replace(/\u300b/g, '"')                    // 将中文右书名号转为英文双引号
                 .replace(/〈/g, '<')                        // 将中文左尖括号转为英文
                 .replace(/〉/g, '>')                        // 将中文右尖括号转为英文
                 .replace(/\s*([()[\]<>"])\s*/g, '$1')      // 处理所有括号周围的空格
                 .replace(/(\d)\s*([³²])\s*/g, '$1$2')      // 修复数学上标
                 .replace(/([a-zA-Z])\s+(?=[a-zA-Z])/g, '$1') // 处理英文单词间距
                 .replace(/[^\S\n]/g, ' ')                   // 合并所有空白字符为单个空格
                 .replace(/([.!?;.!?;])\s*/g, '$1\n')       // 在中文和英文句末添加换行
                 .replace(/(\n\s*){2,}/g, '\n\n')           // 标准化段落间距
                 .replace(/^\s+|\s+$/g, '')                 // 去除首尾空格
                 .replace(/([^\x00-\xff])\s+([^\x00-\xff])/g, '$1$2') // 连接被分散的中文字符
                 .replace(/([^\x00-\xff])\s*([a-zA-Z])/g, '$1 $2') // 优化中英文之间的空格
                 .replace(/([a-zA-Z])\s*([^\x00-\xff])/g, '$1 $2') // 优化英中文之间的空格
                 .replace(/\s+/g, ' ')                       // 最终清理多余空格
                 .trim();

        return text;
    }

    // 创建可视化界面
    function createUI(text) {
        const panel = document.createElement('div');
        panel.style = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: rgba(255,255,255,0.95);
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            z-index: 9999;
            max-width: 400px;
            max-height: 80vh;
            overflow: auto;
            font-family: '微软雅黑', sans-serif;
        `;

        // 创建标题容器使其可以容纳标题、按钮和语言切换器
        const titleContainer = document.createElement('div');
        titleContainer.style = `
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            gap: 10px;
        `;

        const title = document.createElement('h3');
        title.textContent = '纯净文本提取结果';
        title.style = 'margin: 0; color: #0366d6;';

        // 创建按钮容器
        const buttonContainer = document.createElement('div');
        buttonContainer.style = `
            display: flex;
            gap: 10px;
            align-items: center;
        `;

        // 创建语言切换按钮
        const langBtn = document.createElement('div');
        langBtn.style = `
            display: flex;
            border: 1px solid #0366d6;
            border-radius: 5px;
            overflow: hidden;
            font-size: 14px;
        `;

        // 获取当前URL的语言
        const currentLang = window.location.href.includes('/zh/') ? 'zh' :
                           window.location.href.includes('/en/') ? 'en' :
                           window.location.hostname.startsWith('zh.') ? 'zh' : 'en';

        // 创建中文按钮
        const zhBtn = document.createElement('span');
        zhBtn.textContent = 'ZH';
        zhBtn.style = `
            padding: 4px 8px;
            cursor: pointer;
            background: ${currentLang === 'zh' ? '#0366d6' : 'transparent'};
            color: ${currentLang === 'zh' ? 'white' : '#0366d6'};
        `;

        // 创建英文按钮
        const enBtn = document.createElement('span');
        enBtn.textContent = 'EN';
        enBtn.style = `
            padding: 4px 8px;
            cursor: pointer;
            background: ${currentLang === 'en' ? '#0366d6' : 'transparent'};
            color: ${currentLang === 'en' ? 'white' : '#0366d6'};
        `;

        // 添加点击事件
        zhBtn.onclick = () => switchLanguage('zh');
        enBtn.onclick = () => switchLanguage('en');

        // 创建复制按钮
        const copyBtn = document.createElement('button');
        copyBtn.textContent = '一键复制';
        copyBtn.style = `
            padding: 6px 12px;
            background: #0366d6;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: 0.3s;
            font-size: 14px;
        `;
        copyBtn.onmouseover = () => copyBtn.style.background = '#0356b6';
        copyBtn.onmouseout = () => copyBtn.style.background = '#0366d6';
        copyBtn.onclick = () => {
            GM_setClipboard(text);
            copyBtn.textContent = '✓ 已复制!';
            setTimeout(() => copyBtn.textContent = '一键复制', 2000);
        };

        const content = document.createElement('div');
        content.textContent = text;
        content.style = 'line-height: 1.6; color: #333;';

        // 组装语言切换按钮
        langBtn.append(zhBtn, enBtn);
        buttonContainer.append(langBtn, copyBtn);
        titleContainer.append(title, buttonContainer);
        panel.append(titleContainer, content);
        document.body.appendChild(panel);
    }

    // 语言切换函数
    function switchLanguage(targetLang) {
        // 获取当前URL
        const currentURL = window.location.href;

        // 检查当前是否已经是目标语言
        const isCurrentZh = currentURL.includes('zh.wikipedia.org');
        const isCurrentEn = currentURL.includes('en.wikipedia.org');

        // 如果当前语言和目标语言相同,则不执行任何操作
        if ((isCurrentZh && targetLang === 'zh') || (isCurrentEn && targetLang === 'en')) {
            return;
        }

        // 首先尝试获取页面上的语言链接
        const langLinks = document.querySelectorAll('.interlanguage-link');
        for (const link of langLinks) {
            const langCode = link.getAttribute('lang') ||
                            link.querySelector('a').getAttribute('lang');
            if ((targetLang === 'zh' && (langCode === 'zh' || langCode === 'zh-Hans')) ||
                (targetLang === 'en' && langCode === 'en')) {
                // 找到目标语言的链接,直接跳转
                const targetURL = new URL(link.querySelector('a').href);

                // 添加禁止自动翻译的参数
                targetURL.searchParams.append('notranslate', 'true');

                // 跳转前先设置一个会话存储标记
                sessionStorage.setItem('disableAutoTranslate', 'true');

                window.location.href = targetURL.toString();
                return;
            }
        }

        // 如果没有找到语言链接,则尝试智能转换
        let newURL;

        // 检查当前页面是否有其他语言版本的链接
        const interwikiLink = document.querySelector(`a[hreflang="${targetLang}"]`);
        if (interwikiLink) {
            const targetURL = new URL(interwikiLink.href);
            targetURL.searchParams.append('notranslate', 'true');
            newURL = targetURL.toString();
        } else {
            // 如果没有直接的语言链接,保持当前页面标题
            const pageName = currentURL.split('/wiki/')[1];
            if (targetLang === 'zh') {
                newURL = `https://zh.wikipedia.org/wiki/${pageName}?notranslate=true`;
            } else {
                newURL = `https://en.wikipedia.org/wiki/${pageName}?notranslate=true`;
            }
        }

        // 设置会话存储标记
        sessionStorage.setItem('disableAutoTranslate', 'true');
        window.location.href = newURL;
    }

    // 在页面加载时禁用自动翻译
    function disableAutoTranslate() {
        if (sessionStorage.getItem('disableAutoTranslate')) {
            // 添加禁止翻译的元标记
            const meta = document.createElement('meta');
            meta.name = 'google';
            meta.content = 'notranslate';
            document.head.appendChild(meta);

            // 给body添加notranslate类
            document.body.classList.add('notranslate');

            // 设置translate属性
            document.documentElement.setAttribute('translate', 'no');

            // 清除会话存储标记
            sessionStorage.removeItem('disableAutoTranslate');
        }
    }

    // 添加DOM加载完成后的检查
    function ensureNavigationVisible() {
        const navigation = document.querySelector('.vector-sticky-pinned-container');
        if (navigation) {
            navigation.style.setProperty('display', 'block', 'important');
            navigation.style.setProperty('visibility', 'visible', 'important');
            navigation.style.setProperty('opacity', '1', 'important');
        }
    }

    // 启动程序
    init();
})();