单页扒手-基于Node的实现

news2024/11/20 14:34:16

做网站的朋友经常遇见别人的好看的页面,想保存到本地自己用,可是用以前的老办法网页另存为,发现很不好,规则不好处理,路径也不好处理,用这个网页克隆(单页模板扒手)就很好处理了,你只要输入你想要扒皮的网站地址,点击下载就可以了。

我就是最近看到一个很不错的后端的模板,想要扒下来,于是就上网找了个工具,叫做单页扒手。就是下面这个玩意儿。

 

结果下载完成之后,居然给我报有病毒,当我再去找的时候,我突然间想起来,我TM是个不靠谱的程序员,为什么这次就不能给你靠谱点儿自己写一个呢?于是就默默的打开了我的编辑器,建了个工程PageDownload。

基本思路

首先说明一点儿,我的这个单页抓包的东西,只是一个基础的,下载的内容也都是相对来说比较简单的(Just For Fun)。

  1. 给一个网页地址,请求加载网页的Html,然后将一个页面进行保存。
  2. 解析Html,查找Html中所有的资源(主要是CSS,JS和Image),以及连接,然后继续去请求,并且继续下载和保存。
  3. 如果连接的资源是Html的话,则继续进行解析然后下载
  4. 下载过的资源用记录下来,在遇到同样的资源的时候,需要进行检测,如果存在相同的资源,则不在进行下载和处理,避免进入死循环。

好了既然思路都有了,那就开始写代码吧。

可能存在的问题与解决:

  1. 嵌入的资源是第三方资源,与本站的地址不符我们需要处理?(有些模板网站嵌入的有谷歌字体什么的,这些暂时先不处理)。
  2. 某些Css中所引用的资源比如说图片,svg图标字体应该如何去解析?这些个暂时先不处理,毕竟这些都是少量的,
  3. 如果连接的第三方连接会很深应该如何处理?

所需要的第三方包

再开始之前我们根据上面的实现思路先安装一些需要用到的包:

  • request(网络请求)
  • cheerio(一个很好用的Html解析工具,效率非常高)
  • fs-extra(fs-extra模块是系统fs模块的扩展,提供了更多便利的API,并继承了fs模块的API,本文详细介绍所有操作方法,方便读者更好的操作服务端文件)

基本的代码实现

这里就不在瞎说,直接贴代码,具体的代码解释参考代码中的注释:

const path = require('path');
const cheerio = require('cheerio');
const fse = require('fs-extra');
const request = require('request');

// 需要爬取的页面的地址
const HOST = 'https://coderthemes.com/hyper';
// 爬取之后输出的位置
const OUT_PATH = path.join(__dirname, 'hyper');
// 用来记录已经下载过的资源,防止重复下载
const loadedResourceMap = new Map();
const loadErrorSet = new Set();

async function downloadHtmlPage(urlObj) {

    if (!fse.existsSync(urlObj.outputPath)) {
        await downloadHtmlResource(urlObj)
    } else {
        loadedResourceMap.set(urlObj.requestUrl, urlObj);
        return;
    }
    if (fse.existsSync(urlObj.outputPath)) {
        const fileData = fse.readFileSync(urlObj.outputPath, {encoding: 'utf-8'});
        if (fileData) {
            const { htmlResource = [], notHtmlResource = [] } = analysisHtmlData(fileData, urlObj.currentPath);
            for (let index = 0; index < notHtmlResource.length; index++) {
                if (!loadedResourceMap.has(notHtmlResource[index].requestUrl)) {
                    downloadNotHtmlResource(notHtmlResource[index]);
                }
            }
            for (let index = 0; index < htmlResource.length; index++) {
                if (!loadedResourceMap.has(htmlResource[index].requestUrl)) {
                    await downloadHtmlPage(htmlResource[index]);
                }
            }
        }
    }
}

function downloadNotHtmlResource(urlObj) {
    fse.ensureDirSync(urlObj.outputDir);
    request(urlObj.requestUrl)
        .pipe(fse.createWriteStream(urlObj.outputPath))
    loadedResourceMap.set(urlObj.requestUrl, urlObj);
    console.log('下载成功:', urlObj.requestUrl, '>>>>>>>>>>>', urlObj.outputPath);
}
async function downloadHtmlResource(urlObj) {
    return new Promise(resolve => {
        request(urlObj.requestUrl, (err, response) => {
            if (err) {
                console.log('下载失败:', urlObj.requestUrl);
                loadErrorSet.add(urlObj.requestUrl);
            } else if (response.statusCode === 200) {
                console.log('请求成功!');
                const body = response.body;
                fse.ensureFileSync(urlObj.outputPath);
                fse.writeFileSync(urlObj.outputPath, body, { encoding: 'utf-8' });
                loadedResourceMap.set(urlObj.requestUrl, urlObj);
                console.log('下载成功:', urlObj.requestUrl, '>>>>>>>>>>>', urlObj.outputPath);
            } else {
                loadErrorSet.add(urlObj.requestUrl);
                console.log(`下载失败${response.statusCode}:`, urlObj.requestUrl);
            }
            resolve();
        })
    })
}

function formatUrl(url, baseUrl = '/') {
    if (url.startsWith(HOST)) {
        url = url.replace(HOST, '');
    }
    if(url === 0 || url === '/') {
        url = 'index.html';
        // return {
        //     requestUrl: HOST + path.join(baseUrl, 'index.html'),
        //     outputDir: path.join(OUT_PATH, baseUrl),
        //     outputPath: path.join(OUT_PATH, baseUrl, 'index.html'),
        //     currentPath: baseUrl
        // }
    }
    url = url.startsWith('/') ? url.substring(1) : url;
    if (url.endsWith('/')) {
        url = url + 'index.html';
    }
    const urlArr = url.split('/');
    let lastFile = urlArr[urlArr.length - 1] || 'index.html';
    const temp = urlArr.slice(0, urlArr.length - 1);
    return {
        requestUrl: HOST + path.join(baseUrl, temp.join('/'), lastFile),
        outputDir: path.join(OUT_PATH, baseUrl, temp.join('/')),
        outputPath: path.join(OUT_PATH, baseUrl, temp.join('/'), lastFile),
        currentPath: path.join(baseUrl, temp.join('/'))
    }
}


function isOtherHost(url) {
    if (url.startsWith('http://') || url.startsWith('https://')) {
        return !url.startsWith(HOST);
    }
    return false;
}
function analysisHtmlData(htmlData, baseUrl = '/') {
    if (!htmlData) {
        return null;
    }
    const $ = cheerio.load(htmlData);
    const htmlResource = [];
    const notHtmlResource = []; // 这里主要放一些图片和script的资源连接,因为这两类资源都可以直接下载不用再做后期的处理

    $('link').each((index, $item) => {
        const href = $item.attribs.link;
        if (href && href.endsWith('.css') && !isOtherHost(href)) {
            const urlObject = formatUrl(href, baseUrl);
            if (!loadedResourceMap.has(urlObject.requestUrl) && !loadErrorSet.has(urlObject.requestUrl)) {
                notHtmlResource.push(urlObject);
            }
        }
    });

    $('img, script').each((index, $item) => {
        const src = $item.attribs.src;
        const srcTempArr = src ? src.split('.') : [];
        if (srcTempArr.length) {
            const resourceSuffix = srcTempArr[srcTempArr.length - 1];
            const canAddSuffixs = new Set(['js', 'png', 'jpg', 'jpeg', 'svg', 'gif']);
            if (canAddSuffixs.has(resourceSuffix) && !isOtherHost(src)) {
                const urlObject = formatUrl(src, baseUrl);
                if (!loadedResourceMap.has(urlObject.requestUrl) && !loadErrorSet.has(urlObject.requestUrl)) {
                    notHtmlResource.push(urlObject);
                }
            }
        }
    });

    $('a').each((index, $item) => {
        const href = $item.attribs.href;
        if (href && !href.startsWith('#') && !href.startsWith('javascript:') && !isOtherHost(href)) {
            const urlObject = formatUrl(href, baseUrl);
            if (!loadedResourceMap.has(urlObject.requestUrl) && !loadErrorSet.has(urlObject.requestUrl)) {
                htmlResource.push(urlObject);
            }
        }
    });

    return {
        htmlResource,
        notHtmlResource
    };
}

function main() {
    const urlObj = formatUrl('');
    downloadHtmlPage(urlObj);
}

main();

写完代码,跑起来,接下来就是静静的等待。下载完成后,运行页面是可以的。

小结

技术是用来解决问题和需求,离开问题和需求,技术的价值就真的很低很低。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/110451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里「杀手锏」级语音识别模型来了!推理效率较传统模型提升10倍,已开源

阿里达摩院&#xff0c;又搞事儿了。 这两天&#xff0c;它们发布了一个全新的语音识别模型&#xff1a; Paraformer。 开发人员直言不讳&#xff1a;这是我们“杀手锏”级的作品。 ——不仅识别准确率“屠榜”几大权威数据集&#xff0c;一路SOTA&#xff0c;推理效率上相比…

Zookeeper 4 Zookeeper JavaAPI 操作 4.7 Curator API 常用操作【Watch 事件监听】

Zookeeper 【黑马程序员Zookeeper视频教程&#xff0c;快速入门zookeeper技术】 文章目录Zookeeper4 Zookeeper JavaAPI 操作4.7 Curator API 常用操作4.7.1 Watch 事件监听4 Zookeeper JavaAPI 操作 4.7 Curator API 常用操作 4.7.1 Watch 事件监听 【基本概念】 ZooKeep…

异常见闻录-Java.lang.UnsupportedClassVersionError涨知识啦

今天有学生说自己的Eclipse的Java代码只能在新建的项目中运行&#xff0c;运行导入项目中类就报错 异常信息 java.lang.UnsupportedClassVersionError: com/demo1/Demo1Application has been compiled by a more recent version of the Java Runtime (class file version 5…

CMake入门

1. CMake的介绍&#xff1a; 当多人开发同一个项目时&#xff0c;最终要输出一个可执行文件或者共享库&#xff08;dll、so等&#xff09;&#xff0c;就可以使用cmake了 所有操作都是通过 CMakeLists.txt&#xff08;严格区分大小写&#xff09; 来完成的 ----> 简单 2. …

C51——超声波测距 函数封装和舵机代码结合

要注意舵机转动中 延时函数要放在哪里 #include "reg52.h" sbit D5 P3^7;// sbit D6 P3^6;// sbit Trig P1^5; sbit Echo P1^6; sbit sg90_con P1^1; int cnt; int jd; double time; void Delay10us() //11.0592MHz { unsigned ch…

操作系统管程-地址-重定位-内存管理与存储管理

管程的基本概念&#xff1a;为什么会出现管程&#xff1f;信号量机制的不足&#xff1a;程序编写困难、易出错解决&#xff1a;Brinch Hansen(1973)Boare(1974)方案&#xff1a;在程序设计语言中引入管程成分一种高级同步机制管程的定义&#xff1a;是一个特殊的模块有一个名字…

使用MAXScript脚本编写圣诞树建模插件教程

一、前言 2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 今年的圣诞节为大家分享用MAXScript脚本编写圣诞树建模插件的技术创意&#xff0c;喜欢的同学别忘记在下面点个赞&#xff01; 二、创意名 一键圣诞树插件 三、效果展示 四、实现步骤 1.制作圣诞树的设计稿…

智能巡检系统:企业安全生产管理的智能助手

智能巡检是一种高效代替传统人工巡检的新方式&#xff0c;其依靠物联感知技术&#xff0c;通过物联网采集获取信息&#xff0c;自动记录巡检信息&#xff0c;及时发现问题&#xff0c;实现巡检科学化。 工业4.0带来的技术革新加速了企业的转型升级进程&#xff0c;传统企业的运…

F5张振伦:让应用安全、快速、可靠地交付到需要的地方丨2022首届全球数字生态大会

科技云报道原创。 日前&#xff0c;由杭州市人民政府和浙江省商务厅主办的“2022首届全球数字生态大会”在杭州国际博览中心成功举办。 本次大会以“新技术、新业态、新模式”为主题&#xff0c;邀请到国内外20余位演讲嘉宾和超300位专业观众线下参会。 与此同时&#xff0c;…

IB成绩换成GPA,美国大学是如何算的?

IB课程体系是全球公认的难度大、结构强的课程体系。 IB课程可以通过IB文凭课程&#xff08;也称为IBDP&#xff09;进行系统研究。IB课程体系是国际公认的基础文凭&#xff0c;世界上几乎所有大学都认可IB课程体系。这是否意味着IB在申请美国大学时会有优势&#xff1f; 事实上…

【vue系列-02】vue的核心属性,数据代理,事件

vue的核心属性一&#xff0c;vue的核心属性1&#xff0c;模板语法1.1&#xff0c;插值语法1.2&#xff0c;指令语法2&#xff0c;数据绑定2.1&#xff0c;单向绑定2.2&#xff0c;双向绑定3.mvvm模型4&#xff0c;数据代理4.1&#xff0c;数据代理的基本使用4.2&#xff0c;数据…

pytorch实现好莱坞明星识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章地址&#xff1a; 365天深度学习训练营-第P6周&#xff1a;好莱坞明星识别&#x1f356; 作者&#xff1a;K同学啊一、前期准备 1.设置GPU import torch from torch import nn …

获取rdp保存的凭证

获取用户保存的rdp凭证 当获取到一台windows服务器&#xff0c;可以尝试获取本地远程连接的信息&#xff0c;如果用户在登入rdp时勾选了 允许为我保存凭证的选项&#xff0c;则在该用户本地会生成一个凭证文件&#xff0c;我们只需要破解该凭证文件即可获取其明文密码。 通过注…

[含文档+PPT+源码等]基于SSM框架图书借阅管理系统开发与设计

博主介绍&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 [含文档PPT源码等]基于SSM框架图书借阅管理系统开发与设计 系统介绍 《基于SSM框架图书管理系统开发与设计》 该项目含有源码、配套开发软件、软件安…

元数据相关的术语,你知道几个?

元数据被认为是数据治理的基石&#xff0c;但关于元数据相关的概念&#xff0c;很多人不是那么清楚&#xff0c;今天就和大家详解元数据相关的术语。当然&#xff0c;与元数据相关的概念非常多&#xff0c;以下仅罗列几个常见的。 01 元数据 1.名词解释 元数据最简单的定义…

【关于时间序列的ML】项目 7 :使用机器学习进行每日出生预测

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

u盘无法识别如何修复?恢复U盘,建议尝试下这些方法

我们基本都有使用过U盘&#xff0c;也都遇到过U盘损坏的问题。u盘无法识别如何修复&#xff1f;有没有什么实用的方法呢&#xff1f;来看看这篇文章&#xff0c;简单几步&#xff0c;就可以修复成功。如果在操作过程中&#xff0c;遇到数据丢失&#xff0c;也有方法帮你恢复&am…

ASP.NET开发的医疗健康咨询平台源码 养生知识咨询 寻根问药平台源码 C#源码

一、源码特点&#xff1a; 爱心医生健康知识门户网站是一个权威的医疗科普视频、语音、知识、医疗健康问答平台。 包含所有源代码和数据库&#xff0c;可以直接部署到IIS中使用。 二、菜单功能 网站页面&#xff1a; 1、首页&#xff1a;包含幻灯片。 2…

MySQL面试常问问题(SQL 优化 ) —— 赶快收藏

目录 1.慢SQL如何定位呢&#xff1f; 2.有哪些方式优化慢SQL&#xff1f; 避免不必要的列 分页优化 索引优化 JOIN优化 排序优化 UNION优化 3.怎么看执行计划&#xff08;explain&#xff09;&#xff0c;如何理解其中各个字段的含义&#xff1f; 1.慢SQL如何定位呢&a…

基于python开发的DIY宠物桌面系统(附源码)--可自定义修改

定制你的宠物桌面 最近想要做一个自己独一无二的桌面宠物&#xff0c;可以直接使用python来自己订制。属于一个小项目&#xff0c;这个教程主要包含几个步骤&#xff1a; 准备需要的动图素材 规划自己需要的功能 使用python的PyQt5订制功能 在这个教程中&#xff0c;我主要…