上一篇文章《手把手教你如何用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的使用和开发。