当前位置: 首页 > news >正文

别再乱装图片插件了!我手写了一个,能扒光整个网页(含背景/iframe/Shadow DOM)

开场白

我真的受够了,每次想从网页批量保存图片,要么右键被禁用,要么装了五六个插件还漏掉一半的 CSS 背景图,要么好不容易抓到图了,却发现插件在后台偷偷上报我的浏览记录。

于是我自己写了一个 ——Image Harvest。它能把网页里所有图片(包括<img>、CSS 背景、iframe 内嵌、甚至 Shadow DOM 里的)全部扒出来,一键打包 ZIP,而且本地处理,零追踪。

官网功能详情:image-harvest.kyriewen.cn

Chrome 商店安装:https://chromewebstore.google.com/detail/iecgnjidmogebokcfnejncgnelcepffo

这篇文章不讲产品吹水,只说技术实现:MV3 踩坑、深度图片提取、客户端感知哈希去重、Side Panel + Popup 双形态共存。干货 + 代码 + 真实踩坑记录,希望对写 Chrome 插件的朋友有帮助。


一、为什么我要自己写一个图片下载插件?

现有的同类插件,我试过 10+ 个,普遍三个问题:

  1. 抓不全:只能抓<img>,CSSbackground-imageiframe、Shadow DOM 里的图基本放弃。
  2. 有隐私风险:manifest 里申请<all_urls>+webRequest,还往未知服务器发数据。
  3. 体验拉胯:批量下载要么一张张点,要么 ZIP 包里一半是占位图。

所以我决定自己造轮子。核心目标:

  • 不漏图(递归提取所有图片源)
  • 不监守自盗(纯本地,零数据收集)
  • 好用(侧边栏/弹窗双模式、暗色主题、3 档密度)

二、Manifest V3 的几个坑(附解法)

2.1 Service Worker 冷启动

V3 用 service worker 替代 V2 的常驻 background page。它会在几秒无活动后休眠,导致下次调用时变量全丢。

解决方案:用chrome.storage.session缓存关键状态。

// 抓取完成后存入 sessionawaitchrome.storage.session.set({lastExtract:{images,timestamp:Date.now()}});// 下次打开面板时恢复constcached=awaitchrome.storage.session.get('lastExtract');if(cached&&Date.now()-cached.timestamp<60000){returncached.images;}

2.2 远程代码被禁止

V3 完全禁止执行从外部下载的脚本。对我没影响:Image Harvest 所有代码本地打包,不依赖任何远程配置。

2.3webRequestdeclarativeNetRequest替代

如果你需要修改网络请求(如给图片请求加 header),现在只能用声明式规则,灵活性降低。不过图片下载器不需要这玩意儿。


三、深度图片提取:从<img>到 Shadow DOM

3.1 基础提取:<img><picture>

functionextractSimpleImages(){consturls=[];document.querySelectorAll('img').forEach(img=>{if(img.src)urls.push(img.src);});document.querySelectorAll('picture source').forEach(source=>{if(source.srcset){consthighest=source.srcset.split(',').pop().trim().split(' ')[0];urls.push(highest);}});returnurls;}

3.2 提取 CSSbackground-image

很多网站的 Banner、图标都用背景图实现,必须挖出来。

functionextractBgImages(root=document){constelements=root.querySelectorAll('*');constbgUrls=[];for(leti=0;i<Math.min(elements.length,2000);i++){constbg=getComputedStyle(elements[i]).backgroundImage;if(bg&&bg!=='none'){constmatch=bg.match(/url\(["']?([^"')]+)["']?\)/);if(match)bgUrls.push(match[1]);}}returnbgUrls;}

3.3 递归 Shadow DOM

现代前端框架(React/Vue)常把图片封装在 Shadow DOM 里,必须递归遍历。

functionextractFromShadowDOM(root=document){letresults=[];// 普通图片results.push(...extractSimpleImages(root));results.push(...extractBgImages(root));// 递归 Shadow DOMconsthosts=root.querySelectorAll('*');hosts.forEach(host=>{if(host.shadowRoot){results.push(...extractFromShadowDOM(host.shadowRoot));}});returnresults;}

3.4 iframe 处理

同源 iframe 可以用chrome.scripting.executeScript注入提取函数。需要webNavigation权限获取所有 frame。

constframes=awaitchrome.webNavigation.getAllFrames({tabId});for(constframeofframes){if(frame.parentFrameId!==-1)continue;// 只处理顶层 iframeconstinjection=awaitchrome.scripting.executeScript({target:{tabId,frameIds:[frame.frameId]},func:extractFromShadowDOM,});// 合并结果...}

四、客户端感知哈希(pHash)实现相似图去重

很多用户反馈:“下载 100 张图,里面有 30 张是重复的缩略图”。所以我在 Pro 版中加了相似图检测。

4.1 算法选择:dHash

  • 速度快(纯前端)
  • 对缩放、轻微裁剪不敏感
  • 汉明距离 ≤ 5 判定为相似

4.2 核心代码

asyncfunctioncomputeDHash(blob){constimg=awaitcreateImageBitmap(blob);constcanvas=newOffscreenCanvas(9,8);constctx=canvas.getContext('2d');ctx.drawImage(img,0,0,9,8);constdata=ctx.getImageData(0,0,9,8).data;// 转灰度constgray=[];for(leti=0;i<data.length;i+=4){gray.push(0.299*data[i]+0.587*data[i+1]+0.114*data[i+2]);}// 差分哈希lethash=0n;for(letrow=0;row<8;row++){for(letcol=0;col<7;col++){constleft=gray[row*9+col];constright=gray[row*9+col+1];if(right>left)hash|=(1n<<BigInt(row*7+col));}}returnhash;}

4.3 Worker 中运行,不阻塞 UI

constworker=newWorker('phash-worker.js');worker.postMessage({blob});worker.onmessage=(e)=>{console.log(`哈希:${e.data.hash}`);};

五、Side Panel + Popup 双模式共存

Chrome 115+ 支持 Side Panel,但老用户习惯 Popup。我两个都要。

实现要点

  • manifest 中配置side_panel.default_path = "sidepanel.html"
  • action.default_popup留空,动态控制
chrome.action.onClicked.addListener(async(tab)=>{constsettings=awaitgetAppSettings();if(settings.useSidePanel){awaitchrome.sidePanel.open({tabId:tab.id});}else{awaitchrome.action.setPopup({tabId:tab.id,popup:'popup.html'});chrome.action.openPopup();}});

同一套 UI 代码,通过window.location.pathname判断当前模式,微调布局(弹窗固定 620×600,侧边栏自适应)。

六、上架 Chrome Web Store 的 4 个雷区

  1. 图标尺寸不全:必须 16/32/48/128 px,缺一个直接驳回。
  2. 描述太短:简短描述 ≤132 字符,要包含核心关键词。
  3. 隐私政策缺失:即使不收集数据,也要写一份说明“不收集什么”。
  4. 权限过度:不需要<all_urls>就别写,否则审核会问。

我第一次提交被拒就是因为隐私政策链接 404。补上后 2 天过审。


七、成品 & 求个赞

如果你也受够了那些抓不全、偷数据的图片插件,可以试试Image Harvest

官网功能详情:image-harvest.kyriewen.cn

Chrome 商店安装:https://chromewebstore.google.com/detail/iecgnjidmogebokcfnejncgnelcepffo

如果觉得这个工具或这篇文章对你有帮助

  • 在 Chrome 商店留个好评(对独立开发者是续命药水 💊)
  • 给这篇文章点个赞/收藏,让更多人看到技术干货

我会继续维护和更新,任何 bug 或建议欢迎评论区告诉我 🙏

http://www.jsqmd.com/news/721834/

相关文章:

  • 告别手动重复:用Python+HFSS脚本实现天线仿真结果自动导出与报告生成
  • 拥有多个二次元老婆:如何在手机上设置Live2D模型为动态高清壁纸
  • C#-字符串与16进制字节数组转换
  • C# 13指针与fixed语句安全红线:5类高危模式、3层编译器防护、1套企业级审计清单
  • VirtualBrowser 2.1.15:一站式浏览器指纹管理实战指南
  • RS_ASIO:终极低延迟音频解决方案,为Rocksmith 2014带来专业级音频体验
  • 暴雨大讲堂|AI算力异构与液冷重塑算力产业新格局
  • 告别Anchor Boxes:手把手带你用PyTorch复现FCOS目标检测模型(附完整代码)
  • 香港启世集团宣布即将发布人工光合作用突破性技术
  • show
  • Ledger 硬件钱包支持币种大全(中国用户参考版)
  • MagiskHide Props Config终极指南:Android设备指纹伪装与安全检测绕过完整方案
  • 告别理论推导!用SH33F2811的SVPWM模块驱动电机,实测波形与代码分享
  • MacType终极指南:3步让Windows字体焕然一新,告别模糊显示!
  • 微软向美国约7%员工提供自愿退休买断计划
  • Winhance中文版终极指南:完全掌握Windows系统优化与管理
  • JSM27712 650V 高低侧栅极驱动芯片
  • DLSS Swapper终极指南:专业级游戏性能优化解决方案
  • 别再为YOLOv8-Pose数据集发愁了!手把手教你用CVAT标注COCO格式关键点(附可视化代码)
  • 你还在用Worker进程模拟并发?PHP 8.9 原生纤维协程已支持调度器热插拔(仅限RC3+内测通道开放)
  • 从调试助手到真实设备:手把手带你完成汇川AM600与第三方仪表的Modbus RTU通信实战
  • 如何用DyberPet桌面宠物框架打造你的专属数字伙伴?3步开启创意之旅
  • 终极色彩管理解决方案:OpenColorIO-Config-ACES快速入门完整指南
  • 脑机接口初创公司Neurable寻求向消费级可穿戴设备授权“读心“技术
  • 【工业级偏见审计手册】:基于R的因果公平性检验、群体差异分解与置信区间校准(附FDA/EC合规模板)
  • 426-opencua tmux
  • 黄金矿工H5游戏源码 | Vue+uni-app挖矿小游戏 | 内置矿机玩法 | 对接广告联盟 提现变现完整项目
  • 关于在网页中使用CSS样式
  • 告别传统FAST:用Superpoint自监督网络,在COCO数据集上实战像素级特征点提取
  • 电赛备赛笔记:用GD32F470的DMA驱动PWM,我踩过的那些坑(梁山派实战)