检测页面所有链接,找到死链(如导航页面维护清理失效的链接)
netnr 2019-08-09
class LinkChecker {
    static PROXY_SERVERS = [
        "https://cors.eu.org/",
        "https://api.netnr.eu.org/link/",
        "https://api.codetabs.com/v1/proxy?quest=",
        "https://netnr.zme.ink/api/Open/Proxy?url="
    ];

    static proxyIndex = 0;
    static DEFAULT_TIMEOUT = 10000; // 10秒
    static DEFAULT_CONCURRENCY = 6;

    static getProxyUrl(link) {
        const proxyServer = this.PROXY_SERVERS[this.proxyIndex++];
        if (this.proxyIndex >= this.PROXY_SERVERS.length) {
            this.proxyIndex = 0;
        }
        return `${proxyServer}${encodeURIComponent(link)}`;
    }

    static async checkSingleLink(link, timeout = this.DEFAULT_TIMEOUT) {
        const url = this.getProxyUrl(link.href);
        
        try {
            const controller = new AbortController();
            const timeoutId = setTimeout(() => controller.abort(), timeout);
            
            const response = await fetch(url, {
                signal: controller.signal
            });
            
            clearTimeout(timeoutId);
            
            return {
                link,
                success: response.ok
            };
        } catch (error) {
            return {
                link,
                success: false
            };
        }
    }

    static async checkLinks(
        links = document.links,
        concurrency = this.DEFAULT_CONCURRENCY,
        timeout = this.DEFAULT_TIMEOUT
    ) {
        const linksArray = Array.from(links);
        const resultOk = [];
        const resultBad = [];
        
        // 并发控制
        let currentIndex = 0;
        
        const processNext = async () => {
            while (currentIndex < linksArray.length) {
                const index = currentIndex++;
                const link = linksArray[index];
                
                try {
                    const result = await this.checkSingleLink(link, timeout);
                    
                    if (result.success) {
                        resultOk.push(result.link);
                    } else {
                        resultBad.push(result.link);
                    }
                    
                    console.debug(`ok: ${resultOk.length}, bad: ${resultBad.length}, progress: ${index + 1}/${linksArray.length}`);
                } catch (error) {
                    resultBad.push(link);
                    console.debug(`ok: ${resultOk.length}, bad: ${resultBad.length}, progress: ${index + 1}/${linksArray.length}`);
                }
            }
        };
        
        // 启动并发任务
        const tasks = Array(concurrency).fill(null).map(() => processNext());
        await Promise.all(tasks);
        
        // 输出结果
        if (resultBad.length > 0) {
            console.debug('坏链接列表:');
            console.debug(resultBad.map(link => link.href).join('\r\n'));
        }
        
        return {
            okLinks: resultOk,
            badLinks: resultBad
        };
    }

    static async init(links, concurrency, timeout) {
        console.debug('开始检测链接...');
        const result = await this.checkLinks(links, concurrency, timeout);
        console.debug('检测完成!');
        return result;
    }
}

// 使用
LinkChecker.init();
登录写评论