有个做广告设计的朋友,自己开个小工作室,平时接些小广告设计维持生活。但总会遇到一些甲方拿着某个站点的图片,让他用这个图片给他做广告牌或者海报。就这样,作为老切图仔就一直在帮他「从网站里拿出图片」这种脏累活。想着是不是可以干脆送他个工具,这样他就可以自己玩了
(不是很推荐这种操作,但是毕竟要恰饭要苟活,而且这种外包单,甲方是这样的)
想了一下,切图仔唯一高效 GUI 的选型只有 electron
了,没得选。但是这次有点特别,因为 electron
的特殊性,我有了些想法
electron = node + chromium,都有个完整浏览器了 484 不需要无头就可以加载 remote 然后直接获取资源?
这里用的模板是之前实验服务一体化的模板 electron-react-koa-template,然后删除了server
…
删了server
……
TL;DR
- webview
- 获取资源
- 提供下载
webview
这里还有一个方案,BrowserWindow
,然后{show: false}
让这个窗口不显示,用这个窗体当无头
不过在之前开发 hexo 编辑器的时候就有用过,当时用来做内嵌视图打开博客预览地址,还有切换线上地址用的,这里可以用用
const webview = document.createElement('webview')
// 当页面加载完成之后会触发这个事件,可以继续做接下来的事情
webview.addEventListener('dom-ready', () => {})
于是封装一下变成
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
于是在主窗体需要一行配置
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 的味道
webview
.executeJavaScript(
`function gethtml () { return new Promise(resolve => resolve(document.documentElement.innerHTML)) }; gethtml();`,
)
.then((html) => {
});
这个时候html
即一个完整的html
,把执行放到dom-ready
,接下来就交给 ipc 表演了
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
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
边准备一个接收,这波结束
componentDidMount() {
ipcRenderer.on('chuhuo', (e, result) => {
this.setState({
imgs: result,
});
});
}
展示/下载
UI库直接用 antd
,依然是放心产品
这里草草带过:用 form
、input
、button
处理一个简单地址栏,用 card
展示图片,至于要不要funcybox
之类的随缘
继续依赖 node 层就可以做到下载文件保存文件的操作,可以拿到图片信息(exif),获取分辨率以及过滤分辨率啥的
总结
到这里能发现个问题:爬取、加载,如果再算上图片信息解析等操作的话,图片妥妥的获取了三次。虽说因为图片资源都相同,可能有两次获取的是disk cache
这里不开源了,一股 POC 味