分享一个自动爬取静态站的工具

分享一个自动爬取静态站的工具

月光魔力鸭

2020-05-20 09:53 阅读 44 喜欢 0 整站抓取 nodejs 爬虫

分享一个自动抓取静态站资源的小工具,可以在抓取某个静态站点的时候方便很多,尤其是如果页面比较多的话,会很难受,而且会自动将资源进行归类,如果一个页面一个页面的保存的话,那就比较费劲了。

平时多多少少会遇到需要抓某个站点的页面,这里只说静态站点哈,动态的应该也可以的,不过没测试过的。然后需要修修改改改成自己的页面,因此做了这么一个小工具,方便后续再出现这种需求不至于太麻烦。

资源

抓取页面最主要的还是页面中涉及到的资源,我目前将资源主要分为以下几个:

规则

在抓取资源的时候也不是所有都抓取,有些网站引入的是cdn或者外部的资源文件,这类的就不准备抓取了,直接使用,还有些图片跟站点的域名不同,也需要将host添加进来,防止图片不再下载。

因此,做了以下几个变量:

//定义几个资源的前置路径,可以为空
//*** CSS 文件的路径前缀
let cssprefix = '/static/css/',
//=== 图片文件的路径前缀
    imgprefix = '/static/image/',
//=== js文件的路径前缀
    jsprefix = '/static/js/',
//=== css文件中的使用的背景图片相对于css文件的相对路径.
    cssimgprefix = '../image/';
//抓取目标内容
let targetUrl = `http://xxx.html`,
    targetFolderName =  `宝翔电商`;
let targetHost = ['www.xxx.com','hz.xxx.com'];
let pageCode = 'utf8';//页面编码,默认utf8,有些页面是gbk.
let deepFetch = false;//是否深度抓取,false则只抓取当前页面,true则从当前页面进行继续抓取。
let staticDomain = [];

定义好存储目录以及对应的host和编码,就可以开始了,给定一个入口路径,从该页面开始抓取A标签,进行挨个获取和存储。

核心代码

代码其实很简单,就是获取html代码后进行img css 内容解析就OK了,然后做个循环,依次抓取。


//流程:
/**
 * 通过主目标url,开始检索所有的a标签,然后进行递归a标签继续查询并保存。
 * 并根据地址做匹配。
 * 然后将所有的地址中的资源文件进行下载管理分配替换
 */
var tool = {
    mkdir : function(folderPath){
        if(fs.existsSync(folderPath)){
            return true;
        }else if(fs.existsSync(path.dirname(folderPath))){
            return fs.mkdirSync(folderPath);
        }else{
            tool.mkdir(path.dirname(folderPath));
            return fs.mkdirSync(folderPath);
        }
    },
    rs2ws : function(rs,ws){
        return new Promise((resolve,reject)=>{
            rs.pipe(ws);
            rs.on('end',()=>{
                resolve(null);
            })
            rs.on('error',(er)=>{
                reject(er);
            })
        });
    },
    //抓取目标页面内容、
    fetch : function(href){
        return new Promise((resolve,reject)=>{
            axios({url : href,responseType : 'stream'})
            .then(res=>{
                //此时的res.data 则为stream
                let chunks = [];
                res.data.on('data',chunk=>{
                    chunks.push(chunk);
                });
                res.data.on('end',()=>{
                    let buffer = Buffer.concat(chunks);
                    //通过iconv来进行转化。
                    let str = iconv.decode(buffer,pageCode);
                    resolve(str);
                })
            }).catch(err=>{
                reject(err);
            })
        });
    },
    fetchStream :function(href){
        return axios({
            url : href,
            method : 'get',
            responseType : 'stream'
        }).then(rs=>{
            return rs.data;
        })
    },
    //根据url获得对应的url名字,有些是外链地址。
    getFileName : function(href){
        //可以是直接计数,也可以是直接进行替换。
        const obj =url.parse(href);
        const pathhref = obj.pathname;
        let fileName = '';
        if(obj.path == '/'){
            fileName = 'index.html';
        }else{
            var extarr = ['.css','.js','.html','.jpg','.png','.gif','.bmp','.jpeg'];
            //其他的全是html
            var extname = path.extname(pathhref);
            var suffixName = '';
            if(extarr.indexOf(extname) > -1){
                suffixName = '';
            }else{
                suffixName = '.html';
            }
            fileName = pathhref.replace(/[\?\\\/><\"\*|]/g,'_')+''+suffixName;
        }
        return {
            href : href,
            fileName : fileName,
            host : obj.host,
            path : obj.path
        }
    },
    //是否应该下载,做判断。
    shouldDown : function(urlobj){
        if(!urlobj)return false;
        if(!urlobj.path)return false;
        if(targetHost.indexOf(urlobj.host) > -1)return true;
        if(urlobj.host == null && !urlobj.path.startsWith('//'))return true;
        if(staticDomain.length > 0 && staticDomain.indexOf(urlobj.host) > -1)return true;
        return false;
    },
    //下载文件,目标路径以及地址。
    download : async function(filePath,href){
        //先对filePath进行判重
        if(!fs.existsSync(filePath)){
            //判断文件属性,如果是文本,则直接下载,如果是文件,则通过stream
            if(!fs.existsSync(path.dirname(filePath))){
                tool.mkdir(path.dirname(filePath))
                // fs.mkdirSync(path.dirname(filePath));
            }
            try{
                var extname = path.extname(filePath);
                var txtArr = ['.css','.js'];
                if(extname.indexOf('.js') > -1 || extname.indexOf('.css') > -1){//文本
                    let content = await tool.fetch(href);
                    tool.mkdir(path.dirname(filePath));
                    fs.writeFileSync(filePath,content);
                    return content;
                }else{
                    let ws = fs.createWriteStream(filePath);
                    let rs = await tool.fetchStream(href);
                    await tool.rs2ws(rs,ws);
                }
            }catch(e){
                console.error(`文件下载失败:${filePath}---${href}`);
            }

        }
    }
}


//初始创建文件夹
if(!fs.existsSync(folderPath)){
    fs.mkdirSync(folderPath);
    // fs.mkdirSync(path.join(folderPath,'css'))
    // fs.mkdirSync(path.join(folderPath,'js'))
    // fs.mkdirSync(path.join(folderPath,'img'))
    // fs.mkdirSync(path.join(folderPath,'bg'))
}

;
//做循环处理
var urlMap = {};//做去重处理。

(async function(){
    while(pageUrlArr.length > 0){
        //每次都是第一个
        var currentUrl = pageUrlArr.splice(0,1)[0];
        console.log(`开始抓取页面:${currentUrl}`)
        //对href进行甄别处理。
        var fileNameObj = tool.getFileName(currentUrl);
        if(targetHost.indexOf(fileNameObj.host) > -1){//不是本站数据,忽略。
            //获取html 
            let htmlContent = await tool.fetch(currentUrl);
            //如果无法访问则忽略
            if(htmlContent != ''){
                //加入判断map
                let ccodeurl = encodeURIComponent(currentUrl);
                urlMap[ccodeurl] = fileNameObj.fileName;//加入map,防止重复下载
                //对内容进行解析,获得地址信息。
                let $ = cheerio.load(htmlContent);
                //检索所有的相关数据以及图片等
                const $a = $('a');//所有的a标签,并进行记录判断
                console.log(`获得A标签:${$a.length}`)
                $a.each(function(index,item){
                    var ahref = $(item).attr('href');
                    if(ahref && ahref.indexOf('javascript') < 0 && ahref.indexOf('void') < 0){//存在且不是js函数
                        //
                        var ahrefobj = url.parse(ahref);
                        let realHref = ahref;
                        //如果不是hostname
                        if(tool.shouldDown(ahrefobj)){//引入的其他的站点地址
                            if(ahrefobj.host == null){
                                realHref = new URL(ahref,currentUrl).href;
                            }
                            //并进行替换。
                            var fileInfo = tool.getFileName(realHref);
                            //此处替换需要通过正则进行替换
                            let regexp = new RegExp('"('+ahref+')"','g');
                            htmlContent = htmlContent.replace(regexp,'"'+fileInfo.fileName+'"');
                            let acodeurl = encodeURIComponent(realHref);
                            if(!urlMap[acodeurl] && deepFetch){
                                urlMap[acodeurl] = fileInfo.fileName;
                                pageUrlArr.push(realHref);
                            }
                        }
                    }
                })

                //js文件
                const $script = $('script').get();
                console.log(`获得js文件:${$script.length}`)
                for(let i in $script){
                    let item = $script[i];
                    let shref = $(item).attr('src');
                    if(shref){
                        let shrefobj = url.parse(shref);
                        let realHref = shref;
                        if(tool.shouldDown(shrefobj)){//相对路径
                            //进行下载。
                            if(shrefobj.host == null){
                                realHref = new URL(shref,currentUrl).href;
                            }
                            let fileInfo = tool.getFileName(realHref);
                            htmlContent = htmlContent.replace(shref,jsprefix+fileInfo.fileName)
                            await tool.download(path.join(folderPath,jsprefix,fileInfo.fileName),realHref);
                        }
                    }
                }

                //css文件
                const $css = $('link').get();
                console.log(`获得css文件:${$css.length}`)
                for(let i in $css){
                    let item = $css[i];
                    let chref = $(item).attr('href');
                    if(chref){
                        let realHref = chref;
                        let chrefobj = url.parse(chref);
                        if(tool.shouldDown(chrefobj)){//相对路径
                            //进行下载。
                            if(chrefobj.host == null){
                                realHref = new URL(chref,currentUrl).href;
                            }
                            let fileInfo = tool.getFileName(realHref);
                            htmlContent = htmlContent.replace(chref,cssprefix+fileInfo.fileName)
                            let cssContent = '';
                            try{
                                cssContent = await tool.fetch(realHref);
                            }catch(e){
                                console.log(`抓取失败:${e.message},${realHref}`)
                            }
                            //对css内容进行处理。
                            let cssarr = cssContent.match(/url\(([\s\S^]*?)\)/g);
                            if(cssarr && cssarr.length > 0){//存在
                                for(let m in cssarr){
                                    let cssitem = cssarr[m];
                                    let tempPath = (cssitem.match(/url\(([\s\S^]*?)\)/)[1]).trim();
                                    tempPath = tempPath.replace(/"/g,'');
                                    let cssbgreal = new URL(tempPath,realHref).href;
                                    //下载
                                    let cssbgInfo = tool.getFileName(cssbgreal);
                                    cssContent = cssContent.replace(tempPath,cssimgprefix+cssbgInfo.fileName);
                                    await tool.download(path.join(folderPath,imgprefix,cssbgInfo.fileName),cssbgreal);
                                }
                            }
                            //吸入
                            let tempCssPath = path.join(folderPath,cssprefix,fileInfo.fileName);
                            tool.mkdir(path.dirname(tempCssPath))
                            fs.writeFileSync(tempCssPath,cssContent);
                        }
                    }
                }

                //img 文件
                const $img = $('img').get();
                console.log(`获得图片:${$img.length}`)
                for(let i in $img){
                    let item = $img[i];
                    let chref = $(item).attr('src');
                    //由于
                    if(imgAttr!='' && $(item).attr(imgAttr) != undefined){
                        let dchref = $(item).attr(imgAttr);
                        let dchrefobj = url.parse(dchref);
                        let drealHref = dchref;
                        if(tool.shouldDown(dchrefobj)){//相对路径
                            //进行下载。
                            if(dchrefobj.host == null){
                                drealHref = new URL(dchref,currentUrl).href;
                            }
                            let dfileInfo = tool.getFileName(drealHref);
                            htmlContent = htmlContent.replace(dchref,imgprefix+dfileInfo.fileName);
                            await tool.download(path.join(folderPath,imgprefix,dfileInfo.fileName),drealHref);
                        }
                    }
                    if(chref){
                        let chrefobj = url.parse(chref);
                        let realHref = chref;
                        if(tool.shouldDown(chrefobj)){//相对路径
                            //进行下载。

                            if(chrefobj.host == null){
                                realHref = new URL(chref,currentUrl).href;
                            }
                            let fileInfo = tool.getFileName(realHref);
                            htmlContent = htmlContent.replace(chref,imgprefix+fileInfo.fileName);
                            await tool.download(path.join(folderPath,imgprefix,fileInfo.fileName),realHref);
                        }
                    }
                }

                //处理最后的background-image 的写死样式问题
                let bgarr = htmlContent.match(/url\(([\s\S^]*?)\)/g);
                console.log(`处理css内引用文件`)
                if(bgarr && bgarr.length > 0){//存在
                    for(let m in bgarr){
                        let bgitem = bgarr[m];
                        let tempPath = (bgitem.match(/url\(([\s\S^]*?)\)/)[1]).trim();
                        tempPath = tempPath.replace(/"/g,'');
                        let bgreal = new URL(tempPath,currentUrl).href;
                        //下载
                        let bgInfo = tool.getFileName(bgreal);
                        htmlContent = htmlContent.replace(tempPath,imgprefix+bgInfo.fileName);
                        await tool.download(path.join(folderPath,imgprefix,bgInfo.fileName),bgreal);
                    }
                }

                //将html存储起来。
                let currentFilePath = path.join(folderPath,fileNameObj.fileName);
                tool.mkdir(path.dirname(currentFilePath));
                // htmlContent = htmlContent.replace(currentUrl,fileNameObj.fileName);
                fs.writeFileSync(currentFilePath,htmlContent);

            }
        }
    }
    console.log(`执行完毕----------------`)
    process.exit(0);
})();


其实,这个也可以用来做爬某些不可描述网站的图片之类的都是可以的,看怎么用了,反正网页上的资源都可以弄下来,不过视频没增加...后面再说吧,目前没需求。

效果还是很好的,拿下来访问与原网站一样,稍微改动下就换了个名。

转载请注明出处: https://chrunlee.cn/article/spider-fetch-static-site.html


感谢支持!

赞赏支持
提交评论
评论信息(请文明评论)
暂无评论,快来快来写想法...
推荐
在使用puppeteer 跳转窗口的时候,发现waitForNavigator 并不起作用,最后找到通过browser 获得page 并继续操作。
从豆瓣转到网易云后,发现了不少好听的歌曲,然鹅..当我想把这些歌拿下来扔车上听的时候发现竟然不允许下载..能听不能下?这不科学,作为一名程序猿,必然要迎难而上啊.
通过node-xlsx模块读取excel和写入
跑了一个千库网的自动签到,在windows上测试的时候好好的,图片也没问题,可是放到linux服务器就不行了,总是登录不上不说,图片都不一样
因为自己的记录笔记的应用是有道云,又想着把有道云跟自己的小网站联通起来,所以查找了有道云的,然后实现了nodejs版本的sdk.
互联网应用经常需要存储用户上传的图片,比如facebook相册。 facebook目前存储了2600亿张照片,总大小为20PB,每张照片约为80KB。用户每周新增照片数量为10亿。(总大小60TB),平均每秒新增3500张照片(3500次写请求),读操作峰值可以达到每秒百万次
在通过axios读取页面的时候,经常会碰到gbk的编码,如果不进行转化的话,在获取信息或读取上都会很麻烦。
最近家里正在装修,实在是不知道怎么做,之前看好好住APP上有不少设计的图,部分还挺好看。。就去看了下有没有WEB端,结果还真有,就有了下文,我抓了几万张图片,然后根据关键字进行分类,从里面找心仪的设计。

广告位招租