<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于Apify+node+react/vue搭建一個有點意思的爬蟲平臺

          共 8413字,需瀏覽 17分鐘

           ·

          2024-07-20 15:38

          前言

          熟悉我的朋友可能會知道,我一向是不寫熱點的。為什么不寫呢?是因為我不關注熱點嗎?其實也不是。有些事件我還是很關注的,也確實有不少想法和觀點。但我一直奉行一個原則,就是:要做有生命力的內容。
          本文介紹的內容來自于筆者之前負責研發(fā)的爬蟲管理平臺, 專門抽象出了一個相對獨立的功能模塊為大家講解如何使用nodejs開發(fā)專屬于自己的爬蟲平臺.文章涵蓋的知識點比較多,包含nodejs爬蟲框架父子進程及其通信reactumi等知識, 筆者會以盡可能簡單的語言向大家一一介紹.

          你將收獲

          • Apify框架介紹和基本使用
          • 如何創(chuàng)建父子進程以及父子進程通信
          • 使用javascript手動實現(xiàn)控制爬蟲最大并發(fā)數(shù)
          • 截取整個網(wǎng)頁圖片的實現(xiàn)方案
          • nodejs第三方庫和模塊的使用
          • 使用umi3 + antd4.0搭建爬蟲前臺界面

          平臺預覽

          上圖所示的就是我們要實現(xiàn)的爬蟲平臺, 我們可以輸入指定網(wǎng)址來抓取該網(wǎng)站下的數(shù)據(jù),并生成整個網(wǎng)頁的快照.在抓取完之后我們可以下載數(shù)據(jù)和圖片.網(wǎng)頁右邊是用戶抓取的記錄,方便二次利用或者備份.

          正文

          在開始文章之前,我們有必要了解爬蟲的一些應用. 我們一般了解的爬蟲, 多用來爬取網(wǎng)頁數(shù)據(jù), 捕獲請求信息, 網(wǎng)頁截圖等,如下圖:

          當然爬蟲的應用遠遠不止如此,我們還可以利用爬蟲庫做自動化測試服務端渲染自動化表單提交測試谷歌擴展程序性能診斷等. 任何語言實現(xiàn)的爬蟲框架原理往往也大同小異, 接下來筆者將介紹基于nodejs實現(xiàn)的爬蟲框架Apify以及用法,并通過一個實際的案例方便大家快速上手爬蟲開發(fā).

          Apify框架介紹和基本使用

          apify是一款用于JavaScript的可伸縮的web爬蟲庫。能通過無頭(headlessChrome 和 Puppeteer 實現(xiàn)數(shù)據(jù)提取和** Web** 自動化作業(yè)的開發(fā)。它提供了管理和自動擴展無頭Chrome / Puppeteer實例池的工具,支持維護目標URL的請求隊列,并可將爬取結果存儲到本地文件系統(tǒng)或云端。

          我們安裝和使用它非常簡單, 官網(wǎng)上也有非常多的實例案例可以參考, 具體安裝使用步驟如下:

          安裝

             
          npm install apify --save
          復制代碼

          使用Apify開始第一個案例

             
          const Apify = require('apify');

          Apify.main(async () => {
          const requestQueue = await Apify.openRequestQueue();
          await requestQueue.addRequest({ url: 'https://www.iana.org/' });
          const pseudoUrls = [new Apify.PseudoUrl('https://www.iana.org/[.*]')];

          const crawler = new Apify.PuppeteerCrawler({
          requestQueue,
          handlePageFunction: async ({ request, page }) => {
          const title = await page.title();
          console.log(`Title of ${request.url}: ${title}`);
          await Apify.utils.enqueueLinks({
          page,
          selector: 'a',
          pseudoUrls,
          requestQueue,
          });
          },
          maxRequestsPerCrawl: 100,
          maxConcurrency: 10,
          });

          await crawler.run();
          });
          復制代碼
          使用node執(zhí)行后可能會出現(xiàn)如下界面:

          程序會自動打開瀏覽器并打開滿足條件的url頁面. 我們還可以使用它提供的cli工具實現(xiàn)更加便捷的爬蟲服務管理等功能,感興趣的朋友可以嘗試一下. apify提供了很多有用的api供開發(fā)者使用, 如果想實現(xiàn)更加復雜的能力,可以研究一下,下圖是官網(wǎng)api截圖:

          筆者要實現(xiàn)的爬蟲主要使用了Apify集成的Puppeteer能力, 如果對Puppeteer不熟悉的可以去官網(wǎng)學習了解, 本文模塊會一一列出項目使用的技術框架的文檔地址.

          如何創(chuàng)建父子進程以及父子進程通信

          我們要想實現(xiàn)一個爬蟲平臺, 要考慮的一個關鍵問題就是爬蟲任務的執(zhí)行時機以及以何種方式執(zhí)行. 因為爬取網(wǎng)頁和截圖需要等網(wǎng)頁全部加載完成之后再處理, 這樣才能保證數(shù)據(jù)的完整性, 所以我們可以認定它為一個耗時任務.

          當我們使用nodejs作為后臺服務器時, 由于nodejs本身是單線程的,所以當爬取請求傳入nodejs時, nodejs不得不等待這個"耗時任務"完成才能進行其他請求的處理, 這樣將會導致頁面其他請求需要等待該任務執(zhí)行結束才能繼續(xù)進行, 所以為了更好的用戶體驗和流暢的響應,我們不得不考慮多進程處理. 好在nodejs設計支持子進程, 我們可以把爬蟲這類耗時任務放入子進程中來處理,當子進程處理完成之后再通知主進程. 整個流程如下圖所示:

          nodejs有3種創(chuàng)建子進程的方式, 這里我們使用fork來處理, 具體實現(xiàn)方式如下:
             
          // child.js
          function computedTotal(arr, cb) {
          // 耗時計算任務
          }

          // 與主進程通信
          // 監(jiān)聽主進程信號
          process.on('message', (msg) => {
          computedTotal(bigDataArr, (flag) => {
          // 向主進程發(fā)送完成信號
          process.send(flag);
          })
          });

          // main.js
          const { fork } = require('child_process');

          app.use(async (ctx, next) => {
          if(ctx.url === '/fetch') {
          const data = ctx.request.body;
          // 通知子進程開始執(zhí)行任務,并傳入數(shù)據(jù)
          const res = await createPromisefork('./child.js', data)
          }

          // 創(chuàng)建異步線程
          function createPromisefork(childUrl, data) {
          // 加載子進程
          const res = fork(childUrl)
          // 通知子進程開始work
          data && res.send(data)
          return new Promise(reslove => {
          res.on('message', f => {
          reslove(f)
          })
          })
          }

          await next()
          })

          復制代碼
          以上是一個實現(xiàn)父子進程通信的簡單案例, 我們的爬蟲服務也會采用該模式來實現(xiàn).

          使用javascript手動實現(xiàn)控制爬蟲最大并發(fā)數(shù)

          以上介紹的是要實現(xiàn)我們的爬蟲應用需要考慮的技術問題, 接下來我們開始正式實現(xiàn)業(yè)務功能, 因為爬蟲任務是在子進程中進行的,所以我們將在子進程代碼中實現(xiàn)我們的爬蟲功能.我們先來整理一下具體業(yè)務需求, 如下圖:

          接下來我會先解決控制爬蟲最大并發(fā)數(shù)這個問題, 之所以要解決這個問題, 是為了考慮爬蟲性能問題, 我們不能一次性讓爬蟲爬取所有的網(wǎng)頁,這樣會開啟很多并行進程來處理, 所以我們需要設計一個節(jié)流裝置,來控制每次并發(fā)的數(shù)量, 當前一次的完成之后再進行下一批的頁面抓取處理. 具體代碼實現(xiàn)如下:

             
          // 異步隊列
          const queue = []
          // 最大并發(fā)數(shù)
          const max_parallel = 6
          // 開始指針
          let start = 0

          for(let i = 0; i < urls.length; i++) {
          // 添加異步隊列
          queue.push(fetchPage(browser, i, urls[i]))
          if(i &&
          (i+1) % max_parallel === 0
          || i === (urls.length - 1)) {
          // 每隔6條執(zhí)行一次, 實現(xiàn)異步分流執(zhí)行, 控制并發(fā)數(shù)
          await Promise.all(queue.slice(start, i+1))
          start = i
          }
          }

          復制代碼

          以上代碼即可實現(xiàn)每次同時抓取6個網(wǎng)頁, 當?shù)谝淮稳蝿斩冀Y束之后才會執(zhí)行下一批任務.代碼中的urls指的是用戶輸入的url集合, fetchPage為抓取頁面的爬蟲邏輯, 筆者將其封裝成了promise.

          如何截取整個網(wǎng)頁快照

          我們都知道puppeteer截取網(wǎng)頁圖片只會截取加載完成的部分,對于一般的靜態(tài)網(wǎng)站來說完全沒有問題, 但是對于頁面內容比較多的內容型或者電商網(wǎng)站, 基本上都采用了按需加載的模式, 所以一般手段截取下來的只是一部分頁面, 或者截取的是圖片還沒加載出來的占位符,如下圖所示:

          所以為了實現(xiàn)截取整個網(wǎng)頁,需要進行人為干預.筆者這里提供一種簡單的實現(xiàn)思路, 可以解決該問題. 核心思路就是利用puppeteer的api手動讓瀏覽器滾動到底部, 每次滾動一屏, 直到頁面的滾動高度不變時則認為滾動到底部.具體實現(xiàn)如下:
             
          // 滾動高度
          let scrollStep = 1080;
          // 最大滾動高度, 防止無限加載的頁面導致長效耗時任務
          let max_height = 30000;
          let m = {prevScroll: -1, curScroll: 0}

          while (m.prevScroll !== m.curScroll && m.curScroll < max_height) {
          // 如果上一次滾動和本次滾動高度一樣, 或者滾動高度大于設置的最高高度, 則停止截取
          m = await page.evaluate((scrollStep) => {
          if (document.scrollingElement) {
          let prevScroll = document.scrollingElement.scrollTop;
          document.scrollingElement.scrollTop = prevScroll + scrollStep;
          let curScroll = document.scrollingElement.scrollTop
          return {prevScroll, curScroll}
          }
          }, scrollStep);

          // 等待3秒后繼續(xù)滾動頁面, 為了讓頁面加載充分
          await sleep(3000);
          }
          // 其他業(yè)務代碼...
          // 截取網(wǎng)頁快照,并設置圖片質量和保存路徑
          const screenshot = await page.screenshot({path: `static/${uid}.jpg`, fullPage: true, quality: 70});

          復制代碼

          爬蟲代碼的其他部分因為不是核心重點,這里不一一舉例, 我已經(jīng)放到github上,大家可以交流研究.

          有關如何提取網(wǎng)頁文本, 也有現(xiàn)成的api可以調用, 大家可以選擇適合自己業(yè)務的api去應用,筆者這里拿puppeteerpage.$eval來舉例:

             
          const txt = await page.$eval('body', el => {
          // el即為dom節(jié)點, 可以對body的子節(jié)點進行提取,分析
          return {...}
          })

          復制代碼

          nodejs第三方庫和模塊的使用

          為了搭建完整的node服務平臺,筆者采用了
          • koa 一款輕量級可擴展node框架
          • glob 使用強大的正則匹配模式遍歷文件
          • koa2-cors 處理訪問跨域問題
          • koa-static 創(chuàng)建靜態(tài)服務目錄
          • koa-body 獲取請求體數(shù)據(jù) 有關如何使用這些模塊實現(xiàn)一個完整的服務端應用, 筆者在代碼里做了詳細的說明, 這里就不一一討論了. 具體代碼如下:
             
          const Koa = require('koa');
          const { resolve } = require('path');
          const staticServer = require('koa-static');
          const koaBody = require('koa-body');
          const cors = require('koa2-cors');
          const logger = require('koa-logger');
          const glob = require('glob');
          const { fork } = require('child_process');

          const app = new Koa();
          // 創(chuàng)建靜態(tài)目錄
          app.use(staticServer(resolve(__dirname, './static')));
          app.use(staticServer(resolve(__dirname, './db')));
          app.use(koaBody());
          app.use(logger());

          const config = {
          imgPath: resolve('./', 'static'),
          txtPath: resolve('./', 'db')
          }

          // 設置跨域
          app.use(cors({
          origin: function (ctx) {
          if (ctx.url.indexOf('fetch') > -1) {
          return '*'; // 允許來自所有域名請求
          }
          return ''; // 這樣就能只允許 http://localhost 這個域名的請求了
          },
          exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
          maxAge: 5, // 該字段可選,用來指定本次預檢請求的有效期,單位為秒
          credentials: true,
          allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
          allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'x-requested-with'],
          }))

          // 創(chuàng)建異步線程
          function createPromisefork(childUrl, data) {
          const res = fork(childUrl)
          data && res.send(data)
          return new Promise(reslove => {
          res.on('message', f => {
          reslove(f)
          })
          })
          }

          app.use(async (ctx, next) => {
          if(ctx.url === '/fetch') {
          const data = ctx.request.body;
          const res = await createPromisefork('./child.js', data)
          // 獲取文件路徑
          const txtUrls = [];
          let reg = /.*?(\d+)\.\w*$/;
          glob.sync(`${config.txtPath}/*.*`).forEach(item => {
          if(reg.test(item)) {
          txtUrls.push(item.replace(reg, '$1'))
          }
          })

          ctx.body = {
          state: res,
          data: txtUrls,
          msg: res ? '抓取完成' : '抓取失敗,原因可能是非法的url或者請求超時或者服務器內部錯誤'
          }
          }
          await next()
          })

          app.listen(80)

          復制代碼

          使用umi3 + antd4.0搭建爬蟲前臺界面

          該爬蟲平臺的前端界面筆者采用umi3+antd4.0開發(fā), 因為antd4.0相比之前版本確實體積和性能都提高了不少, 對于組件來說也做了更合理的拆分. 因為前端頁面實現(xiàn)比較簡單,整個前端代碼使用hooks寫不到200行,這里就不一一介紹了.大家可以在筆者的github上學習研究.

          • github項目地址: 基于Apify+node+react搭建的有點意思的爬蟲平臺

          界面如下:
          大家可以自己克隆本地運行, 也可以基于此開發(fā)屬于自己的爬蟲應用.

          項目使用的技術文檔地址

          • apify 一款用于JavaScript的可伸縮的web爬蟲庫

          • Puppeteer

          • koa -- 基于nodejs平臺的下一代web開發(fā)框架


          最后

          歡迎大家留言反饋,交流真正的 Nodejs + Python 爬蟲技術~

          往期精彩


          瀏覽 142
          1點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          1點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久操视频免费看 | 久久久久久黄色视频 | 韩日一区 | 免费黄色在线观看国产 | 国模私拍一区二区三区 |