標籤:閱讀 base use 對象 pack [] repo ble span
背景:女票參加了一個簡書的寫作訓練營,並擔任某個小班的班長,每個周末需要統計每個學員上一周寫了多少篇文章以及寫了哪些文章等資料。為了討女票開心,於是就答應給她寫一個簡書的爬蟲,用於統計資料,畢竟咋是搞這行的,能用程式解決的就堅決就不去做那種重複的體力活。
下面,咋一起來進入這個爬蟲的書寫
一、前期分析:
首先我們來分析一下我們要怎麼爬,:
第一步:
我們需要進入到每個學員的使用者中心,類似於這種連結: http://www.jianshu.com/u/eeb221fb7dac
這個只能由女票幫我們收集好她們班每個學員的使用者中心的連結。有了這種使用者中心的連結後,我們第一步要爬的就是這個使用者中心
第二步:
爬取使用者中心,我們需要擷取到某些資料。如:我們統計的是上一周資料,所以需要某個時間段內,文章詳情的連結集合、 使用者名稱。
這些資料如何擷取呢?如所示: 擷取使用者名稱,我們需要 $(‘.nickname’) 這裡的文本即可。擷取某個時間段的文章詳情連結,我們需要遍曆 $(‘.note-list li’)中的 $(‘.time’) 和 $(‘.titile’)
第三步:
爬取第二步擷取到的文章詳情連結,爬取文章詳情裡面的內容,文章詳情類似的連結為:http://www.jianshu.com/p/aeaa1f2a0196
在文章詳情頁中,擷取我們需要的資料,如: 標題,字數,閱讀數等等。 如所示
第四步:
將擷取的資料產生excel表,這樣女票才會更加的崇拜你,哇,斯國一。
二、前期準備:
經常上面的分析,下面我們來準備爬蟲需要的工具:
cheerio: 讓你可以像使用jquery一樣操作爬取回來的頁面
superagent-charset: 解決爬回來的頁面編碼問題
superagent: 用於發起請求
async: 用於非同步流程和並發控制
ejsexcel: 用於產生excel表格
express、 node >=6.0.0
這些模組具體用法,可以在這裡查,https://www.npmjs.com ,一些用法下面會說到
三、開始爬蟲
1.我們將所有學員的簡書使用者中心連結放在設定檔 config.js中,並定義了產生的excel表存放路徑:
const path = require(‘path‘);module.exports = { excelFile: { path: path.join(__dirname, ‘public/excel/‘) }, data: [ {name:"糕小糕",url:"http://www.jianshu.com/u/eeb221fb7dac"}, ]}
由於要保護別人的資訊,所以你可以去簡書隨便找其他人的使用者中心,造更多的資料
2.我們先定義一些全域變數,如: 基礎的連結,當前並發數,抓取錯誤的連結集合
let _baseUrl = "http://www.jianshu.com", _currentCount = 0, _errorUrls = [];
3.封裝一些函數:
// 封裝 superagent 函數const fetchUrl = (url,callback) => { let fetchStart = new Date().getTime(); superagent .get(url) .charset(‘utf-8‘) .end((err, ssres) => { if(err) { _errorUrls.push(url); console.log(‘抓取【‘+ url +‘】出錯‘); return false; } let spendTime = new Date().getTime() - fetchStart; console.log(‘抓取:‘+ url +‘成功,耗時:‘+ spendTime +‘毫秒,現在並發數為:‘+ _currentCount ); _currentCount--; callback(ssres.text); }); } // 數組去重const removeSame = (arr) => { const newArr = []; const obj = {}; arr.forEach((item) => { if(!obj[item.title]) { newArr.push(item); obj[item.title] = item.title; } }); return newArr;}
4.開始爬取使用者中心,擷取某個時間段的文章詳情連結
// 爬取使用者中心,擷取某個時間段文章詳情連結const crawlUserCenter = (res,startTime,endTime) => { //startTime,endTime來自於使用者的ajax請求時間 const centerUrlArr = config.data; async.mapLimit(centerUrlArr, 5, (elem, callback) => { _currentCount++; fetchUrl(elem.url, (html) => { const $ = cheerio.load(html); const detailUrlArr = getDetailUrlCollections($,startTime,endTime); callback(null,detailUrlArr); //callback是必須的 }); }, (err, detailUrlArr) => { // 並髮結束後的結果 ,需要由 [[abc,def],[hij,xxx]] => [abc,def,hij,xxx] _currentCount = 0; crawArticleDetail(detailUrlArr,res); return false; }); }
在這裡,使用者中心的連結來自於 confg檔案,我們使用 async.mapLimit來對我們的抓取做了一個並發控制,並發數最大為5。
async.mapLimit用法為: mapLimit(arr, limit, iterator, callback);
arr: 數組
limit: 並發數
iterator: 迭代器(處理函數), 這裡指爬取一次使用者中心,它裡面 callback必須執行 ,將本次執行結果儲存起來。
callback: 執行完成之後的回調。 所有的使用者中心抓取完,抓取到的總結果返回這個回調裡面。
從使用者中心擷取某個時間段的文章詳情集合:
// 擷取某個時間段文章連結集合const getDetailUrlCollections = ($,startTime,endTime) => { let articleList = $(‘#list-container .note-list li‘), detailUrlCollections = []; for(let i = 0,len = articleList.length; i < len; i++) { let crateAt = articleList.eq(i).find(‘.author .time‘).attr(‘data-shared-at‘); let createTime = new Date(crateAt).getTime(); if(createTime >= startTime && createTime <= endTime) { let articleUrl = articleList.eq(i).find(‘.title‘).attr(‘href‘); let url = _baseUrl + articleUrl; detailUrlCollections.push(url); } } return detailUrlCollections;}
5.從第4步中,我們擷取到了所有的文章詳情連結,所以,下面我們來爬取文章詳情裡面的內容,做法跟第4步差不多
// 爬取文章詳情const crawArticleDetail = (detailUrls,res) => { const detailUrlArr = spreadDetailUrl(detailUrls); async.mapLimit(detailUrlArr, 5, (url, callback) => { _currentCount ++; fetchUrl(url, (html) => { const $ = cheerio.load(html,{decodeEntities: false}); const data = { title: $(‘.article .title‘).html(), wordage: $(‘.article .wordage‘).html(), publishTime: $(‘.article .publish-time‘).html(), author: $(‘.author .name a‘).html() }; callback(null,data); }); }, (err, resData) => { let result = removeSame(resData); const sumUpData = sumUpResult(result); res.json({ data: result, sumUpData: sumUpData }); createExcel(result,sumUpData); console.info(‘抓取資料完畢,一共抓取了:‘+ result.length +‘篇文章,其中,錯誤數為:‘ + _errorUrls.length +‘條‘); if(_errorUrls.length > 0) { console.info(‘錯誤的url為:‘ + _errorUrls.join(‘,‘)); } return false; });}// [[abc,def],[hij,xxx]] => [abc,def,hij,xxx]const spreadDetailUrl= (urls) => { const urlCollections = []; urls.forEach((item) => { item.forEach((url) => { urlCollections.push(url); }) }); return urlCollections;}
從文章詳情裡,我們擷取了標題,字數,發布時間,當然,你可以擷取該頁面你想要的資訊,我這裡不需要太多,這些就夠了。這裡擷取到的資料是有重複的,所以要做數組去重處理
6.至此,我們需要爬取的資料都擷取到了,最後一步,我們將其產生excel表格
用node產生excel表,找了一圈,發覺 ejsexcel 這個架構評價比較好。點擊查看:https://www.npmjs.com/package/ejsexcel
它的使用方法為: 我們先需要有一個excel表模板,然後在改模板裡面,可以使用ejs文法,來按照我們的意思產生表格
// 產生excel表const createExcel = (dataArr,sumUpData) => { const exlBuf = fs.readFileSync(config.excelFile.path + "/report.xlsx"); //資料來源 const data = [ [{"table_name":"7班簡書統計表","date": formatTime()}], dataArr, sumUpData ]; //用資料來源(對象)data渲染Excel模板 ejsExcel.renderExcel(exlBuf, data) .then(function(exlBuf2) { fs.writeFileSync(config.excelFile.path + "/report2.xlsx", exlBuf2); console.log("產生excel表成功"); }).catch(function(err) { console.error(‘產生excel表失敗‘); });}
先讀取我們的模板,然後再將我們的資料寫入到模板中,產生新的excel表格
excel模板
7.由於抓取的時間是不固定的,所以我們將整個抓取的過程弄成由前端ajax請求的形式,傳遞 startTime 和 endTime這兩個資料,前端介面比較簡單
前端介面
產生的excel表格圖:
前端介面
抓取的過程如下:
至此,我們的簡書爬蟲完成了,比較簡單。
注意點:
由於只能抓取使用者中心中的第一頁資料,所以時間的選擇上,最好選擇上一周時間內。而且excel表格產生時,要把excel關了,不然會導致產生excel表不成功。
代碼已放在github中,歡迎使用(希望簡書不會把我拉黑): https://github.com/xianyulaodi/jianshu_spider
對了,這個爬蟲的名字叫做: 大黃蜂
http://www.aibbt.com/a/19374.html
Python簡書爬蟲