资讯专栏INFORMATION COLUMN

手把手教你如何用Crawlab构建技术文章聚合平台(一)

Jeffrrey / 2984人阅读

摘要:本文将介绍如何使用和抓取主流的技术博客文章,然后用搭建一个小型的技术文章聚合平台。是谷歌开源的基于和的自动化测试工具,可以很方便的让程序模拟用户的操作,对浏览器进行程序化控制。相对于,是新的开源项目,而且是谷歌开发,可以使用很多新的特性。

背景

说到爬虫,大多数程序员想到的是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 code1表示该任务失败了,这样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_COLLECTIONCRAWLAB_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做一个简单的技术文章聚合网站。能看到这里的都是有耐心的好同学,赞一个。

Github: tikazyq/crawlab

如果感觉Crawlab还不错的话,请加作者微信拉入开发交流群,大家一起交流关于Crawlab的使用和开发。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/43387.html

相关文章

  • 把手你如何用Crawlab构建技术文章聚合平台(二)

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

    zhunjiee 评论0 收藏0
  • 把手你如何用Crawlab构建技术文章聚合平台()

    摘要:本文将介绍如何使用和抓取主流的技术博客文章,然后用搭建一个小型的技术文章聚合平台。是谷歌开源的基于和的自动化测试工具,可以很方便的让程序模拟用户的操作,对浏览器进行程序化控制。相对于,是新的开源项目,而且是谷歌开发,可以使用很多新的特性。 背景 说到爬虫,大多数程序员想到的是scrapy这样受人欢迎的框架。scrapy的确不错,而且有很强大的生态圈,有gerapy等优秀的可视化界面。但...

    LinkedME2016 评论0 收藏0
  • 爬虫平台Crawlab v0.2发布

    摘要:是一个专注于爬虫的集成了爬虫管理任务调度任务监控数据分析等模块的分布式爬虫管理平台,非常适合对爬虫管理爬虫工程化有要求的开发者及企业。从目前开源的框架来看,大部分爬虫平台是以为核心,因此只能支持框架的爬虫,而不仅支持,还支持其他框架的爬虫。 showImg(https://segmentfault.com/img/remote/1460000019143107?w=2559&h=112...

    yiliang 评论0 收藏0
  • [爬虫手记] 我是如何在3分钟内开发完个爬虫的

    摘要:前言开发爬虫是一件有趣的事情。的可配置爬虫是基于的,因此天生是支持并发的。遵守协议这个默认是开启的。的可配置爬虫降低了爬虫的开发时间,增加了爬虫开发效率,完善了工程化水平,将爬虫工程师从日常的繁琐配置工作中解放出来。 前言 开发爬虫是一件有趣的事情。写一个程序,对感兴趣的目标网站发起HTTP请求,获取HTML,解析HTML,提取数据,将数据保存到数据库或者存为CSV、JSON等格式,再...

    sushi 评论0 收藏0
  • [爬虫手记] 我是如何在3分钟内开发完个爬虫的

    摘要:前言开发爬虫是一件有趣的事情。的可配置爬虫是基于的,因此天生是支持并发的。的可配置爬虫降低了爬虫的开发时间,增加了爬虫开发效率,完善了工程化水平,将爬虫工程师从日常的繁琐配置工作中解放出来。前言 开发爬虫是一件有趣的事情。写一个程序,对感兴趣的目标网站发起HTTP请求,获取HTML,解析HTML,提取数据,将数据保存到数据库或者存为CSV、JSON等格式,再用自己熟悉的语言例如Python对...

    YorkChen 评论0 收藏0

发表评论

0条评论

Jeffrrey

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<