百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

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

ccwgpt 2024-11-07 09:46 76 浏览 0 评论

或许是 Scrapy 之外的一个新选择,尤其对于比较熟悉 JavaScript 的同学来说。

作者 | MarvinZhang

来源 | 掘金

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

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

背景

说到爬虫,大多数程序员想到的是scrapy这样受人欢迎的框架。scrapy的确不错,而且有很强大的生态圈,有gerapy等优秀的可视化界面。但是,它还是有一些不能做到的事情,例如在页面上做翻页点击操作、移动端抓取等等。对于这些新的需求,可以用Selenium、Puppeteer、Appium这些自动化测试框架绕开繁琐的动态内容,直接模拟用户操作进行抓取。可惜的是,这些框架不是专门的爬虫框架,不能对爬虫进行集中管理,因此对于一个多达数十个爬虫的大型项目来说有些棘手

Crawlab是一个基于Celery的分布式通用爬虫管理平台,擅长将不同编程语言编写的爬虫整合在一处,方便监控和管理。Crawlab有精美的可视化界面,能对多个爬虫进行运行和管理。任务调度引擎是本身支持分布式架构的Celery,因此Crawlab可以天然集成分布式爬虫。有一些朋友认为Crawlab只是一个任务调度引擎,其实这样认为并不完全正确。Crawlab是类似Gerapy这样的专注于爬虫的管理平台。

本文将介绍如何使用Crawlab和Puppeteer抓取主流的技术博客文章,然后用Flask+Vue搭建一个小型的技术文章聚合平台

Crawlab

在前一篇文章《分布式通用爬虫管理平台Crawlab》已介绍了Crawlab的架构以及安装使用,这里快速介绍一下如何安装、运行、使用Crawlab。(感兴趣的同学可以去作者的掘金主页查看)

安装

到Crawlab的Github Repo用克隆一份到本地。

  1. git clone https://github.com/tikazyq/crawlab

  2. 复制代码

安装相应的依赖包和库。

  1. cd crawlab


  2. # 安装python依赖

  3. pip install -r crawlab/requirements


  4. # 安装前端依赖

  5. cd frontend

  6. npm install

  7. 复制代码

安装mongodb和redis-server。Crawlab将用MongoDB作为结果集以及运行操作的储存方式,Redis作为Celery的任务队列,因此需要安装这两个数据库。

运行

在运行之前需要对Crawlab进行一些配置,配置文件为 config.py

  1. # project variables

  2. PROJECT_SOURCE_FILE_FOLDER = '/Users/yeqing/projects/crawlab/spiders' # 爬虫源码根目录

  3. PROJECT_DEPLOY_FILE_FOLDER = '/var/crawlab' # 爬虫部署根目录

  4. PROJECT_LOGS_FOLDER = '/var/logs/crawlab' # 日志目录

  5. PROJECT_TMP_FOLDER = '/tmp' # 临时文件目录


  6. # celery variables

  7. BROKER_URL = 'redis://192.168.99.100:6379/0' # 中间者URL,连接redis

  8. CELERY_RESULT_BACKEND = 'mongodb://192.168.99.100:27017/' # CELERY后台URL

  9. CELERY_MONGODB_BACKEND_SETTINGS = {

  10. 'database': 'crawlab_test',

  11. 'taskmeta_collection': 'tasks_celery',

  12. }

  13. CELERY_TIMEZONE = 'Asia/Shanghai'

  14. CELERY_ENABLE_UTC = True


  15. # flower variables

  16. FLOWER_API_ENDPOINT = 'http://localhost:5555/api' # Flower服务地址


  17. # database variables

  18. MONGO_HOST = '192.168.99.100'

  19. MONGO_PORT = 27017

  20. MONGO_DB = 'crawlab_test'


  21. # flask variables

  22. DEBUG = True

  23. FLASK_HOST = '127.0.0.1'

  24. FLASK_PORT = 8000

  25. 复制代码

启动后端API,也就是一个Flask App,可以直接启动,或者用gunicorn代替。

  1. cd ../crawlab

  2. python app.py

  3. 复制代码

启动Flower服务(抱歉目前集成Flower到App服务中,必须单独启动来获取节点信息,后面的版本不需要这个操作)。

  1. python ./bin/run_flower.py

  2. 复制代码

启动本地Worker。在其他节点中如果想只是想执行任务的话,只需要启动这一个服务就可以了。

  1. python ./bin/run_worker.py

  2. 复制代码

启动前端服务器。

  1. cd ../frontend

  2. npm run serve

  3. 复制代码

使用

首页Home中可以看到总任务数、总爬虫数、在线节点数和总部署数,以及过去30天的任务运行数量。

点击侧边栏的Spiders或者上方到Spiders数,可以进入到爬虫列表页。

这些是爬虫源码根目录 PROJECT_SOURCE_FILE_FOLDER下的爬虫。Crawlab会自动扫描该目录下的子目录,将子目录看作一个爬虫。Action列下有一些操作选项,点击部署Deploy按钮将爬虫部署到所有在线节点中。部署成功后,点击运行Run按钮,触发抓取任务。这时,任务应该已经在执行了。点击侧边栏的Tasks到任务列表,可以看到已经调度过的爬虫任务。

基本使用就是这些,但是Crawlab还能做到更多,大家可以进一步探索,详情请见Github。

Puppeteer

Puppeteer是谷歌开源的基于Chromium和NodeJS的自动化测试工具,可以很方便的让程序模拟用户的操作,对浏览器进行程序化控制。Puppeteer有一些常用操作,例如点击,鼠标移动,滑动,截屏,下载文件等等。另外,Puppeteer很类似Selenium,可以定位浏览器中网页元素,将其数据抓取下来。因此,Puppeteer也成为了新的爬虫利器。

相对于Selenium,Puppeteer是新的开源项目,而且是谷歌开发,可以使用很多新的特性。对于爬虫来说,如果前端知识足够的话,写数据抓取逻辑简直不能再简单。正如其名字一样,我们是在操作木偶人来帮我们抓取数据,是不是很贴切?

掘金上已经有很多关于Puppeteer的教程了(爬虫利器 Puppeteer 实战、Puppeteer 与 Chrome Headless —— 从入门到爬虫),这里只简单介绍一下Puppeteer的安装和使用。

安装

安装很简单,就一行 npm install命令,npm会自动下载Chromium并安装,这个时间会比较长。为了让安装好的puppeteer模块能够被所有nodejs爬虫所共享,我们在PROJECT_DEPLOY_FILE_FOLDER目录下安装node的包。

  1. # PROJECT_DEPLOY_FILE_FOLDER变量值

  2. cd /var/crawlab


  3. # 安装puppeteer

  4. npm i puppeteer


  5. # 安装mongodb

  6. npm i mongodb

  7. 复制代码

安装mongodb是为了后续的数据库操作。

使用

以下是Copy/Paste的一段用Puppeteer访问简书然后截屏的代码,非常简洁。

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


  2. (async => {

  3. const browser = await (puppeteer.launch);

  4. const page = await browser.newPage;

  5. await page.goto('https://www.jianshu.com/u/40909ea33e50');

  6. await page.screenshot({

  7. path: 'jianshu.png',

  8. type: 'png',

  9. // quality: 100, 只对jpg有效

  10. fullPage: true,

  11. // 指定区域截图,clip和fullPage两者只能设置一个

  12. // clip: {

  13. // x: 0,

  14. // y: 0,

  15. // width: 1000,

  16. // height: 40

  17. // }

  18. });

  19. browser.close;

  20. });

  21. 复制代码

关于Puppeteer的常用操作,请移步《我常用的puppeteer爬虫api》。

编写爬虫

啰嗦了这么久,终于到了万众期待的爬虫时间了。Talk is cheap, show me the code!咦?我们不是已经Show了不少代码了么...

由于我们的目标是建立一个技术文章聚合平台,我们需要去各大技术网站抓取文章。资源当然是越多越好。作为展示用,我们将抓取下面几个具有代表性的网站:

  • 掘金

  • SegmentFault

  • CSDN

研究发现这三个网站都是由Ajax获取文章列表,生成动态内容以作为传统的分页替代。这对于Puppeteer来说很容易处理,因为Puppeteer绕开了解析Ajax这一部分,浏览器会自动处理这样的操作和请求,我们只着重关注数据获取就行了。三个网站的抓取策略基本相同,我们以掘金为例着重讲解。

掘金

首先是引入Puppeteer和打开网页。

  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. // define start url

  9. const url = 'https://juejin.im';


  10. // start a new page

  11. const page = await browser.newPage;


  12. ...


  13. });

  14. 复制代码

headless设置为true可以让浏览器以headless的方式运行,也就是指浏览器不用在界面中打开,它会在后台运行,用户是看不到浏览器的。browser.newPage将新生成一个标签页。后面的操作基本就围绕着生成的page来进行。

接下来我们让浏览器导航到start url。

  1. ...


  2. // navigate to url

  3. try {

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

  5. await page.waitFor(2000);

  6. } catch (e) {

  7. console.error(e);


  8. // close browser

  9. browser.close;


  10. // exit code 1 indicating an error happened

  11. code = 1;

  12. process.emit("exit ");

  13. process.reallyExit(code);


  14. return

  15. }


  16. ...

  17. 复制代码

这里 trycatch的操作是为了处理浏览器访问超时的错误。当访问超时时,设置exit code1表示该任务失败了,这样Crawlab会将该任务状态设置为FAILURE

然后我们需要下拉页面让浏览器可以读取下一页。

  1. ...


  2. // scroll down to fetch more data

  3. for (let i = 0; i < 100; i++) {

  4. console.log('Pressing PageDown...');

  5. await page.keyboard.press('PageDown', 200);

  6. await page.waitFor(100);

  7. }


  8. ...

  9. 复制代码

翻页完毕后,就开始抓取数据了。

  1. ...

  2. // scrape data

  3. const results = await page.evaluate( => {

  4. let results = ;

  5. document.querySelectorAll('.entry-list > .item').forEach(el => {

  6. if (!el.querySelector('.title')) return;

  7. results.push({

  8. url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),

  9. title: el.querySelector('.title').innerText

  10. });

  11. });

  12. return results;

  13. });

  14. ...

  15. 复制代码

page.evaluate可以在浏览器Console中进行JS操作。这段代码其实可以直接在浏览器Console中直接运行。调试起来是不是方便到爽?前端工程师们,开始欢呼吧!

获取了数据,接下来我们需要将其储存在数据库中。

  1. ...


  2. // open database connection

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

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

  5. const colName = process.env.CRAWLAB_COLLECTION || 'results_juejin';

  6. const taskId = process.env.CRAWLAB_TASK_ID;

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


  8. // save to database

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

  10. // de-duplication

  11. const r = await col.findOne({url: results[i]});

  12. if (r) continue;


  13. // assign taskID

  14. results[i].task_id = taskId;


  15. // insert row

  16. await col.insertOne(results[i]);

  17. }


  18. ...

  19. 复制代码

这样,我们就将掘金最新的文章数据保存在了数据库中。其中,我们用 url字段做了去重处理。CRAWLAB_COLLECTIONCRAWLAB_TASK_ID是Crawlab传过来的环境变量,分别是储存的collection和任务ID。任务ID需要以task_id为键保存起来,这样在Crawlab中就可以将数据与任务关联起来了。

整个爬虫代码如下。

  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. // define start url

  9. const url = 'https://juejin.im';


  10. // start a new page

  11. const page = await browser.newPage;


  12. // navigate to url

  13. try {

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

  15. await page.waitFor(2000);

  16. } catch (e) {

  17. console.error(e);


  18. // close browser

  19. browser.close;


  20. // exit code 1 indicating an error happened

  21. code = 1;

  22. process.emit("exit ");

  23. process.reallyExit(code);


  24. return

  25. }


  26. // scroll down to fetch more data

  27. for (let i = 0; i < 100; i++) {

  28. console.log('Pressing PageDown...');

  29. await page.keyboard.press('PageDown', 200);

  30. await page.waitFor(100);

  31. }


  32. // scrape data

  33. const results = await page.evaluate( => {

  34. let results = ;

  35. document.querySelectorAll('.entry-list > .item').forEach(el => {

  36. if (!el.querySelector('.title')) return;

  37. results.push({

  38. url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),

  39. title: el.querySelector('.title').innerText

  40. });

  41. });

  42. return results;

  43. });


  44. // open database connection

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

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

  47. const colName = process.env.CRAWLAB_COLLECTION || 'results_juejin';

  48. const taskId = process.env.CRAWLAB_TASK_ID;

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


  50. // save to database

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

  52. // de-duplication

  53. const r = await col.findOne({url: results[i]});

  54. if (r) continue;


  55. // assign taskID

  56. results[i].task_id = taskId;


  57. // insert row

  58. await col.insertOne(results[i]);

  59. }


  60. console.log(`results.length: ${results.length}`);


  61. // close database connection

  62. client.close;


  63. // shutdown browser

  64. browser.close;

  65. });

  66. 复制代码

SegmentFault & CSDN

这两个网站的爬虫代码基本与上面的爬虫一样,只是一些参数不一样而已。我们的爬虫项目结构如下。

运行爬虫

在Crawlab中打开Spiders,我们可以看到刚刚编写好的爬虫。

点击各个爬虫的View查看按钮,进入到爬虫详情。

在Execute Command中输入爬虫执行命令。对掘金爬虫来说,是 node juejin_spider.js。输入完毕后点击Save保存。然后点击Deploy部署爬虫。最后点击Run运行爬虫。

点击左上角到刷新按钮可以看到刚刚运行的爬虫任务已经在运行了。点击Create Time后可以进入到任务详情。Overview标签中可以看到任务信息,Log标签可以看到日志信息,Results信息中可以看到抓取结果。目前在Crawlab结果列表中还不支持数据导出,但是不久的版本中肯定会将导出功能加入进来。

总结

在这一小节,我们已经能够将Crawlab运行起来,并且能用Puppeteer编写抓取三大网站技术文章的爬虫,并且能够用Crawlab运行爬虫,并且读取抓取后的数据。下一节,我们将用Flask+Vue做一个简单的技术文章聚合网站。能看到这里的都是有耐心的好同学,赞一个。

-- END --

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

回复关键词「 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 游戏

题图:pexels,CC0 授权。

点击阅读原文,查看更多 Python 教程和资源。

相关推荐

一个基于.Net Core遵循Clean Architecture原则开源架构

今天给大家推荐一个遵循CleanArchitecture原则开源架构。项目简介这是基于Asp.netCore6开发的,遵循CleanArchitecture原则,可以高效、快速地构建基于Ra...

AI写代码翻车无数次,我发现只要提前做好这3步,bug立减80%

写十万行全是bug之后终于找到方法了开发"提示词管理助手"新版本那会儿,我差点被bug整崩溃。刚开始两周,全靠AI改代码架构,结果十万行程序漏洞百出。本来以为AI说没问题就稳了,结果...

OneCode低代码平台的事件驱动设计:架构解析与实践

引言:低代码平台的事件驱动范式在现代软件开发中,事件驱动架构(EDA)已成为构建灵活、松耦合系统的核心范式。OneCode低代码平台通过创新性的注解驱动设计,将事件驱动理念深度融入平台架构,实现了业务...

国内大厂AI插件评测:根据UI图生成Vue前端代码

在IDEA中安装大厂的AI插件,打开ruoyi增强项目:yudao-ui-admin-vue31.CodeBuddy插件登录腾讯的CodeBuddy后,大模型选择deepseek-v3,输入提示语:...

AI+低代码技术揭秘(二):核心架构

本文档介绍了为VTJ低代码平台提供支持的基本架构组件,包括Engine编排层、Provider服务系统、数据模型和代码生成管道。有关UI组件库和widget系统的信息,请参阅UI...

GitDiagram用AI把代码库变成可视化架构图

这是一个名为gitdiagram的开源工具,可将GitHub仓库实时转换为交互式架构图,帮助开发者快速理解代码结构。核心功能一键可视化:替换GitHubURL中的"hub...

30天自制操作系统:第六天:代码架构整理与中断处理

1.拆开bootpack.c文件。根据设计模式将对应的功能封装成独立的文件。2.初始化pic:pic(可编程中断控制器):在设计上,cpu单独只能处理一个中断。而pic是将8个中断信号集合成一个中断...

AI写代码越帮越忙?2025年研究揭露惊人真相

近年来,AI工具如雨后春笋般涌现,许多人开始幻想程序员的未来就是“对着AI说几句话”,就能轻松写出完美的代码。然而,2025年的一项最新研究却颠覆了这一期待,揭示了一个令人意外的结果。研究邀请了16位...

一键理解开源项目:两个自动生成GitHub代码架构图与说明书工具

一、GitDiagram可以一键生成github代码仓库的架构图如果想要可视化github开源项目:https://github.com/luler/reflex_ai_fast,也可以直接把域名替换...

5分钟掌握 c# 网络通讯架构及代码示例

以下是C#网络通讯架构的核心要点及代码示例,按协议类型分类整理:一、TCP协议(可靠连接)1.同步通信//服务器端usingSystem.Net.Sockets;usingTcpListene...

从复杂到优雅:用建造者和责任链重塑代码架构

引用设计模式是软件开发中的重要工具,它为解决常见问题提供了标准化的解决方案,提高了代码的可维护性和可扩展性,提升了开发效率,促进了团队协作,提高了软件质量,并帮助开发者更好地适应需求变化。通过学习和应...

低代码开发当道,我还需要学习LangChain这些框架吗?| IT杂谈

专注LLM深度应用,关注我不迷路前两天有位兄弟问了个问题:当然我很能理解这位朋友的担忧:期望效率最大化,时间用在刀刃上,“不要重新发明轮子”嘛。铺天盖地的AI信息轰炸与概念炒作,很容易让人浮躁与迷茫。...

框架设计并不是简单粗暴地写代码,而是要先弄清逻辑

3.框架设计3.框架设计本节我们要开发一个UI框架,底层以白鹭引擎为例。框架设计的第一步并不是直接撸代码,而是先想清楚设计思想,抽象。一个一个的UI窗口是独立的吗?不是的,...

大佬用 Avalonia 框架开发的 C# 代码 IDE

AvalonStudioAvalonStudio是一个开源的跨平台的开发编辑器(IDE),AvalonStudio的目标是成为一个功能齐全,并且可以让开发者快速使用的IDE,提高开发的生产力。A...

轻量级框架Lagent 仅需20行代码即可构建自己的智能代理

站长之家(ChinaZ.com)8月30日消息:Lagent是一个专注于基于LLM模型的代理开发的轻量级框架。它的设计旨在简化和提高这种模型下代理的开发效率。LLM模型是一种强大的工具,可以...

取消回复欢迎 发表评论: