图片爬虫 V9 修正版

收集/下载图片,缩略图预览,大图查看,全选/反选,原文件名压缩下载

// ==UserScript==
// @name         图片爬虫 V9 修正版
// @namespace    http://tampermonkey.net/
// @version      9.0
// @description  收集/下载图片,缩略图预览,大图查看,全选/反选,原文件名压缩下载
// @author       YourName
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// ==/UserScript==

(function(){
    'use strict';

    const TIMEOUT = 30000;
    const MAX_RETRIES = 5;
    const downloadedUrls = new Set(JSON.parse(localStorage.getItem('downloadedImages')||'[]'));

    let imageUrls = [];

    // ---------------- UI ----------------
    const container = document.createElement('div');
    container.style.position='fixed';
    container.style.top='50px';
    container.style.right='50px';
    container.style.width='280px';
    container.style.backgroundColor='#fff';
    container.style.border='1px solid #ccc';
    container.style.padding='10px';
    container.style.zIndex='99999';
    container.style.fontSize='12px';
    container.style.borderRadius='6px';
    container.style.boxShadow='0 0 10px rgba(0,0,0,0.3)';
    container.style.overflow='hidden';
    document.body.appendChild(container);

    // 下拉格式
    const formatFilter = document.createElement('select');
    formatFilter.style.width='100%';
    formatFilter.style.marginBottom='5px';
    formatFilter.innerHTML = [
        '<option value="">全部格式</option>',
        '<option value="jpg">JPG</option>',
        '<option value="png">PNG</option>',
        '<option value="gif">GIF</option>',
        '<option value="webp">WEBP</option>',
        '<option value="bmp">BMP</option>'
    ].join('');
    container.appendChild(formatFilter);

    // 收集按钮
    const collectBtn = document.createElement('button');
    collectBtn.innerText='收集图片';
    collectBtn.style.width='100%';
    collectBtn.style.marginBottom='5px';
    container.appendChild(collectBtn);

    // 全选 / 反选
    const selectAllBtn = document.createElement('button');
    selectAllBtn.innerText='全选/反选';
    selectAllBtn.style.width='100%';
    selectAllBtn.style.marginBottom='5px';
    container.appendChild(selectAllBtn);

    // 下载按钮
    const downloadBtn = document.createElement('button');
    downloadBtn.innerText='全部下载';
    downloadBtn.style.width='100%';
    downloadBtn.style.marginBottom='5px';
    container.appendChild(downloadBtn);

    // 清空记录
    const clearBtn = document.createElement('button');
    clearBtn.innerText='清空已下载记录';
    clearBtn.style.width='100%';
    clearBtn.style.marginBottom='5px';
    container.appendChild(clearBtn);

    // 下载进度
    const progressText = document.createElement('div');
    progressText.style.textAlign='center';
    progressText.style.marginBottom='5px';
    container.appendChild(progressText);

    // 图片列表
    const imgListDiv = document.createElement('div');
    imgListDiv.style.maxHeight='300px';
    imgListDiv.style.overflowY='auto';
    imgListDiv.style.borderTop='1px dashed #ddd';
    imgListDiv.style.marginTop='5px';
    container.appendChild(imgListDiv);

    // ---------------- 工具函数 ----------------
    function extFromUrl(url){
        try{
            const clean=url.split('#')[0].split('?')[0];
            const part=clean.split('/').pop()||'';
            const ext=(part.includes('.')?part.split('.').pop():'').toLowerCase();
            return ext||'jpg';
        }catch(e){ return 'jpg'; }
    }

    function getFileName(url){
        try{ return url.split('/').pop().split('?')[0].split('#')[0]; }catch(e){ return 'image.jpg'; }
    }

    function getCandidateSrcs(img){
        const srcs=[];
        if(img.src) srcs.push(img.src);
        if(img.dataset){
            for(const key of ['src','srcset','original','lazy','lazySrc','lazyLoad','image','bg']){
                const k='data-'+key.replace(/[A-Z]/g,m=>'-'+m.toLowerCase());
                if(img.getAttribute&&img.getAttribute(k)) srcs.push(img.getAttribute(k));
            }
            if(img.dataset.src) srcs.push(img.dataset.src);
            if(img.dataset.original) srcs.push(img.dataset.original);
        }
        if(img.srcset){
            img.srcset.split(',').forEach(u=>srcs.push(u.trim().split(' ')[0]));
        }
        return srcs.filter(Boolean);
    }

    async function fetchImageWithRetry(url){
        for(let i=0;i<MAX_RETRIES;i++){
            try{
                const blob=await new Promise((resolve,reject)=>{
                    const timer=setTimeout(()=>reject('timeout'),TIMEOUT);
                    GM_xmlhttpRequest({
                        method:'GET',
                        url,
                        responseType:'blob',
                        onload:res=>{ clearTimeout(timer); resolve(res.response); },
                        onerror:e=>{ clearTimeout(timer); reject(e); }
                    });
                });
                if(blob && blob.size>0) return blob;
            }catch(e){ if(i===MAX_RETRIES-1) return null; }
        }
        return null;
    }

    // ---------------- 收集 & 渲染 ----------------
    function collectImages(){
        imageUrls=[];
        const filter=(formatFilter.value||'').toLowerCase();
        document.querySelectorAll('img').forEach(img=>{
            getCandidateSrcs(img).forEach(url=>{
                const ext=extFromUrl(url);
                if((!filter || ext===filter) && !imageUrls.includes(url)) imageUrls.push(url);
            });
        });
        renderImageList();
        progressText.innerText=`已收集图片: ${imageUrls.length}`;
    }

    function renderImageList(){
        imgListDiv.innerHTML='';
        imageUrls.forEach(url=>{
            const row=document.createElement('div');
            row.style.display='flex';
            row.style.alignItems='center';
            row.style.padding='4px 0';

            const checkbox=document.createElement('input');
            checkbox.type='checkbox';
            const isDownloaded=downloadedUrls.has(url);
            checkbox.checked=!isDownloaded;
            checkbox.style.marginRight='6px';
            checkbox.addEventListener('change', e=>{
                if(e.target.checked) downloadedUrls.delete(url);
                else downloadedUrls.add(url);
                thumb.style.opacity = downloadedUrls.has(url) ? '0.45' : '1';
            });
            row.appendChild(checkbox);

            const thumb=document.createElement('img');
            thumb.src=url;
            thumb.style.width='50px';
            thumb.style.height='50px';
            thumb.style.objectFit='cover';
            thumb.style.marginRight='6px';
            thumb.style.borderRadius='4px';
            thumb.style.cursor='pointer';
            thumb.style.transition='opacity .15s ease';
            if(isDownloaded) thumb.style.opacity='0.45';
            thumb.addEventListener('click',()=>window.open(url));
            row.appendChild(thumb);

            const link=document.createElement('a');
            link.href=url;
            link.target='_blank';
            link.textContent=getFileName(url);
            link.style.fontSize='11px';
            link.style.marginLeft='2px';
            row.appendChild(link);

            imgListDiv.appendChild(row);
        });
    }

    // ---------------- 下载 ----------------
    async function downloadAll(){
        const pendingUrls=imageUrls.filter(url=>!downloadedUrls.has(url));
        if(pendingUrls.length===0){ alert('没有可下载的图片'); return; }

        const zip=new JSZip();
        progressText.innerText='开始下载...';
        for(let i=0;i<pendingUrls.length;i++){
            const url=pendingUrls[i];
            const blob=await fetchImageWithRetry(url);
            if(blob) zip.file(getFileName(url), blob);
            else console.warn('下载失败:', url);
            progressText.innerText=`下载进度: ${i+1}/${pendingUrls.length}`;
        }
        const content=await zip.generateAsync({type:'blob'});
        const a=document.createElement('a');
        a.href=URL.createObjectURL(content);
        a.download=`images_${new Date().toISOString().split('T')[0]}.zip`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        pendingUrls.forEach(u=>downloadedUrls.add(u));
        localStorage.setItem('downloadedImages', JSON.stringify(Array.from(downloadedUrls)));
        progressText.innerText='下载完成';
        renderImageList();
        alert('下载完成');
    }

    // ---------------- 事件 ----------------
    collectBtn.addEventListener('click',collectImages);
    selectAllBtn.addEventListener('click',()=>{
        const checkboxes=imgListDiv.querySelectorAll('input[type=checkbox]');
        const allChecked=Array.from(checkboxes).every(cb=>cb.checked);
        checkboxes.forEach(cb=>cb.checked=!allChecked);
        const thumbs=imgListDiv.querySelectorAll('img');
        thumbs.forEach((img,i)=>{
            const url=imageUrls[i];
            const cb=checkboxes[i];
            if(cb && url){
                if(cb.checked) downloadedUrls.delete(url);
                else downloadedUrls.add(url);
                img.style.opacity=downloadedUrls.has(url)?'0.45':'1';
            }
        });
    });
    downloadBtn.addEventListener('click',downloadAll);
    clearBtn.addEventListener('click',()=>{
        downloadedUrls.clear();
        localStorage.removeItem('downloadedImages');
        renderImageList();
        alert('已清空已下载记录');
    });
    formatFilter.addEventListener('change',collectImages);

})();// ==UserScript==
// @name         New Userscript
// @namespace    https://bbs.tampermonkey.net.cn/
// @version      0.1.0
// @description  try to take over the world!
// @author       You
// @match        
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
})();