picture_collector

Collect pictures of paginated structure picture website

// ==UserScript==
// @name         picture_collector
// @namespace    http://tampermonkey.net/
// @version      1.0.5
// @description  Collect pictures of paginated structure picture website
// @author       petitepluie
// @match        http://pic.netbian.com/*
// @match        http://www.gaoimg.com/*
// @match        *://desk.zol.com.cn/*
// @match        https://588ku.com/*
// @match        https://www.pixivacg.com/*
// @match        https://www.10wallpaper.com/*
// @match        http://www.netbian.com/*
// @grant        none
// @license      MIT
// ==/UserScript==


(function(){
    //获取本页地址
    var selfUrl=window.location.href;
    
    //添加界面css
    var mystyle=document.createElement("style");
    mystyle.innerHTML=`#popUpBox {
            position:fixed;
            top:0;
            right:-618px;
            z-index:10000;
            width:650px;
            height:20px;
            overflow-y:scroll;
            border:1px solid blue;
            background:#fff;
        }
        #boxCont {
            display:none;
        }
        #showAllConfig {
            width:100%;
        }
        #boxTitle {
            width:100%;
            height:22px;
            color:#fff;
            background:#0451ad;
        }
        #show_hid {
            width:32px;
            height:18px;
            line-height:15px;
            color:#0451ad;
            font-size:15px;
            font-weight:bold;
        }
        #baseOptionPlace {
            width:100%;
            height:50px;
        }
        #baseOptionCont {
            float:left;
            width:90%;
            height:100%;
        }
        #baseOptionCont span {
            font-size:12px;
        }
        #baseOptionCont input {
            height:10px;
        }
        .baseOption {
            width:100%;
            height:50%;
        }
        .reqLinkTitle {
            float:left;
            width:15%;
            height:100%;
            text-indent:5px;
            background:#c5dcfb;
        }
        .reqLinkCont {
            float:left;
            width:85%;
            height:100%;
        }
        #baseOptionTitle {
            float:right;
            width:10%;
            height:100%;
            
            background:#438dec;
        }
        #boxOptionPlace {
            width:100%;
        }
        .boxOption {
            width:100%;
            height:100px;
            margin-top:6px;
            border-top:2px solid blue;
            border-bottom:2px solid blue;
        }
        .boxOptionCont {
            float:left;
            width:90%;
            height:100%;
        }
        .options {
            width:100%;
            height:33%;
            border-bottom:0.5px solid blue;
        }
        .optionsTitle {
            float:left;
            width:15%;
            height:100%;
            line-height:30px;
            text-indent:5px;
            background:#c5dcfb;
        }
        .optionsCont {
            float:right;
            width:85%;
            height:100%;
        }
        .options span {
            font-size:12px;
        }
        .options input {
            height:10px;
        }
        .optionsContCube {
            float:left;
            width:50%;
            height:100%;
        }
        .optionsContName {
            width:100%;
            height:40%;
            line-height:10px;
            text-align:center;
            border-left:2px solid #fff;
            background:#deebf3;
        }
        .optionsContInput {
            width:100%;
            height:60%;
            line-height:15px;
        }
        .inputName {
            width:28%;
        }
        .inputNumb {
            width:12%;
        }
        .inputLink {
            width:56%;
        }
        .boxOptionTitle {
            float:right;
            width:10%;
            height:100%;
            background:#438dec;
        }
        .boxOptionTitle span {
            font-size:18px;
            color:#fff;
        }
        #dealOptionPlace {
            width:100%;
            height:36px;
        }
        .dealBtn {
            float:left;
            width:14%;
            height:25px;
            line-height:25px;
            margin:5px 10px;
            font-size:12px;
            text-align:center;
            border-radius:3px;
            background:rgba(0,0,0,0.5);
            transition:all 0.1s linear;
            cursor: pointer;
        }
        #start {
            background:#438dec;
            color:#fff;
        }
        #add {
            background:#c9defb;
        }
        #redu {
            background:#c9defb;
        }
        #add:hover {
            background:#438dec;
            color:#fff;
        }
        #redu:hover {
            background:#438dec;
            color:#fff;
        }
        #showCollectState {
            width:100%;
            height:40px;
            border-top:1px solid blue;
        }
        #sCsTitle {
            float:left;
            width:13%;
            height:100%;
            line-height:40px;
            font-size:14px;
            font-weight:bold;
            text-align:center;
            color:#0451ad;
        }
        #sCsCont {
            float:left;
            width:87%;
            height:100%;
            line-height:40px;
        }
        #showAllPicture {
            width:100%;
            height:490px;
            background:#438dec;
            overflow-y:scroll;
        }
        .cubeBox {
            float:left;
            width:85px;
            height:50px;
            margin:1px;
            background:rgba(0,0,0,0.5);
            overflow:hidden;
        }
        .cubeBox img {
            width:100%;
        }`;
    document.head.append(mystyle);
    
    //创建界面html
    var interFace=document.createElement('div');
    interFace.id='popUpBox';
    interFace.innerHTML=`
        <div id="boxTitle">
            <button id="show_hid">&lt;&lt;</button>
            <span>图片采集器(v.1.0.5)</span>
        </div>
        <div id="boxCont">
            <div id="showAllConfig">
                <div id="baseOptionPlace">
                    <div id="baseOptionCont">
                        <div class="baseOption">
                            <div class="reqLinkTitle"><span>分页链接拆分</span></div>
                            <div class="reqLinkCont">
                                <span>索引前:</span><input class="inputLink" type="text">
                                <span>索引后:</span><input class="inputNumb" type="text">
                            </div>
                        </div>
                        <div class="baseOption">
                            <div class="reqLinkTitle"><span>采集分页范围</span></div>
                            <div class="reqLinkCont">
                                <span>开始索引:</span><input class="inputNumb" type="number" min="2">
                                <span>结束索引:</span><input class="inputNumb" type="number" min="2">
                            </div>
                        </div>
                    </div>
                    <div id="baseOptionTitle">基础信息配置</div>
                </div>
                <div id="boxOptionPlace"></div>
                <div id="dealOptionPlace">
                    <div id="start" class="dealBtn">开始采集</div>
                    <div id="add" class="dealBtn">选择器+</div>
                    <div id="redu" class="dealBtn">选择器-</div>
                </div>
            </div>
            <div id="showCollectState">
                <div id="sCsTitle">采集状态:</div>
                <div id="sCsCont">null</div>
            </div>
            <div id="showAllPicture">
                <div class="cubeBox"></div>
                <div class="cubeBox"></div>
                <div class="cubeBox"></div>
                <div class="cubeBox"></div>
                <div class="cubeBox"></div>
                <div class="cubeBox"></div>
                <div class="cubeBox"></div>
            </div>
        </div>`;
    document.body.append(interFace);
    
    //添加界面逻辑
    var popUpBox=document.getElementById("popUpBox");
    var boxCont=document.getElementById("boxCont");
    var show_hid=document.getElementById("show_hid");
    var display=false;
    show_hid.onclick=function(){
        if(display){
            startMove(popUpBox,{
                "height":20,
                "right":-618
            },function(){
                display=false;
                show_hid.innerHTML="&lt;&lt;";
            })
        } else {
            boxCont.style.display="block";
            startMove(popUpBox,{
                "height":750,
                "right":0
            },function(){
                display=true;
                show_hid.innerHTML="&gt;&gt;";
            })
        }
    }
    
    var boxOptionPlace=document.getElementById('boxOptionPlace');
    boxOptionPlace.n=0;
    var boxOption=document.getElementsByClassName("boxOption");
    var add=document.getElementById("add");
    var redu=document.getElementById("redu");
    add.onclick=function(){
        var boxOptionTemp=document.createElement('div');
        boxOptionTemp.className='boxOption';
        boxOptionTemp.innerHTML=`
        <div class="boxOptionCont">
            <div class="options">
                <div class="optionsTitle"><span>父级节点</span></div>
                <div class="optionsCont parent">
                    <div class="optionsContCube">
                        <div class="optionsContName"><span>id</span></div>
                        <div class="optionsContInput">
                            <span>名称:</span><input class="inputName" type="text">
                        </div>
                    </div>
                    <div class="optionsContCube">
                        <div class="optionsContName"><span>class</span></div>
                        <div class="optionsContInput">
                            <span>名称:</span><input class="inputName" type="text">
                            <span>索引:</span><input class="inputNumb" type="number" min="0">
                        </div>
                    </div>
                </div>
            </div>
         
            <div class="options">
                <div class="optionsTitle"><span>子级节点集</span></div>
                <div class="optionsCont children">
                    <div class="optionsContCube">
                        <div class="optionsContName"><span>tag</span></div>
                        <div class="optionsContInput">
                            <span>名称:</span><input class="inputName" type="text">
                        </div>
                    </div>
                    <div class="optionsContCube">
                        <div class="optionsContName"><span>class</span></div>
                        <div class="optionsContInput">
                            <span>名称:</span><input class="inputName" type="text">
                        </div>
                    </div>
                </div>
            </div>
         
            <div class="options">
                <div class="optionsTitle"><span>目标元素位置</span></div>
                <div class="optionsCont flower">
                    <div class="optionsContCube">
                        <div class="optionsContName"><span>tag</span></div>
                        <div class="optionsContInput">
                            <span>名称:</span><input class="inputNumb" type="text">
                            <span>索引:</span><input class="inputNumb" type="number" min="0">
                            <span>属性:</span><input class="inputNumb" type="text" min="0">
                        </div>
                    </div>
                    <div class="optionsContCube">
                        <div class="optionsContName"><span>class</span></div>
                        <div class="optionsContInput">
                            <span>名称:</span><input class="inputNumb" type="text">
                            <span>索引:</span><input class="inputNumb" type="number" min="0">
                            <span>属性:</span><input class="inputNumb" type="text" min="0">
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="boxOptionTitle"><span>${++boxOptionPlace.n}级</span>页面元素选择器</div>`;
        boxOptionPlace.appendChild(boxOptionTemp);
        
    }
        
    redu.onclick=function(){
        if(boxOptionPlace.n==0) return;
        boxOptionPlace.removeChild(boxOption[--boxOptionPlace.n]);
    }
    add.click();
        
    /**
     * 输入数据处理
     */
    //基础数据获取
    var baseOptionCont=document.getElementById('baseOptionCont');
    var baseOptionData=baseOptionCont.getElementsByTagName('input');
    var arrUrlFirst=[];
    
    var start=document.getElementById('start');
    //父级选择器获取
    var parent=document.getElementsByClassName('parent');
    //子级选择器获取
    var children=document.getElementsByClassName('children');
    //flower选择器获取
    var flower=document.getElementsByClassName('flower');
    
    //存放临时选择器数据的容器
    var arrPCFselect=[[],[],[]];
    
    //存放最终选择器数据的容器
    var arrSelector=[];
    
    //显示提示的容器
    var sCsCont=document.getElementById('sCsCont');
    
    start.onclick=function(){
        arrUrlFirst=[];
        if(baseOptionData[0].value!=''&&baseOptionData[1].value!=''&&baseOptionData[2].value!=''&&baseOptionData[3].value!=''){
            if(parseInt(baseOptionData[2].value)>parseInt(baseOptionData[3].value)){
                sCsCont.innerHTML='<span style="color:red">开始索引勿大于结束索引!</span>';
                return;
            }
            for(let i=parseInt(baseOptionData[2].value);i<=parseInt(baseOptionData[3].value);i++){
                arrUrlFirst.push(baseOptionData[0].value+i+baseOptionData[1].value);
            }
        } else {
            sCsCont.innerHTML='<span style="color:red">基础数据需填写完全,请检查!</span>';
            return;
        }
        
        //清空容器
        arrPCFselect=[[],[],[]];
        arrSelector=[];
        
        //存放临时选择器数据
        for(let i=0;i<boxOptionPlace.n;i++){
            //父级选择器
            var prtInput=parent[i].getElementsByTagName('input');
            if(prtInput[0].value!=''){
                arrPCFselect[0].push('id___'+prtInput[0].value);
            } else if(prtInput[1].value!=''&&prtInput[2].value!='') {
                arrPCFselect[0].push('class___'+prtInput[1].value+'___'+prtInput[2].value);
            } else {
                sCsCont.innerHTML='<span style="color:red">'+(i+1)+'级页面的父级节点选择器配置不能为空,请检查!</span>';
                return;
            }
            
            //子级选择器
            var cldInput=children[i].getElementsByTagName('input');
            if(cldInput[0].value!=''){
                arrPCFselect[1].push('tag___'+cldInput[0].value);
            } else if(cldInput[1].value!='') {
                arrPCFselect[1].push('class___'+cldInput[1].value);
            } else {
                sCsCont.innerHTML='<span style="color:red">'+(i+1)+'级页面的子级节点集选择器配置不能为空,请检查!</span>';
                return;
            }
            
            //flower选择器
            var flrInput=flower[i].getElementsByTagName('input');
            if(flrInput[0].value!=''&&flrInput[1].value!=''&&flrInput[2].value!=''){
                arrPCFselect[2].push('tag___'+flrInput[0].value+'___'+flrInput[1].value+'___'+flrInput[2].value);
            } else if(flrInput[3].value!=''&&flrInput[4].value!=''&&flrInput[5].value!='') {
                arrPCFselect[2].push('class___'+flrInput[3].value+'___'+flrInput[4].value+'___'+flrInput[5].value);
            } else {
                sCsCont.innerHTML='<span style="color:red">'+(i+1)+'级页面的目标元素选择器配置不能为空,请检查!</span>';
                return;
            }
            
        }
        //console.log("临时选择器数据如下:");
        //console.log(JSON.stringify(arrPCFselect));
        
        //获取最终选择器数据
        for(let i=0;i<boxOptionPlace.n;i++){
            var timerObj={
                parent:`${arrPCFselect[0][i]}`,
                children:`${arrPCFselect[1][i]}`,
                flower:`${arrPCFselect[2][i]}`
            }
            arrSelector.push(timerObj);
        }
        
        console.log("基础配置数据如下:");
        console.log(arrUrlFirst);
        console.log("最终选择器结果如下:");
        console.log(arrSelector);
        
        
        //清空待请求队列
        watingRequest=[];
        //设置清秋层级
        intDiving=arrSelector.length;
        //放入待请求队列集合
        watingRequest.push(arrUrlFirst);
        //记录队列长度
        watingLength.push(arrUrlFirst.length);
        
        console.log("队列结果如下:");
        console.log(watingRequest);
        
        if("当前没有其他采集任务"){
            console.log("执行采集任务");
            sCsCont.innerHTML='<span style="color:purple">采集中,请稍等...</span>';
            //开启采集程序
            spider();
        }
    }
    
    /*******************采集核心代码**************************/
    //存放各级待请求队列集合
    var watingRequest=[];
    
    var watingLength=[];
    
    var intDiving=0;
    
    var j=0;
    
    var finalResult=[];
    
    function spider(){
        ajax({
            url:watingRequest[j][0],
            type:"GET",
            sync:true,
            responseType:"document",
            success:function(xhr){
                var _document=xhr.responseXML.documentElement;
                var nowSelector=arrSelector[j];
                var parent=null;
                var children=null;
                var flower=null;

                var ArrUrl=[];
                var parentMegs=nowSelector.parent.split("___");

                switch(parentMegs[0]){
                    case "id":parent=_document.getElementById(parentMegs[1]);break;
                    case "class":parent=_document.getElementsByClassName(parentMegs[1])[parentMegs[2]];break;
                    default: alert("parent空缺!");
                }
                
                var childrenMegs=nowSelector.children.split("___");
                switch(childrenMegs[0]){
                    case "class":children=parent.getElementsByClassName(childrenMegs[1]);break;
                    case "tag":children=parent.getElementsByTagName(childrenMegs[1]);break;
                    default: alert("children空缺!");
                }
                
                var flowerMegs=nowSelector.flower.split("___");
                switch(flowerMegs[0]){
                    case "class":getArrUrlByClass();break;
                    case "tag":getArrUrlByTag();break;
                    default: alert("flower空缺!");
                }
                
                function getArrUrlByClass(){
                    for(let i=0;i<children.length;i++){
                        flower=children[i].getElementsByClassName(flowerMegs[1])[flowerMegs[2]];
                        var url=flower.getAttribute(flowerMegs[3]);
                        if(url!=null){
                            ArrUrl.push(url);
                        }
                    }
                }
                
                function getArrUrlByTag(){
                    for(let i=0;i<children.length;i++){
                        flower=children[i].getElementsByTagName(flowerMegs[1])[flowerMegs[2]];
                        var url=flower.getAttribute(flowerMegs[3]);
                        if(url!=null){
                            ArrUrl.push(url);
                        }
                    }
                }
                
                if(ArrUrl.length==0){
                    alert("队列为空!");   
                    console.log(xhr);
                    console.log(_document);
                    console.log(arrSelector[j]);
                    console.log(ArrUrl);
                } else {
                    //console.log("成功获取队列!");
                    //console.log(_document);
                    //console.log(arrSelector[j]);
                    //console.log(ArrUrl);
                }
                
                watingRequest[j].shift();
                
                if((j+1)==intDiving){
                    var strName="_";
                    for(let i=0;i<=j;i++){
                        strName += (watingLength[i]-watingRequest[i].length)+"_";
                    }
                    
                    /**
                     * 获取到图片链接
                     */
                    //1.直接下载
                    /*
                    for(var i=0;i<ArrUrl.length;i++){
                        var imgName=strName+(i+1)+".jpg";
                        var imgUrl=ArrUrl[i];
                        //downloadPicture(imgName,imgUrl);
                        console.log("下载了: "+strName+(i+1)+".jpg"+" :"+ArrUrl[i]);  //后缀动态生成
                    }
                    */
                    
                    //2.添加到最终下载集合(推荐)
                    /*
                    for(var i=0;i<ArrUrl.length;i++){
                        finalResult.push(ArrUrl[i]);
                    }
                    */
                    
                    //3.添加到页面显示 showAllPicture
                    for(let i=0;i<ArrUrl.length;i++){
                        var imgName=strName+(i+1)+".jpg";
                        var imgUrl=ArrUrl[i];
                        var elem=document.createElement('div');
                        elem.className='cubeBox';
                        elem.innerHTML=`<img src="${imgUrl}" title="${imgName}">`;
                        showAllPicture.appendChild(elem);
                    }
                    
                    if(watingRequest[j].length!=0){
                        spider();
                    } else {
                        
                        console.log("判断是否完成前的j和队列:");
                        console.log(j);
                        console.log(JSON.stringify(watingRequest));
                        
                        //判断是否完成
                        function isEnd(){
                            for(var i=j;i>=0;i--){
                                if(watingRequest[i].length!=0){
                                    j=i;
                                    return false;
                                } else {
                                    watingRequest.pop();
                                }
                            }
                            return true;
                        }
                        var end=isEnd();
                        
                        console.log("判断是否完成后的j和队列:");
                        console.log(j);
                        console.log(JSON.stringify(watingRequest));
                        
                        if(end){
                            console.log("程序结束,任务队列如下");
                            var allImgLoaded=checkImgLoaded();
                            var startCheckTime=Date.now();
                            var timer=setInterval(function(){
                                if(allImgLoaded){
                                    clearInterval(timer);
                                    console.log('图片加载完,改变页面形态');

                                    var showAllConfig=document.getElementById('showAllConfig');
                                    
                                    boxCont.removeChild(showAllConfig);
                                    
                                    while(mystyle.previousElementSibling){
                                        document.head.removeChild(document.head.firstChild);
                                    }
                                    var title=document.createElement('title');
                                    title.innerHTML='采集图片——文件夹';
                                    document.head.appendChild(title);
                                    
                                    while(popUpBox.previousElementSibling){
                                        document.body.removeChild(document.body.firstChild);
                                    }
                                    
                                    sCsCont.innerHTML='<span style="color:green">完成,请按组合键"Ctrl+S"选择一个位置保存采集的图片</span>&nbsp;&nbsp;&nbsp;&nbsp;'+
                                        `<a href="${selfUrl}" alt="返回">否则返回</a>`;
                                } else {
                                    if(Date.now()-startCheckTime>10000){
                                        sCsCont.innerHTML='<span style="color:yellowgreen">图片大部分加载完...可以按组合键"Ctrl+S"选择一个位置保存采集的图片</span>';
                                        allImgLoaded='true';
                                    }
                                    sCsCont.innerHTML='<span style="color:red">图片还未加载完,请稍等...</span>';
                                }
                            },200);
                            return;
                        } else {
                            spider();
                        }
                    }
                } else {
                    watingRequest.push(ArrUrl);
                    watingLength.push(ArrUrl.length);
                    j++;
                    spider();
                }
            },
            error:function(xhr){
                alert(xhr.status+":ajax请求"+this.url+"失败!");
                console.log(xhr);
            }
        })
    }
    
    //ajax工具函数
    function ajax(obj){
        var xhr=null;
        if(window.XMLHttpRequest){
            xhr=new window.XMLHttpRequest();
        } else {
            xhr=new ActiveXObject("Microsoft.XMLHTTP");
        }
        xhr.responseType=obj.responseType;
        xhr.onreadystatechange=function(){
            if (xhr.readyState!=4) return;
            if(xhr.status==200){
                obj.success(xhr);
            } else {
                obj.error(xhr);
            }
        }
        xhr.open(obj.type,obj.url,obj.sync);
        xhr.send();
    }
    
    
    //工具函数4 下载图片(待完善)
    function downloadPicture(imgName,imgUrl){
        //return new Promise(function(resolve,reject){
            var anchor=document.createElement("A");
            anchor.href=imgUrl;
            anchor.download=imgName;
            anchor.target="_blank";
            document.body.appendChild(anchor);
            anchor.click();
            //给单个图片下载任务分配0.5s
        //    setTimeout(function(){
        //        resolve();
        //    },500)
        //})
    }
    
    
    //动画工具函数
    function startMove(obj,json,fn){
        clearInterval(obj.timer);
        obj.timer=setInterval(function(){
            var now='';
            var onStop=true;
            for(var attr in json){
                if(attr=='opacity'){
                    now=parseInt(parseFloat(getStyle(obj,attr))*100);
                } else {
                    now=parseInt(getStyle(obj,attr));
                }
                var speed=(json[attr]-now)/5; 
                speed=speed>0?Math.ceil(speed):Math.floor(speed);
                if(now!=json[attr]){
                    onStop=false;
                }
                if(attr=='opacity'){
                    obj.style[attr]=(now+speed)/100;
                } else {
                    obj.style[attr]=now+speed+'px';
                }
            }
            if(onStop){
                clearInterval(obj.timer);
                if(fn){
                    fn();
                }
            }
        },50);
    }
    function getStyle(obj,attr){
        if(obj.currentStyle){
            return obj.currentStyle(attr);
        } else {
            return getComputedStyle(obj,false)[attr];
        }
    }
    
    var showAllPicture=document.getElementById('showAllPicture');
    var allImg=showAllPicture.getElementsByTagName('img');
    //检查所有图片是否加载完全(待优化:不一定是所有图片都加载);
    function checkImgLoaded(){
        for(let i=0;i<allImg.length;i++){
            if(allImg[i].complete==false){
                return false;
            }
        }
        return true;
    }
    
})()