使用Angular与TypeScript构建Electron应用(四)
这一节我们只做两件事,第一是建立相应的爬虫系统,从网页链接上提取合适的信息,第二则是将这些信息储存在数据库中,render需要展示时再查询予以显示。开始构建代码前我们先思考一下这样做的好处是什么。 介绍在news-feed应用中,我们把爬虫逻辑放在客户应用里而非服务端,这是正确的,考虑到用户增加的情况下我们无法负担所有的爬虫任务,如果我们将这些任务进行合理的分配是最优的,利用一些客户端资源。在生产环境里还可以考虑用户每次爬取完毕后发送处理好的字符串发送回服务端进行存储,甚至可以根据服务器返回不同得资源来考虑返回给用户不同的任务。虽然在news-feed中我们不会做这些事,但我们不妨考虑这样的系统是如何工作的:
这样的系统很有意思,积累众多格式化数据资源后甚至可以转为开发的新闻API供大家使用,不过它很复杂(你可以自己尝试一下),目前我们希望应用的所有数据都能够自行完成,为此我们至少需要一个数据库存储格式化数据,一段可配置的代码爬取与分析数据。在做所有事情之前,我准备加入一个新的语法糖,以适应爬虫任务。 配置Asyncasync是ES7的新语法,简单的说,async是一个基于Generator的语法糖。如果你对Generator还不了解,建议先学习一些ES6基础知识。爬虫任务可能涉及到很多的异步任务,但大多数时候我们更希望它们可以同步执行(并发过大很容易被网站屏蔽IP地址),async函数可以帮助我们轻松的用同步函数的方式写异步逻辑,而且它足够简单,学习它也是理所应当的,这是javascript的趋势之一。 首先我们需要安装一些必要的npm包: npm i --save transform-async-to-generator syntax-async-functions transform-regenerator npm i --save babel-core babel-polyfill babel-preset-es2016 这里我希望代码不要经过频繁的转码,应用可以不考虑兼容性,所以我加入一些垫片使语法糖能够正常工作即可。 { "presets": ["es2016"],"plugins": ["transform-async-to-generator","syntax-async-functions","transform-regenerator"] } 并在根文件夹建立一个 require('babel-core/register'); require("babel-polyfill"); require("./index"); 从现在开始我们每次只需运行 安装数据库作为一个桌面应用,数据存储是必不可少的一环,但这里并没有使用已携带的浏览器存储:
除此之外我们还可以选用一些流行的云储存,远程数据库等等,但我希望应用能够在脱机时正常工作,为此我们需要一个安装简单,在本地即时编译的轻量级数据库。 这里我选用的流行的nedb,它的社区环境足够好,有很多的使用者(保证库能够及时更新并解决各类问题),而且与electron能够很好的结合。 npm i --save nedb 在根目录的 const Datastore = require('nedb') global.Storage = new Datastore({filename: `${__dirname}/.database/news-feed.db`,autoload: true })
构建爬虫代码在动手之前我们先尝试分析爬虫代码的逻辑:这里至少需要一个实际工作的爬虫函数,它从http请求得到数据并且开始分析html,最后存储这些数据。不同的网站结构不同意味着需要不同的解析函数,但其中至少可以将基础的http服务抽离出来(它们总是相同的),未来我们可以从服务端获取一些解析代码填充在这里。 手动发起http请求与处理字符串工作量非常大,我们可以借助一下库来完成这些工作: * https://github.com/request/request npm i --save request * https://github.com/cheeriojs/cheerio npm i --save cheerio 1.新建http请求函数 在 const req = require('request') module.exports = class Base { constructor (){ } static makeOptions (url){ return { url: url,port: 8080,method: 'GET',headers: { 'User-Agent': 'nodejs','Content-Type': 'application/json' } } } static request (url){ return new Promise((resolve,reject) =>{ req(Base.makeOptions(url),(err,response,body) =>{ if (err) return reject(err) resolve(body) }) }) } } Base类有两个静态方法, 也许你开始注意到,这两个静态函数完全不依赖 2.新建爬虫文件 // /browser/task/index.js module.exports = { ifeng: require('./ifeng') } 在task文件夹下再创建一个 const cheerio = require('cheerio') const Base = require('./base') module.exports = new class Self extends Base { constructor (){ super() this.url = 'http://news.ifeng.com/xijinping/' } start (){ global.Storage.count({},c) =>{ if (c || c > 0) return ; this.request() .then(res =>{ console.log('全部储存完毕!'); global.Storage.loadDatabase() }) .catch(err =>{ console.log(err); }) }) } async request (){ try{ const body = await Self.request(this.url) let links = await this.parseLink(body) for (let index = 1; index< links.length; index++){ const content = await Self.request(links[index -1]) const article = await this.parseContent(content) await this.saveContent(Object.assign({id: index},article)) console.log(`第${index}篇文章:${article&&article.title}储存完毕`); } } catch (err){ return Promise.reject(err) } } parseLink (html){ const $ = cheerio.load(html) return $('.con_lis > a') .map((i,el) => $(el) .attr('href')) } parseContent (html){ if (!html) return; const $ = cheerio.load(html) const title = $('title').text() const content = $('.yc_con_txt').html() return {title: title,content: content} } saveContent (article){ if (!article|| !article.title) return ; return global.Storage.insert(article) } }()
这里的
OK,这一节的所有目标都已完成,下一节我们开始讨论如何在Angular中构建一个合理的展示模块并与数据库通信。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |