玖叶教程网

前端编程开发入门

爬虫 | 如何构建技术文章聚合平台(二)

作者 | MarvinZhang

来源 | 掘金

上一篇文章《手把手教你如何用Crawlab构建技术文章聚合平台(一)》介绍了如何使用搭建Crawlab的运行环境,并且将Puppeteer与Crawlab集成,对掘金、SegmentFault、CSDN进行技术文章的抓取,最后可以查看抓取结果。本篇文章将继续讲解如何利用Flask+Vue编写一个精简的聚合平台,将抓取好的文章内容展示出来。

文章内容爬虫

首先,我们需要对爬虫部分做点小小的补充。上篇文章中我们只编写了抓取文章URL的爬虫,我们还需要抓取文章内容,因此还需要将这部分爬虫编写了。上次爬虫的结果collection全部更改为 results,文章的内容将以content字段保存在数据库中。

经分析知道每个技术网站的文章页都有一个固定标签,将该标签下的HTML全部抓取下来就OK了。具体代码分析就不展开了,这里贴出具体代码。

  1. const puppeteer = require('puppeteer');

  2. const MongoClient = require('mongodb').MongoClient;


  3. (async => {

  4. // browser

  5. const browser = await (puppeteer.launch({

  6. headless: true

  7. }));


  8. // page

  9. const page = await browser.newPage;


  10. // open database connection

  11. const client = await MongoClient.connect('mongodb://192.168.99.100:27017');

  12. let db = await client.db('crawlab_test');

  13. const colName = process.env.CRAWLAB_COLLECTION || 'results';

  14. const col = db.collection(colName);

  15. const col_src = db.collection('results');


  16. const results = await col_src.find({content: {$exists: false}}).toArray;

  17. for (let i = 0; i < results.length; i++) {

  18. let item = results[i];


  19. // define article anchor

  20. let anchor;

  21. if (item.source === 'juejin') {

  22. anchor = '.article-content';

  23. } else if (item.source === 'segmentfault') {

  24. anchor = '.article';

  25. } else if (item.source === 'csdn') {

  26. anchor = '#content_views';

  27. } else {

  28. continue;

  29. }


  30. console.log(`anchor: ${anchor}`);


  31. // navigate to the article

  32. try {

  33. await page.goto(item.url, {waitUntil: 'domcontentloaded'});

  34. await page.waitFor(2000);

  35. } catch (e) {

  36. console.error(e);

  37. continue;

  38. }


  39. // scrape article content

  40. item.content = await page.$eval(anchor, el => el.innerHTML);


  41. // save to database

  42. await col.save(item);

  43. console.log(`saved item: ${JSON.stringify(item)}`)

  44. }


  45. // close mongodb

  46. client.close;


  47. // close browser

  48. browser.close;


  49. });

然后将该爬虫按照前一篇文章的步骤部署运行爬虫,就可以采集到详细的文章内容了。

文章内容爬虫的代码已经更新到Github了。

接下来,我们可以开始对这些文章做文章了。

前后端分离

目前的技术发展来看,前后端分离已经是主流:一来前端技术越来越复杂,要求模块化、工程化;二来前后端分离可以让前后端团队分工协作,更加高效地开发应用。由于本文的聚合平台是一个轻量级应用,后端接口编写我们用Python的轻量级Web应用框架Flask,前端我们用近年来大红大紫的上手容易的Vue。

Flask

Flask被称为Micro Framework,可见其轻量级,几行代码便可以编写一个Web应用。它靠Extensions插件来扩展其特定功能,例如登录验证、RESTful、数据模型等等。这个小节中我们将搭建一个REST风格的后台API应用。

安装

首先安装相关的依赖。

  1. pip install flask flask_restful flask_cors pymongo

基本应用

安装完成后我们可以新建一个 app.py文件,输入如下代码

  1. from flask import Flask

  2. from flask_cors import CORS

  3. from flask_restful import Api


  4. # 生成Flask App实例

  5. app = Flask(__name__)


  6. # 生成API实例

  7. api = Api(app)


  8. # 支持CORS跨域

  9. CORS(app, supports_credentials=True)


  10. if __name__ == '__main__':

  11. app.run

命令行中输入 python app.py就可以运行这个基础的Flask应用了。

编写API

接下来,我们需要编写获取文章的接口。首先我们简单分析一下需求。

这个Flask应用要实现的功能为:

  1. 从数据库中获取抓取到的文章,将文章ID、标题、摘要、抓取时间返回给前端做文章列表使用;

  2. 对给定文章ID,从数据库返回相应文章内容给前端做详情页使用。

因此,我们需要实现上述两个API。下面开始编写接口。

列表接口

app.py中添加如下代码,作为列表接口。

  1. class ListApi(Resource):

  2. def get(self):

  3. # 查询

  4. items = col.find({'content': {'$exists': True}}).sort('_id', DESCENDING).limit(40)


  5. data =

  6. for item in items:

  7. # 将pymongo object转化为python object

  8. _item = json.loads(json_util.dumps(item))


  9. data.append({

  10. '_id': _item['_id']['$oid'],

  11. 'title': _item['title'],

  12. 'source': _item['source'],

  13. 'ts': item['_id'].generation_time.strftime('%Y-%m-%d %H:%M:%S')

  14. })


  15. return data

详情接口

同样的,在 app.py中输入如下代码。

  1. class DetailApi(Resource):

  2. def get(self, id):

  3. item = col.find_one({'_id': ObjectId(id)})


  4. # 将pymongo object转化为python object

  5. _item = json.loads(json_util.dumps(item))


  6. return {

  7. '_id': _item['_id']['$oid'],

  8. 'title': _item['title'],

  9. 'source': _item['source'],

  10. 'ts': item['_id'].generation_time.strftime('%Y-%m-%d %H:%M:%S'),

  11. 'content': _item['content']

  12. }

映射接口

编写完接口,我们需要将它们映射到对应到URL中。

  1. api.add_resource(ListApi, '/results')

  2. api.add_resource(DetailApi, '/results/<string:id>')

完整代码

以下是完整的Flask应用代码,很简单,实现了文章列表和文章详情两个功能。接下来,我们将开始开发前端的部分。

  1. import json


  2. from bson import json_util, ObjectId

  3. from flask import Flask, jsonify

  4. from flask_cors import CORS

  5. from flask_restful import Api, Resource

  6. from pymongo import MongoClient, DESCENDING


  7. # 生成Flask App实例

  8. app = Flask(__name__)


  9. # 生成MongoDB实例

  10. mongo = MongoClient(host='192.168.99.100')

  11. db = mongo['crawlab_test']

  12. col = db['results']


  13. # 生成API实例

  14. api = Api(app)


  15. # 支持CORS跨域

  16. CORS(app, supports_credentials=True)



  17. class ListApi(Resource):

  18. def get(self):

  19. # 查询

  20. items = col.find({}).sort('_id', DESCENDING).limit(20)


  21. data =

  22. for item in items:

  23. # 将pymongo object转化为python object

  24. _item = json.loads(json_util.dumps(item))


  25. data.append({

  26. '_id': _item['_id']['$oid'],

  27. 'title': _item['title'],

  28. 'source': _item['source'],

  29. 'ts': item['_id'].generation_time.strftime('%Y-%m-%d %H:%M:%S')

  30. })


  31. return data



  32. class DetailApi(Resource):

  33. def get(self, id):

  34. item = col.find_one({'_id': ObjectId(id)})


  35. # 将pymongo object转化为python object

  36. _item = json.loads(json_util.dumps(item))


  37. return {

  38. '_id': _item['_id']['$oid'],

  39. 'title': _item['title'],

  40. 'source': _item['source'],

  41. 'ts': item['_id'].generation_time.strftime('%Y-%m-%d %H:%M:%S'),

  42. 'content': _item['content']

  43. }



  44. api.add_resource(ListApi, '/results')

  45. api.add_resource(DetailApi, '/results/<string:id>')


  46. if __name__ == '__main__':

  47. app.run

运行 python app.py,将后台接口服务器跑起来。

Vue

Vue近年来是热得发烫,在Github上已经超越React,成为三大开源框架(React,Vue,Angular)中star数最多的项目。相比于React和Angular,Vue非常容易上手,既可以双向绑定数据快速开始构建简单应用,又可以利用Vuex单向数据传递构建大型应用。这种灵活性是它受大多数开发者欢迎的原因之一。

为了构建一个简单的Vue应用,我们将用到vue-cli3,一个vue项目的脚手架。首先,我们从npm上安装脚手架。

安装vue-cli3

  1. yarn add @vue/cli

如果你还没有安装yarn,执行下列命令安装。

  1. npm i -g yarn

创建项目

接下来,我们需要用vue-cli3构建一个项目。执行以下命令。

  1. vue create frontend

命令行中会弹出下列选项,选择 default

  1. ? Please pick a preset: (Use arrow keys)

  2. ? default (babel, eslint)

  3. preset (vue-router, vuex, node-sass, babel, eslint, unit-jest)

  4. Manually select features

然后vue-cli3会开始准备构建项目必要的依赖以及生成项目结构。

此外,我们还需要安装完成其他功能所需要的包。

  1. yarn add axios

文章列表页面

views目录中创建一个List.vue文件,写入下列内容。

  1. <template>

  2. <div class="list">

  3. <div class="left"></div>

  4. <div class="center">

  5. <ul class="article-list">

  6. <li v-for="article in list" :key="article._id" class="article-item">

  7. <a href="javascript:" @click="showArticle(article._id)" class="title">

  8. {{article.title}}

  9. </a>

  10. <span class="time">

  11. {{article.ts}}

  12. </span>

  13. </li>

  14. </ul>

  15. </div>

  16. <div class="right"></div>

  17. </div>

  18. </template>


  19. <script>

  20. import axios from 'axios'


  21. export default {

  22. name: 'List',

  23. data {

  24. return {

  25. list:

  26. }

  27. },

  28. methods: {

  29. showArticle (id) {

  30. this.$router.push(`/${id}`)

  31. }

  32. },

  33. created {

  34. axios.get('http://localhost:5000/results')

  35. .then(response => {

  36. this.list = response.data

  37. })

  38. }

  39. }

  40. </script>


  41. <style scoped>

  42. .list {

  43. display: flex;

  44. }


  45. .left {

  46. flex-basis: 20%;

  47. }


  48. .right {

  49. flex-basis: 20%;

  50. }


  51. .article-list {

  52. text-align: left;

  53. list-style: none;

  54. }


  55. .article-item {

  56. background: #c3edfb;

  57. border-radius: 5px;

  58. padding: 5px;

  59. height: 32px;

  60. display: flex;

  61. align-items: center;

  62. justify-content: space-between;

  63. margin-bottom: 10px;

  64. }


  65. .title {

  66. flex-basis: auto;

  67. color: #58769d;

  68. }


  69. .time {

  70. font-size: 10px;

  71. text-align: right;

  72. flex-basis: 180px;

  73. }

  74. </style>

其中,引用了 axios来与API进行ajax交互,这里获取的是列表接口。布局用来经典的双圣杯布局。methods中的showArticle方法接收id参数,将页面跳转至详情页。

文章详情页面

views目录中,创建Detail.vue文件,并输入如下内容。

  1. <template>

  2. <div class="detail">

  3. <div class="left"></div>

  4. <div class="center">

  5. <h1 class="title">{{article.title}}</h1>

  6. <div class="content" v-html="article.content">

  7. </div>

  8. </div>

  9. <div class="right"></div>

  10. </div>

  11. </template>


  12. <script>

  13. import axios from 'axios'


  14. export default {

  15. name: 'Detail',

  16. data {

  17. return {

  18. article: {}

  19. }

  20. },

  21. computed: {

  22. id {

  23. return this.$route.params.id

  24. }

  25. },

  26. created {

  27. axios.get(`http://localhost:5000/results/${this.id}`)

  28. .then(response => {

  29. this.article = response.data

  30. })

  31. }

  32. }

  33. </script>


  34. <style scoped>

  35. .detail {

  36. display: flex;

  37. }


  38. .left {

  39. flex-basis: 20%;

  40. }


  41. .right {

  42. flex-basis: 20%;

  43. }


  44. .center {

  45. flex-basis: 60%;

  46. text-align: left;

  47. }


  48. .title {


  49. }

  50. </style>

这个页面也是经典的双圣杯布局,中间占40%。由API获取的文章内容输出到 content中,由v-html绑定。这里其实可以做进一步的CSS优化,但作者太懒了,这个任务就交给读者来实现吧。

添加路由

编辑 router.js文件,将其修改为以下内容。

  1. import Vue from 'vue'

  2. import Router from 'vue-router'

  3. import List from './views/List'

  4. import Detail from './views/Detail'


  5. Vue.use(Router)


  6. export default new Router({

  7. mode: 'hash',

  8. base: process.env.BASE_URL,

  9. routes: [

  10. {

  11. path: '/',

  12. name: 'List',

  13. component: List

  14. },

  15. {

  16. path: '/:id',

  17. name: 'Detail',

  18. component: Detail

  19. }

  20. ]

  21. })

运行前端

在命令行中输入以下命令,打开 http://localhost:8080就可以看到文章列表了。

  1. npm run serve

最终效果

最后的聚合平台效果截屏如下,可以看到基本的样式已经出来了。

总结

本文在上一篇文章《手把手教你如何用Crawlab构建技术文章聚合平台(一)》的基础上,介绍了如何利用Flask+Vue和之前抓取的文章数据,搭建一个简易的技术文章聚合平台。用到的技术很基础,当然,肯定也还有很多需要优化和提升的空间,这个就留给读者和各位大佬吧。

本文经作者授权发布,如需转载请直接联系原作者。

博客地址:https://juejin.im/user/5a1ba6def265da430b7af463

回复下方「关键词」,获取优质资源

回复关键词「 pybook03」,可立即获取主页君与小伙伴一起翻译的《Think Python 2e》电子版

回复关键词「pybooks02」,可立即获取 O'Reilly 出版社推出的免费 Python 相关电子书合集

回复关键词「书单02」,可立即获取主页君整理的 10 本 Python 入门书的电子版

印度小伙写了套深度学习教程,Github上星标已经5000+

上百个数据文件合并,只能手动复制粘贴?教你一招十秒搞定!

一个提升图像识别准确率的精妙技巧

一文读懂:从 Python 打包到 CLI 工具

如何使用 Python 进行时间序列预测?

美亚Kindle排名第一的Python 3入门书,火遍了整个编程圈

十分钟搭建私有 Jupyter Notebook 服务器

使用 Python 制作属于自己的 PDF 电子书

12步轻松搞定Python装饰器

200 行代码实现 2048 游戏

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言