爬虫 | 如何构建技术文章聚合平台(一)
ccwgpt 2024-11-07 09:46 73 浏览 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用克隆一份到本地。
git clone https://github.com/tikazyq/crawlab
复制代码
安装相应的依赖包和库。
cd crawlab
# 安装python依赖
pip install -r crawlab/requirements
# 安装前端依赖
cd frontend
npm install
复制代码
安装mongodb和redis-server。Crawlab将用MongoDB作为结果集以及运行操作的储存方式,Redis作为Celery的任务队列,因此需要安装这两个数据库。
运行
在运行之前需要对Crawlab进行一些配置,配置文件为 config.py
。
# project variables
PROJECT_SOURCE_FILE_FOLDER = '/Users/yeqing/projects/crawlab/spiders' # 爬虫源码根目录
PROJECT_DEPLOY_FILE_FOLDER = '/var/crawlab' # 爬虫部署根目录
PROJECT_LOGS_FOLDER = '/var/logs/crawlab' # 日志目录
PROJECT_TMP_FOLDER = '/tmp' # 临时文件目录
# celery variables
BROKER_URL = 'redis://192.168.99.100:6379/0' # 中间者URL,连接redis
CELERY_RESULT_BACKEND = 'mongodb://192.168.99.100:27017/' # CELERY后台URL
CELERY_MONGODB_BACKEND_SETTINGS = {
'database': 'crawlab_test',
'taskmeta_collection': 'tasks_celery',
}
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_ENABLE_UTC = True
# flower variables
FLOWER_API_ENDPOINT = 'http://localhost:5555/api' # Flower服务地址
# database variables
MONGO_HOST = '192.168.99.100'
MONGO_PORT = 27017
MONGO_DB = 'crawlab_test'
# flask variables
DEBUG = True
FLASK_HOST = '127.0.0.1'
FLASK_PORT = 8000
复制代码
启动后端API,也就是一个Flask App,可以直接启动,或者用gunicorn代替。
cd ../crawlab
python app.py
复制代码
启动Flower服务(抱歉目前集成Flower到App服务中,必须单独启动来获取节点信息,后面的版本不需要这个操作)。
python ./bin/run_flower.py
复制代码
启动本地Worker。在其他节点中如果想只是想执行任务的话,只需要启动这一个服务就可以了。
python ./bin/run_worker.py
复制代码
启动前端服务器。
cd ../frontend
npm run serve
复制代码
使用
首页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的包。
# PROJECT_DEPLOY_FILE_FOLDER变量值
cd /var/crawlab
# 安装puppeteer
npm i puppeteer
# 安装mongodb
npm i mongodb
复制代码
安装mongodb是为了后续的数据库操作。
使用
以下是Copy/Paste的一段用Puppeteer访问简书然后截屏的代码,非常简洁。
const puppeteer = require('puppeteer');
(async => {
const browser = await (puppeteer.launch);
const page = await browser.newPage;
await page.goto('https://www.jianshu.com/u/40909ea33e50');
await page.screenshot({
path: 'jianshu.png',
type: 'png',
// quality: 100, 只对jpg有效
fullPage: true,
// 指定区域截图,clip和fullPage两者只能设置一个
// clip: {
// x: 0,
// y: 0,
// width: 1000,
// height: 40
// }
});
browser.close;
});
复制代码
关于Puppeteer的常用操作,请移步《我常用的puppeteer爬虫api》。
编写爬虫
啰嗦了这么久,终于到了万众期待的爬虫时间了。Talk is cheap, show me the code!咦?我们不是已经Show了不少代码了么...
由于我们的目标是建立一个技术文章聚合平台,我们需要去各大技术网站抓取文章。资源当然是越多越好。作为展示用,我们将抓取下面几个具有代表性的网站:
掘金
SegmentFault
CSDN
研究发现这三个网站都是由Ajax获取文章列表,生成动态内容以作为传统的分页替代。这对于Puppeteer来说很容易处理,因为Puppeteer绕开了解析Ajax这一部分,浏览器会自动处理这样的操作和请求,我们只着重关注数据获取就行了。三个网站的抓取策略基本相同,我们以掘金为例着重讲解。
掘金
首先是引入Puppeteer和打开网页。
const puppeteer = require('puppeteer');
const MongoClient = require('mongodb').MongoClient;
(async => {
// browser
const browser = await (puppeteer.launch({
headless: true
}));
// define start url
const url = 'https://juejin.im';
// start a new page
const page = await browser.newPage;
...
});
复制代码
headless
设置为true
可以让浏览器以headless的方式运行,也就是指浏览器不用在界面中打开,它会在后台运行,用户是看不到浏览器的。browser.newPage
将新生成一个标签页。后面的操作基本就围绕着生成的page
来进行。
接下来我们让浏览器导航到start url。
...
// navigate to url
try {
await page.goto(url, {waitUntil: 'domcontentloaded'});
await page.waitFor(2000);
} catch (e) {
console.error(e);
// close browser
browser.close;
// exit code 1 indicating an error happened
code = 1;
process.emit("exit ");
process.reallyExit(code);
return
}
...
复制代码
这里 try
catch
的操作是为了处理浏览器访问超时的错误。当访问超时时,设置exit code
为1
表示该任务失败了,这样Crawlab会将该任务状态设置为FAILURE
。
然后我们需要下拉页面让浏览器可以读取下一页。
...
// scroll down to fetch more data
for (let i = 0; i < 100; i++) {
console.log('Pressing PageDown...');
await page.keyboard.press('PageDown', 200);
await page.waitFor(100);
}
...
复制代码
翻页完毕后,就开始抓取数据了。
...
// scrape data
const results = await page.evaluate( => {
let results = ;
document.querySelectorAll('.entry-list > .item').forEach(el => {
if (!el.querySelector('.title')) return;
results.push({
url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),
title: el.querySelector('.title').innerText
});
});
return results;
});
...
复制代码
page.evaluate
可以在浏览器Console中进行JS操作。这段代码其实可以直接在浏览器Console中直接运行。调试起来是不是方便到爽?前端工程师们,开始欢呼吧!
获取了数据,接下来我们需要将其储存在数据库中。
...
// 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_juejin';
const taskId = process.env.CRAWLAB_TASK_ID;
const col = db.collection(colName);
// save to database
for (let i = 0; i < results.length; i++) {
// de-duplication
const r = await col.findOne({url: results[i]});
if (r) continue;
// assign taskID
results[i].task_id = taskId;
// insert row
await col.insertOne(results[i]);
}
...
复制代码
这样,我们就将掘金最新的文章数据保存在了数据库中。其中,我们用 url
字段做了去重处理。CRAWLAB_COLLECTION
和CRAWLAB_TASK_ID
是Crawlab传过来的环境变量,分别是储存的collection和任务ID。任务ID需要以task_id
为键保存起来,这样在Crawlab中就可以将数据与任务关联起来了。
整个爬虫代码如下。
const puppeteer = require('puppeteer');
const MongoClient = require('mongodb').MongoClient;
(async => {
// browser
const browser = await (puppeteer.launch({
headless: true
}));
// define start url
const url = 'https://juejin.im';
// start a new page
const page = await browser.newPage;
// navigate to url
try {
await page.goto(url, {waitUntil: 'domcontentloaded'});
await page.waitFor(2000);
} catch (e) {
console.error(e);
// close browser
browser.close;
// exit code 1 indicating an error happened
code = 1;
process.emit("exit ");
process.reallyExit(code);
return
}
// scroll down to fetch more data
for (let i = 0; i < 100; i++) {
console.log('Pressing PageDown...');
await page.keyboard.press('PageDown', 200);
await page.waitFor(100);
}
// scrape data
const results = await page.evaluate( => {
let results = ;
document.querySelectorAll('.entry-list > .item').forEach(el => {
if (!el.querySelector('.title')) return;
results.push({
url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),
title: el.querySelector('.title').innerText
});
});
return results;
});
// 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_juejin';
const taskId = process.env.CRAWLAB_TASK_ID;
const col = db.collection(colName);
// save to database
for (let i = 0; i < results.length; i++) {
// de-duplication
const r = await col.findOne({url: results[i]});
if (r) continue;
// assign taskID
results[i].task_id = taskId;
// insert row
await col.insertOne(results[i]);
}
console.log(`results.length: ${results.length}`);
// close database connection
client.close;
// shutdown browser
browser.close;
});
复制代码
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 教程和资源。
相关推荐
- 用Deepseek扩写土木工程毕业论文实操指南
-
用Deepseek扩写毕业论文实操指南一、前期准备整理现有论文初稿/提纲列清楚论文核心框架(背景、现状、意义、方法、数据、结论等)梳理好关键文献,明确核心技术路线二、Deepseek扩写核心思路...
- 985学霸亲授,DeepSeek也能绘6大科研图表,5分钟就出图
-
在实验数据处理中,高效可视化是每个科研人的必修课。传统绘图软件操作复杂、耗时费力,而智能工具DeepSeek的出现彻底改变了这一现状。本文将详解如何用DeepSeek一键生成六大科研常用图表,从思维导...
- AI写论文刷屏?大学生正在丢掉的思考力
-
一、宿舍深夜:当论文变成"Ctrl+C+V"凌晨两点的大学宿舍,小王对着电脑屏幕叹气。本该三天前开始写的近代史论文,此刻还一片空白。他熟练打开某AI写作网站,输入"论五四运动的...
- Grok在辅助论文写作上能不能既“聪明”又“可怕”?!
-
AcademicIdeas-学境思源AI初稿写作随着人工智能技术的飞速发展,论文写作这一学术任务正迎来新的助力。2025年2月18日,美国xAI公司推出了备受瞩目的Grok3模型,其创始人埃隆·...
- 大四论文沟通场景!音频转文字难题听脑AI来化解
-
大四学生都知道,写论文时和导师沟通修改意见,简直是“过关斩将”。电话、语音沟通完,想把导师说的修改方向、重点要求记下来,麻烦事儿可不少。手写记不全,用普通录音转文字工具,转完还得自己慢慢找重点,稍不注...
- 论文写作 | 技术路线图怎么画?(提供经典优秀模板参考)
-
技术路线图是一种图表或文字说明,用于描述研究目标、方法和实施计划。它展示了研究的整体框架和步骤,有助于读者理解研究的逻辑和进展。在课题及论文中,技术路线图是常见的一部分,甚至是一个类似心脏一样的中枢器...
- 25年信息系统项目管理师考试第2批论文题目写作建议思路框架
-
25年信息系统项目管理师考试第2批论文题目写作建议思路框架--马军老师
- 微信购物应尽快纳入法律框架(微信购物管辖)
-
符向军近日,甘肃省工商行政管理局发布《2016年上半年信息分析报告》。报告显示,微信网购纠纷迅猛增长,网络购物投诉呈上升趋势。投诉的主要问题有出售的商品质量不过关、消费者通过微信付款后对方不发货、购买...
- 泛珠三角区域网络媒体与腾讯微信签署《战略合作框架协议》
-
新海南客户端、南海网7月14日消息(记者任桐)7月14日上午,参加第四届泛珠三角区域合作网络媒体论坛的区域网络媒体负责人及嘉宾一行到腾讯微信总部座谈交流,并签署《战略合作框架协议》(以下简称《框架协...
- 离线使用、植入微信-看乐心Mambo手环如何打破框架
-
从2014年开始智能手环就成功进入人们的生活,至今已经演变出数据监测、信息推送、心率监测等诸多五花八门的功能,人们选择智能手环并不指望其能够改变身体健康情况,更多的是通过数据来正视自身运动情况和身体健...
- 华专网络:如何零基础制作一个网站出来?
-
#如何零基础制作一个网站出来?#你是不是觉得网站建设很复杂,觉得自己是小白,需求不明确、流程搞不懂、怕被外包公司坑……这些问题我都懂!今天华专网络就用大白话给你捋清楚建站的全流程,让你轻松get网站制...
- WAIC2024丨明日上午9点,不见不散!共同探讨智能社会与全球治理框架
-
大咖云集,硕果闪耀WAIC2024世界人工智能大会智能社会论坛将于7月5日9:00-12:00与你相约直播间WAIC2024上海杨浦同济大学哔哩哔哩多平台同步直播探讨智能社会与全球治理框架WAIC...
- 约基奇:森林狼换来戈贝尔时大家都在嘲笑 他们的阵容框架很不错
-
直播吧5月4日讯西部季后赛半决赛,掘金将迎战森林狼,约基奇赛前接受采访。约基奇说道:“当蒂姆-康纳利(森林狼总经理、前掘金总经理&曾选中约基奇)做了那笔交易(换来戈贝尔)时,每个人都在嘲笑他...
- 视频号带货为什么一个流量都没有?顶级分析框架送给你
-
视频号带货为什么一个流量都没有?遇到问题,一定是步步来分析内容,视频号带货一个流量都没有,用另外一个意思来讲,就可以说是零播放。为什么视频号带货一个流量都没有?跟你说再多,都不如来个分析框架。1、是否...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)