分享一个自动抓取静态站资源的小工具,可以在抓取某个静态站点的时候方便很多,尤其是如果页面比较多的话,会很难受,而且会自动将资源进行归类,如果一个页面一个页面的保存的话,那就比较费劲了。
平时多多少少会遇到需要抓某个站点的页面,这里只说静态站点哈,动态的应该也可以的,不过没测试过的。然后需要修修改改改成自己的页面,因此做了这么一个小工具,方便后续再出现这种需求不至于太麻烦。
抓取页面最主要的还是页面中涉及到的资源,我目前将资源主要分为以下几个:
在抓取资源的时候也不是所有都抓取,有些网站引入的是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