nodejs 保存一个页面中的css文件

nodejs yekong

使用electron开发一款单页模板下载助手中,我们需要一个功能将页面中的css文件全部下载下来。保存到本地。

基础版

我们创建一个方法来解析 HTML 内容,找到其中的 CSS 链接,并将它们保存到指定的路径下。

  1. 提取 HTML 中的 CSS 链接:这里可以使用一个库,例如 cheerio,来解析 HTML 并提取 CSS 链接。
  2. 下载 CSS 文件:然后使用 axios 或其他 HTTP 请求库来下载这些链接指向的 CSS 文件。
  3. 保存 CSS 文件:最后,你可以将下载的 CSS 文件保存到我们所指定的目录下。

下面是具体的代码示例:

const cheerio = require('cheerio');
const axios = require('axios');
const fs = require('fs');
const path = require('path');

async function saveCssFile(htmlContent, savePath) {
    // 使用 cheerio 加载 HTML
    const $ = cheerio.load(htmlContent);

    // 找到所有的 CSS 链接
    const cssLinks = [];
    $('link[rel="stylesheet"]').each((index, element) => {
        const link = $(element).attr('href');
        if (link) cssLinks.push(link);
    });

    // 遍历 CSS 链接并保存
    for (const link of cssLinks) {
        try {
            // 请求 CSS 文件
            const response = await axios.get(link, {responseType: 'text'});

            // 创建保存的路径
            const filename = path.basename(link);
            const filePath = path.join(savePath, 'css', filename);

            // 将内容写入文件
            fs.writeFileSync(filePath, response.data, 'utf8');
            sendMessage(`CSS 文件 ${filename} 保存成功`);
        } catch (error) {
            sendMessage(`下载 CSS 文件 ``{link} 失败: ``{error}`);
        }
    }
}

此方法的工作方式如下:

  • 使用 cheerio 解析 HTML,找到所有的 CSS 链接。
  • 遍历这些链接,并用 axios 发送 GET 请求来下载 CSS 文件。
  • 使用 Node.js 的文件系统模块 fs 将下载的 CSS 文件保存到指定的目录下。

这个代码如果 CSS 链接都是绝对路径,如果有相对路径,我们需要使用一个库来处理 URL,或者手动构造绝对 URL。

处理相对路径文件

上面我们的代码css绝对地址的话没有问题,但是在相对地址的话,就会报错,无法下载,所以我们还需要对现有的代码进一步优化。

处理相对路径文件

当我们从HTML页面中提取CSS链接时,链接可能是绝对路径或相对路径。相对路径需要基于HTML页面的URL来解析。我们可以使用url库来协助处理这个问题。(如果对url库不了解的话,可以查看url组件库是做什么用的以及使用实例,简单的了解一下。)以下是一个更新后的代码示例,其中添加了对相对路径的处理:

const url = require('url');

async function saveCssFile(htmlContent, savePath, baseUrl) { // 添加 baseUrl 参数
    // 使用 cheerio 加载 HTML
    const $ = cheerio.load(htmlContent);

    // 找到所有的 CSS 链接
    const cssLinks = [];
    $('link[rel="stylesheet"]').each((index, element) => {
        const link = $(element).attr('href');
        if (link) {
            // 如果是相对路径,使用 url.resolve 来解析
            const fullLink = url.resolve(baseUrl, link);
            cssLinks.push(fullLink);
        }
    });

    // 遍历 CSS 链接并保存
    for (const link of cssLinks) {
        try {
            // 请求 CSS 文件
            const response = await axios.get(link, {responseType: 'text'});

            // 创建保存的路径
            const filename = path.basename(link);
            const filePath = path.join(savePath, 'css', filename);

            // 将内容写入文件
            fs.writeFileSync(filePath, response.data, 'utf8');
            sendMessage(`CSS 文件 ${filename} 保存成功`);
        } catch (error) {
            sendMessage(`下载 CSS 文件 ``{link} 失败: ``{error}`);
        }
    }
}

在这个版本中,我添加了一个baseUrl参数,你可以将HTML页面的URL传递给这个函数。然后,使用url.resolve来确保所有的相对路径都被正确解析为绝对路径。

调用这个函数时,确保传递正确的基础URL,例如:

saveCssFile(htmlContent, savePath, 'http://example.com/path/');

到这里我们就正常下载css文件了。

正常下载css文件

处理下载后文件命名

处理下载后文件命名

当我们下载CSS文件时,可能会遇到文件名后带有查询参数(如?v=22)的情况。查询参数通常用于缓存控制,但在保存文件时,我们要去除这些参数。这里可以通过解析URL并忽略查询部分来实现这一目的。

以下是更新后的代码片段:

const url = require('url');
const path = require('path');

// ...

// 遍历 CSS 链接并保存
for (const link of cssLinks) {
    try {
        // 请求 CSS 文件
        const response = await axios.get(link, {responseType: 'text'});

        // 解析链接并提取文件名
        const parsedUrl = url.parse(link);
        const filename = path.basename(parsedUrl.pathname); // 使用pathname而不是完整链接

        const filePath = path.join(savePath, 'css', filename);

        // 将内容写入文件
        fs.writeFileSync(filePath, response.data, 'utf8');
        sendMessage(`CSS 文件 ${filename} 保存成功`);
    } catch (error) {
        sendMessage(`下载 CSS 文件 ``{link} 失败: ``{error}`);
    }
}

在这个修改中,使用了url.parse方法来解析链接,然后使用path.basename函数从解析后的pathname属性中提取文件名。这样就确保了保存的文件名不包括查询参数或其它额外的字符。

处理后的效果截图

处理代码

当我们下载完css文件后,我们需要对html代码里面的css路径进行更新,并返回以便于保存后的html代码引用的文件是我们本地的css文件。

async function saveCssFile(htmlContent, savePath, baseUrl) {
    // 使用 cheerio 加载 HTML
    const $ = cheerio.load(htmlContent);

    // 创建一个存储新链接的映射
    const linkMapping = {};

    // 找到所有的 CSS 链接
    $('link[rel="stylesheet"]').each((index, element) => {
        const link = $(element).attr('href');
        if (link) {
            // 如果是相对路径,使用 url.resolve 来解析
            const fullLink = url.resolve(baseUrl, link);
            linkMapping[fullLink] = link; // 将新链接与原链接关联
        }
    });

    // 遍历 CSS 链接并保存
    for (const [fullLink, originalLink] of Object.entries(linkMapping)) {
        try {
            // 请求 CSS 文件
            const response = await axios.get(fullLink, { responseType: 'text' });

            // 解析链接并提取文件名
            const parsedUrl = url.parse(fullLink);
            const filename = path.basename(parsedUrl.pathname);

            // 创建文件路径
            const filePath = path.join(savePath, 'css', filename);

            // 将内容写入文件
            fs.writeFileSync(filePath, response.data, 'utf8');
            sendMessage(`CSS 文件 ${filename} 保存成功`);

            // 更新HTML内容中的链接
            const localPath = path.join('css', filename).replace(/\\/g, '/');
            $('link[href="' + originalLink + '"]').attr('href', localPath);
        } catch (error) {
            sendMessage(`下载 CSS 文件 ``{fullLink} 失败: ``{error}`);
        }
    }

    // 返回更新后的HTML内容
    return $.html();
}

这里使用了linkMapping对象来存储原始链接与新链接的映射,然后遍历这些链接以下载和保存CSS文件。我还更新了HTML内容中的CSS链接,使其指向本地路径,然后返回了更新后的HTML内容。

请注意,这个例子假设所有的CSS链接都是以/开头的相对路径。如果HTML内容中的链接使用了其他格式,你可能需要做一些额外的处理来确保链接被正确更新。我们在后续中遇到类似的页面再做处理。

不同目录相同文件名问题

页面中我们可能会遇到相同的css命名,但是目录是不同的页面,我们在下载的时候需要考虑这种情况,后续会对这种进行处理。

不同页面不同css目录相同文件名的

在下载页面的时候,我们可能会下载同一个域名不同的页面,css可能会重名导致覆盖旧文件,导致之前下载的页面的样式出错。

喜欢