You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Chrome扩展Manifest V3实现完整网页下载的问题求助

Chrome扩展Manifest V3下保存网页为MHTML的解决方案

需求

  • 实现类似Chrome浏览器Ctrl+S的完整网页保存功能
  • 或寻找能将.MHTML文件转换为包含网页文件的ZIP压缩包的库

遇到的问题

在Manifest V3的Service Worker中,URL.createObjectURL()方法未定义,尝试两种解决方案均失败:

  1. 使用blobToBase64函数替代URL.createObjectURL(),生成的.MHTML文件打开为空白,无法正常显示
  2. 将Blob发送给content script处理时,接收的Blob已损坏,调用URL.createObjectURL()时出现TypeError: Failed to execute 'createObjectURL' on 'URL': Overload resolution failed错误,使用FileSaver.js的saveAs()也因Blob损坏而失败

旧参考代码(Manifest V2可用)

// Create new tab, wait until it is loaded and save the page
chrome.tabs.create({
    url: 'http://example.com'
}, function(tab) {
    chrome.tabs.onUpdated.addListener(function func(tabId, changeInfo) {
        if (tabId == tab.id && changeInfo.status == 'complete') {
            chrome.tabs.onUpdated.removeListener(func);
            savePage(tabId);
        }
    });
});

function savePage(tabId) {
    chrome.pageCapture.saveAsMHTML({
        tabId: tabId
    }, function(blob) {
        var url = URL.createObjectURL(blob);
        // Optional: chrome.tabs.remove(tabId); // to close the tab
        chrome.downloads.download({
            url: url,
            filename: 'whatever.mhtml'
        });
    });
}

失败的尝试代码

第一次尝试(Service Worker中转Base64)

service-worker.js:

function saveAsPage(url) {
    //Added code start
    const blobToBase64 = blob => new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsDataURL(blob);
        reader.onload = () => resolve(reader.result);
        reader.onerror = error => reject(error);
    });
    //Added code end

    function savePage(tabId) {
        chrome.pageCapture.saveAsMHTML({
            tabId: tabId
        }, async function (blob) {

            //var url = URL.createObjectURL(blob); //not working in manifest v3 service worker

            var url = await blobToBase64(blob); //Added line code

            chrome.downloads.download({
                url: url,
                filename: 'whatever.mhtml'
            });

        });
    }

    chrome.tabs.create({
        url: url
    }, function (tab) {

        chrome.tabs.onUpdated.addListener(function func(tabId, changeInfo) {
            if (tabId == tab.id && changeInfo.status == 'complete') {
                chrome.tabs.onUpdated.removeListener(func);
                savePage(tabId, tab.title);
            }
        });
    });

}

第二次尝试(传递Blob到Content Script)

service-worker.js:

function saveAsPage(url) {

    async function savePage(tabId, tabTitle) {
        chrome.pageCapture.saveAsMHTML({
            tabId: tabId
        }, async function (blob) {

            var response = await chrome.tabs.sendMessage(tabId, { blob: blob }); //Added line code

            chrome.downloads.download({
                url: response.url,
                filename: 'whatever.mhtml'
            });

        });
    }

    chrome.tabs.create({
        url: url
    }, function (tab) {

        chrome.tabs.onUpdated.addListener(function func(tabId, changeInfo) {
            if (tabId == tab.id && changeInfo.status == 'complete') {
                chrome.tabs.onUpdated.removeListener(func);
                savePage(tabId, tab.title);
            }
        });
    });

}

content-script.js:

chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse) {
        if (request.blob != undefined) {
            //saveAS(request.blob); // From FileSaver.js
            var url = window.URL.createObjectURL(request.blob);
            sendResponse({ url: url });
        }

    }
);

Manifest配置

{
    "manifest_version": 3,
    "name": "Test",
    "description": "",
    "version": "0.0.1",
    "icons": {
        "16": "logo/logo-16.png",
        "48": "logo/logo-48.png",
        "128": "logo/logo-128.png"
    },
    "permissions": [
        "activeTab",
        "pageCapture",
        "downloads",
        "tabs",
        "nativeMessaging"
    ],
    "host_permissions": [
        "*://*/*"
    ],
    "background": {
        "service_worker": "service-worker.js"
    },
    "content_scripts": [
        {
            "js": [
                "FileSaver.js",
                "content-script.js"
            ],
            "matches": [
                "*://*/*"
            ]
        }
    ]
}

正确解决方案

Manifest V3的chrome.downloads.download()API支持直接传入Blob对象,无需通过URL.createObjectURL()或转Base64。修改savePage函数即可解决问题:

修改后的Service Worker代码

function saveAsPage(url) {
    function savePage(tabId, tabTitle) {
        chrome.pageCapture.saveAsMHTML({ tabId: tabId }, function(blob) {
            // 直接将Blob传入downloads.download的blob参数
            chrome.downloads.download({
                blob: blob,
                filename: `${tabTitle || '网页保存'}.mhtml`
            });
            // 可选:关闭新建的标签页
            chrome.tabs.remove(tabId);
        });
    }

    chrome.tabs.create({ url: url }, function(tab) {
        chrome.tabs.onUpdated.addListener(function func(tabId, changeInfo) {
            if (tabId === tab.id && changeInfo.status === 'complete') {
                chrome.tabs.onUpdated.removeListener(func);
                savePage(tabId, tab.title);
            }
        });
    });
}

失败原因说明

  1. Base64转换方案:chrome.downloads.download的url参数虽然支持Data URL,但MHTML格式的Data URL可能因编码规则或浏览器解析逻辑问题,导致生成的文件无法正常渲染,出现空白。
  2. Blob传递到Content Script:Chrome的消息传递系统会自动将消息序列化为JSON,Blob对象无法被正确序列化,传递后会丢失原始数据,导致接收的是损坏的对象,进而引发错误。

MHTML转ZIP压缩包的实现思路

若需要将MHTML转换为包含独立网页文件的ZIP包,可以按以下步骤实现:

  1. 使用mhtml-parser解析MHTML Blob,提取HTML、CSS、图片等资源
  2. 使用jszip创建ZIP包,将解析出的资源逐个添加到包中
  3. 通过chrome.downloads.download保存ZIP文件

示例代码(需引入mhtml-parser和jszip)

async function mhtmlToZip(mhtmlBlob) {
    const mhtmlText = await mhtmlBlob.text();
    const parsed = await mhtmlParser.parse(mhtmlText);
    const zip = new JSZip();

    // 添加主HTML文件
    zip.file('index.html', parsed.html);

    // 添加所有资源文件(CSS、图片等)
    parsed.resources.forEach(res => {
        zip.file(res.filename, res.content);
    });

    // 生成ZIP Blob并触发下载
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    chrome.downloads.download({
        blob: zipBlob,
        filename: '网页打包.zip'
    });
}

内容的提问来源于stack exchange,提问作者MWagihM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 15:48:31