上一篇文章《手把手教你如何用Crawlab构建技术文章聚合平台(一)》介绍了如何使用搭建Crawlab的运行环境,而且将Puppeteer与Crawlab集成,对掘金、SegmentFault、CSDN进行技术文章的抓取,最后能够查看抓取结果。本篇文章将继续讲解如何利用Flask+Vue编写一个精简的聚合平台,将抓取好的文章内容展现出来。javascript
首先,咱们须要对爬虫部分作点小小的补充。上篇文章中咱们只编写了抓取文章URL的爬虫,咱们还须要抓取文章内容,所以还须要将这部分爬虫编写了。上次爬虫的结果collection所有更改成results
,文章的内容将以content
字段保存在数据库中。html
经分析知道每一个技术网站的文章页都有一个固定标签,将该标签下的HTML所有抓取下来就OK了。具体代码分析就不展开了,这里贴出具体代码。前端
const puppeteer = require('puppeteer'); const MongoClient = require('mongodb').MongoClient; (async () => { // browser const browser = await (puppeteer.launch({ headless: true })); // page const page = await browser.newPage(); // open database connection const client = await MongoClient.connect('mongodb://192.168.99.100:27017'); let db = await client.db('crawlab_test'); const colName = process.env.CRAWLAB_COLLECTION || 'results'; const col = db.collection(colName); const col_src = db.collection('results'); const results = await col_src.find({content: {$exists: false}}).toArray(); for (let i = 0; i < results.length; i++) { let item = results[i]; // define article anchor let anchor; if (item.source === 'juejin') { anchor = '.article-content'; } else if (item.source === 'segmentfault') { anchor = '.article'; } else if (item.source === 'csdn') { anchor = '#content_views'; } else { continue; } console.log(`anchor: ${anchor}`); // navigate to the article try { await page.goto(item.url, {waitUntil: 'domcontentloaded'}); await page.waitFor(2000); } catch (e) { console.error(e); continue; } // scrape article content item.content = await page.$eval(anchor, el => el.innerHTML); // save to database await col.save(item); console.log(`saved item: ${JSON.stringify(item)}`) } // close mongodb client.close(); // close browser browser.close(); })();
而后将该爬虫按照前一篇文章的步骤部署运行爬虫,就能够采集到详细的文章内容了。vue
文章内容爬虫的代码已经更新到Github了。java
接下来,咱们能够开始对这些文章作文章了。node
目前的技术发展来看,先后端分离已是主流:一来前端技术愈来愈复杂,要求模块化、工程化;二来先后端分离可让先后端团队分工协做,更加高效地开发应用。因为本文的聚合平台是一个轻量级应用,后端接口编写咱们用Python的轻量级Web应用框架Flask,前端咱们用近年来大红大紫的上手容易的Vue。python
Flask被称为Micro Framework,可见其轻量级,几行代码即可以编写一个Web应用。它靠Extensions插件来扩展其特定功能,例如登陆验证、RESTful、数据模型等等。这个小节中咱们将搭建一个REST风格的后台API应用。ios
首先安装相关的依赖。git
pip install flask flask_restful flask_cors pymongo
安装完成后咱们能够新建一个app.py
文件,输入以下代码github
from flask import Flask from flask_cors import CORS from flask_restful import Api # 生成Flask App实例 app = Flask(__name__) # 生成API实例 api = Api(app) # 支持CORS跨域 CORS(app, supports_credentials=True) if __name__ == '__main__': app.run()
命令行中输入python app.py
就能够运行这个基础的Flask应用了。
接下来,咱们须要编写获取文章的接口。首先咱们简单分析一下需求。
这个Flask应用要实现的功能为:
所以,咱们须要实现上述两个API。下面开始编写接口。
在app.py
中添加以下代码,做为列表接口。
class ListApi(Resource): def get(self): # 查询 items = col.find({'content': {'$exists': True}}).sort('_id', DESCENDING).limit(40) data = [] for item in items: # 将pymongo object转化为python object _item = json.loads(json_util.dumps(item)) data.append({ '_id': _item['_id']['$oid'], 'title': _item['title'], 'source': _item['source'], 'ts': item['_id'].generation_time.strftime('%Y-%m-%d %H:%M:%S') }) return data
一样的,在app.py
中输入以下代码。
class DetailApi(Resource): def get(self, id): item = col.find_one({'_id': ObjectId(id)}) # 将pymongo object转化为python object _item = json.loads(json_util.dumps(item)) return { '_id': _item['_id']['$oid'], 'title': _item['title'], 'source': _item['source'], 'ts': item['_id'].generation_time.strftime('%Y-%m-%d %H:%M:%S'), 'content': _item['content'] }
编写完接口,咱们须要将它们映射到对应到URL中。
api.add_resource(ListApi, '/results') api.add_resource(DetailApi, '/results/<string:id>')
如下是完整的Flask应用代码,很简单,实现了文章列表和文章详情两个功能。接下来,咱们将开始开发前端的部分。
import json from bson import json_util, ObjectId from flask import Flask, jsonify from flask_cors import CORS from flask_restful import Api, Resource from pymongo import MongoClient, DESCENDING # 生成Flask App实例 app = Flask(__name__) # 生成MongoDB实例 mongo = MongoClient(host='192.168.99.100') db = mongo['crawlab_test'] col = db['results'] # 生成API实例 api = Api(app) # 支持CORS跨域 CORS(app, supports_credentials=True) class ListApi(Resource): def get(self): # 查询 items = col.find({}).sort('_id', DESCENDING).limit(20) data = [] for item in items: # 将pymongo object转化为python object _item = json.loads(json_util.dumps(item)) data.append({ '_id': _item['_id']['$oid'], 'title': _item['title'], 'source': _item['source'], 'ts': item['_id'].generation_time.strftime('%Y-%m-%d %H:%M:%S') }) return data class DetailApi(Resource): def get(self, id): item = col.find_one({'_id': ObjectId(id)}) # 将pymongo object转化为python object _item = json.loads(json_util.dumps(item)) return { '_id': _item['_id']['$oid'], 'title': _item['title'], 'source': _item['source'], 'ts': item['_id'].generation_time.strftime('%Y-%m-%d %H:%M:%S'), 'content': _item['content'] } api.add_resource(ListApi, '/results') api.add_resource(DetailApi, '/results/<string:id>') if __name__ == '__main__': app.run()
运行python app.py
,将后台接口服务器跑起来。
Vue近年来是热得发烫,在Github上已经超越React,成为三大开源框架(React,Vue,Angular)中star数最多的项目。相比于React和Angular,Vue很是容易上手,既能够双向绑定数据快速开始构建简单应用,又能够利用Vuex单向数据传递构建大型应用。这种灵活性是它受大多数开发者欢迎的缘由之一。
为了构建一个简单的Vue应用,咱们将用到vue-cli3,一个vue项目的脚手架。首先,咱们从npm上安装脚手架。
yarn add @vue/cli
若是你尚未安装yarn,执行下列命令安装。
npm i -g yarn
接下来,咱们须要用vue-cli3构建一个项目。执行如下命令。
vue create frontend
命令行中会弹出下列选项,选择default
。
? Please pick a preset: (Use arrow keys) ❯ default (babel, eslint) preset (vue-router, vuex, node-sass, babel, eslint, unit-jest) Manually select features
而后vue-cli3会开始准备构建项目必要的依赖以及生成项目结构。
此外,咱们还须要安装完成其余功能所须要的包。
yarn add axios
在views
目录中建立一个List.vue
文件,写入下列内容。
<template> <div class="list"> <div class="left"></div> <div class="center"> <ul class="article-list"> <li v-for="article in list" :key="article._id" class="article-item"> <a href="javascript:" @click="showArticle(article._id)" class="title"> {{article.title}} </a> <span class="time"> {{article.ts}} </span> </li> </ul> </div> <div class="right"></div> </div> </template> <script> import axios from 'axios' export default { name: 'List', data () { return { list: [] } }, methods: { showArticle (id) { this.$router.push(`/${id}`) } }, created () { axios.get('http://localhost:5000/results') .then(response => { this.list = response.data }) } } </script> <style scoped> .list { display: flex; } .left { flex-basis: 20%; } .right { flex-basis: 20%; } .article-list { text-align: left; list-style: none; } .article-item { background: #c3edfb; border-radius: 5px; padding: 5px; height: 32px; display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; } .title { flex-basis: auto; color: #58769d; } .time { font-size: 10px; text-align: right; flex-basis: 180px; } </style>
其中,引用了axios
来与API进行ajax交互,这里获取的是列表接口。布局用来经典的双圣杯布局。methods
中的showArticle
方法接收id
参数,将页面跳转至详情页。
在views
目录中,建立Detail.vue
文件,并输入以下内容。
<template> <div class="detail"> <div class="left"></div> <div class="center"> <h1 class="title">{{article.title}}</h1> <div class="content" v-html="article.content"> </div> </div> <div class="right"></div> </div> </template> <script> import axios from 'axios' export default { name: 'Detail', data () { return { article: {} } }, computed: { id () { return this.$route.params.id } }, created () { axios.get(`http://localhost:5000/results/${this.id}`) .then(response => { this.article = response.data }) } } </script> <style scoped> .detail { display: flex; } .left { flex-basis: 20%; } .right { flex-basis: 20%; } .center { flex-basis: 60%; text-align: left; } .title { } </style>
这个页面也是经典的双圣杯布局,中间占40%。由API获取的文章内容输出到content
中,由v-html
绑定。这里其实能够作进一步的CSS优化,但做者太懒了,这个任务就交给读者来实现吧。
编辑router.js
文件,将其修改成如下内容。
import Vue from 'vue' import Router from 'vue-router' import List from './views/List' import Detail from './views/Detail' Vue.use(Router) export default new Router({ mode: 'hash', base: process.env.BASE_URL, routes: [ { path: '/', name: 'List', component: List }, { path: '/:id', name: 'Detail', component: Detail } ] })
在命令行中输入如下命令,打开http://localhost:8080
就能够看到文章列表了。
npm run serve
最后的聚合平台效果截屏以下,能够看到基本的样式已经出来了。
本文在上一篇文章《手把手教你如何用Crawlab构建技术文章聚合平台(一)》的基础上,介绍了如何利用Flask+Vue和以前抓取的文章数据,搭建一个简易的技术文章聚合平台。用到的技术很基础,固然,确定也还有不少须要优化和提高的空间,这个就留给读者和各位大佬吧。
Github
若是感受Crawlab还不错的话,请加做者微信拉入开发交流群,你们一块儿交流关于Crawlab的使用和开发。