当前位置: 首页 > 后端技术 > Node.js

使用Electron搭建跨平台爬虫桌面程序

时间:2023-04-03 23:47:51 Node.js

使用Electron搭建一个跨平台的捕获桌面程序说到桌面应用开发技术,我们会想到.Net下的WinForm,Java下的JavaFX,Linux下的QT。这些技术对于Web应用程序员来说普遍比较陌生,因为大多数Web应用程序员的开发技能都是前端JavaScript和后端Java、PHP等语言。如果Web应用程序程序员想要开发桌面应用程序怎么办?主流桌面应用开发技术的学习曲线并不低,上手难度大。Electron的出现给Web应用程序员带来了福音。Electron简介:Electron是Github发布的跨平台桌面应用开发工具,支持基于Web技术的桌面应用开发。它本身是基于C++开发的。GUI核心来自于Chrome,而JavaScript引擎使用的是v8……简单来说,Electron平台使用Javascript来连接UI和后台逻辑。后台主进程使用NodeJs丰富的API完成复杂耗时的逻辑,而UI进程使用Chrome渲染html完成交互。我用SpringBoot开发了一套抓市长信箱的web应用。由于没有服务器部署,现在想把同样的功能移植到桌面上,做成桌面应用。我对开发平台有如下要求:可以使用我现有的一些技术栈:Web前端JavaScript、服务端Java或者NodeJs。可跨平台,Mac下可编译成DMG安装器,windows平台下可编译成exe文件,满足不足场景使用。Electron作为开发平台正好可以满足我的需求。经过一天的摸索,完成了这个桌面应用,终于打包了Mac平台下的DMG安装文件。项目代码:https://github.com/ybak/watcher下面将介绍我是如何使用Electron平台开发这个桌面应用的。回顾:在开始抓市长邮箱的web应用之前,先分析一下我之前做的web抓包应用。其架构如下:应用分为四个部分:爬虫程序:使用Java的OkHttp作为Http请求类库获取网页内容,交给Jsoup解析获取邮件内容。数据库:用Mysql实现,用于保存爬取的网页内容,提供检索查询服务。静态交互页面:一个简单的HTML页面,使用jQuery发起ajax和后端交互,并使用车把作为展示模板。通信:使用SpringBoot提供交互所需的API(搜索服务、全量抓取和更新邮件)。设计:使用Electron构建桌面应用,将通过捕获桌面应用来实现,也需要完成这四个部分。我做了如下设计:Electron主进程利用NodeJs丰富的生态系统完成网页爬取、数据存储和搜索等功能,UI进程完成页面的渲染。爬虫程序:使用NodeJs完成Request、cheerio、async。数据库:使用NodeJs下的nedb存储,作为应用嵌入式数据库,可以方便的集成到桌面应用中。UI:使用HTML和前端JavaScript类库完成,复用之前web应用中的静态页面。通信:使用Electron提供的IPC来完成主进程与UI进程的通信。实现:使用Electron搭建一个爬虫桌面应用1.爬虫程序的实现:市长信箱里有几万封邮件,JavaScript的异步特性会让人一不小心写出有几千个并发请求的程序,而且大短时间内尝试与抓包目标服务器建立连接的次数会被服务器拒绝,导致抓包进程失败。因此,抓包程序必须做到:tcp连接复用的并发频率是可控的。我使用了以下三个NodeJs组件:Requesthttpclient,利用了NodeJs底层的HttpKeepAlive特性,实现了tcp连接的复用。async控制请求的并发和异步编程的顺序。cheeriohtml解析器。代码:crawlService.js//使用request获取页面内容request('http://12345.chengdu.gov.cn/moreMail',(err,response,body)=>{if(err)throwerr;//使用cheerio解析htmlvar$=cheerio.load(body),totalSize=$('div.pagesscript').html().match(/iRecCount=\d+/g)[0].match(/\d+/g)[0];......//使用async控制请求并发,顺序抓取邮件页面内容async.eachSeries(pagesCollection,function(page,crawlNextPage){pageCrawl(page,totalPageSize,updater,crawlNextPage);})});2、数据库实现:爬取后存储内容有多种选择:文本文件搜索引擎数据库文本文件虽然便于保存,但不利于查询和搜索,所以没有使用。搜索引擎一般需要独立部署,不利于桌面应用的安装,这里不使用。独立部署的数据库和搜索引擎存在同样的问题,所以像这里不使用连接外部Mysql的方式。综合考虑,我需要一个嵌入式数据库。幸运的是,NodeJs的组件非常丰富,nedb是一个很好的解决方案。它可以同时在内存和磁盘中存储数据,也是一个文档类型的嵌入式数据库,使用mongodb语法进行数据操作。代码:dbService.js//建立数据库连接constdb=newDatastore({filename:getUserHome()+'/.electronapp/watcher/12345mails.db',autoload:true});...//使用nedb插入datadb.update({_id:mail._id},mail,{upsert:true},function(err,newDoc){});......//使用nedb进行邮件查询letmatch={$regex:eval('/'+关键字+'/')};//关键字匹配varquery=keyword?{$or:[{title:match},{content:match}]}:{};db.find(query).sort({publishDate:-1}).limit(100).exec(function(err,mails){event.sender.send('search-reply',{mails:mails});//处理查询结果});3、UI实现:桌面应用的项目目录如图:我把UI页面放在static文件夹下。前端ElectronUI开发与常用web开发方法1这样,因为Electron的UI进程是一个Chrome进程。Electron启动时,主进程会执行index.js文件,index.js会初始化应用程序窗口,设置大小,在窗口中加载UI入口页面index.html。代码:index.jsfunctioncreateMainWindow(){constwin=newelectron.BrowserWindow({width:1200,height:800});//初始应用窗口大小win.loadURL(`file://${__dirname}/static/index.html`);//在窗口中加载页面win.openDevTools();//打开chrome的devToolswin.on('closed',onClosed);returnwin;}在UI页面开发过程中,有一点需要注意,最重要的是:默认情况下,页面会加载jQuery、require等组件失败。这是因为浏览器窗口加载了一些NodeJs的方法,与jQuery类库的方法有冲突。所以我们需要做一些特别的事情,在浏览器窗口中删除这些NodeJs方法:代码:preload.js//解决jQuery等组件因require冲突不可用的问题window.nodeRequire=require;deletewindow.require;deletewindow.exports;deletewindow.module;//解决chrome调试工具devtron不可用的问题window.__devtron={require:nodeRequire,process:process}4.通信的实现:在web应用中,之间的通信页面和服务是通过ajax进行的,那么我们的桌面应用是否也可以使用ajax进行通信呢?虽然这在理论上是可行的,但是它有一个很大的缺点:我们的应用程序需要打开一个http监听端口。通常,个人操作系统是禁止软件开放http80端口的,同时开放其他端口也容易造成与其他程序的端口冲突,所以我们需要一种更优雅的通信方式。乙Lectron为UI进程和主进程之间的通信提供了一个IPCAPI。通过IPC通信,我们可以实现UI页面向NodeJs服务逻辑发起查询和抓取请求,也可以实现NodeJs服务主动通知UI页面抓取进度的更新。.使用Electron的IPC非常简单。首先,我们需要在UI中使用ipcRenderer将消息发送到自定义通道。代码:app.jsconstipcRenderer=nodeRequire('electron').ipcRenderer;//提交查询表单$('form.searchForm').submit(function(event){$('#waitModal').modal('show');event.preventDefault();ipcRenderer.send('search-keyword',$('input.keyword').val());//发起查询请求});ipcRenderer.on('search-reply',function(event,data){//监听查询结果$('#waitModal').modal('hide');if(data.mails){vartemplate=Handlebars.compile($('#template').html());$('div.list-group').html(template(data));}});然后,需要在主进程执行的NodeJs代码中使用ipcMain,监听之前自定义的channel,然后接受UI的请求。代码:crawlService.jsconstipcMain=require('electron').ipcMain;ipcMain.on('search-keyword',(event,arg)=>{....//处理查询逻辑});ipcMain.on('start-crawl',(event,arg)=>{....//处理抓包逻辑});桌面应用程序打包解决了以上四个问题后,剩下的程序就容易写了。程序调试完成后,使用electron-builder,可以为不同平台编译打包可执行文件。