有个做广告设计的朋友,自己开个小工作室,平时接些小广告设计维持生活。但总会遇到一些甲方拿着某个站点的图片,让他用这个图片给他做广告牌或者海报。就这样,作为老切图仔就一直在帮他「从网站里拿出图片」这种脏累活。想着是不是可以干脆送他个工具,这样他就可以自己玩了
(不是很推荐这种操作,但是毕竟要恰饭要苟活,而且这种外包单,甲方是这样的)

想了一下,切图仔唯一高效 GUI 的选型只有 electron 了,没得选。但是这次有点特别,因为 electron 的特殊性,我有了些想法

electron = node + chromium,都有个完整浏览器了 484 不需要无头就可以加载 remote 然后直接获取资源?

这里用的模板是之前实验服务一体化的模板 electron-react-koa-template,然后删除了server

删了server……

TL;DR

  • webview
  • 获取资源
  • 提供下载

webview

这里还有一个方案,BrowserWindow,然后{show: false}让这个窗口不显示,用这个窗体当无头

不过在之前开发 hexo 编辑器的时候就有用过,当时用来做内嵌视图打开博客预览地址,还有切换线上地址用的,这里可以用用

1
2
3
const webview = document.createElement('webview')
// 当页面加载完成之后会触发这个事件,可以继续做接下来的事情
webview.addEventListener('dom-ready', () => {})

于是封装一下变成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
componentDidMount() {
const webview = document.createElement('webview')
// 保险起见
webview.useragent
= 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) '
+ 'AppleWebKit/537.36 (KHTML, like Gecko) '
+ 'Chrome/81.0.4044.129 Safari/537.36 Edg/81.0.416.68'
webview.addEventListener('dom-ready', () => {})

document.body.appendChild(webview)
this.webview = webview
}

search(url) {
this.webview.src = url
}

一套操作之后,你会发现什么都看不到……这个时候你会先怀疑你上面写的这个 createElement,是不是 electron 的 dom 不可以直接创建(匪夷所思),于是你将 <webview /> 直接放到 render 里,发现依然什么都没有

坑:安全性

这里使用的是electron@6,查了一番之后,发现electron@5加了一个安全性设定:需要允许webviewTag

于是在主窗体需要一行配置

1
2
3
4
5
6
7
mainWindow = new BrowserWindow({
webPreferences: {
// 这里
webviewTag: true,
nodeIntegration: true,
},
})

然后你就能看到页面被加载

接着,确认能加载之后就可以大方的把 webview 隐藏起来了

解析资源

这里计划是直接尝试获取 webview 的资源,但是没找到方法,只能退而求其次:爬tmd。那么这就需要一个拥有 80 年爬虫经验的工具:cheerio

与现在普遍的互联网上某些技术社区所分享的「一小时精通 nodejs 爬虫」、「教你怎么用 nodejs 爬妹子图」等文章不同 —— 他们对 SPA 一点办法都没有!

我这不一样,我有浏览器,在 dom-ready 的时候也意味着真实结构已经加载到了(亲测!专门拿 SPA 试的!

执行 JavaScript

webview 有个方法 <webview>.executeJavaScript(code[, userGesture]),所以可以通过执行一段 js 把 html 拿出来,有股叉 ass ass 的味道

1
2
3
4
5
6
webview
.executeJavaScript(
`function gethtml () { return new Promise(resolve => resolve(document.documentElement.innerHTML)) }; gethtml();`,
)
.then((html) => {
});

这个时候html即一个完整的html,把执行放到dom-ready,接下来就交给 ipc 表演了

1
2
3
4
5
6
7
8
9
webview.addEventListener('dom-ready', () => {
webview
.executeJavaScript(
`function gethtml () { return new Promise(resolve => resolve(document.documentElement.innerHTML)) }; gethtml();`,
)
.then(html => {
ipcRenderer.send('ganhuo', html)
});
})

node/cheerio

主要是 cheerio 是一个 node 方的应用,依然是在 main 层操作更安心一些

准备一个 ipc 监听,刚刚那个是ganhuo

1
2
3
4
5
6
7
8
ipcMain.on('ganhuo', (e, arg) => {
const $ = cheerio.load(arg)
// 各种教程都能看到的
// 这里没多余操作,是个 img 就拿走
// 接着 reply 回 renderer
const imgs = $('body').find('img').map((idx, ele) => $(ele).attr('src')).get()
e.reply('chuhuo', imgs)
})

renderer 边准备一个接收,这波结束

1
2
3
4
5
6
7
componentDidMount() {
ipcRenderer.on('chuhuo', (e, result) => {
this.setState({
imgs: result,
});
});
}

展示/下载

UI库直接用 antd,依然是放心产品

这里草草带过:用 forminputbutton 处理一个简单地址栏,用 card 展示图片,至于要不要funcybox之类的随缘

继续依赖 node 层就可以做到下载文件保存文件的操作,可以拿到图片信息(exif),获取分辨率以及过滤分辨率啥的

总结

到这里能发现个问题:爬取、加载,如果再算上图片信息解析等操作的话,图片妥妥的获取了三次。虽说因为图片资源都相同,可能有两次获取的是disk cache

这里不开源了,一股 POC 味

参考链接