node.js爬蟲-爬取簡書特定作者的所有文章

1. 前言

這篇文章藏在心中已經好一段時日了,遲遲不敢動筆,主要是擔心不知道該如何去組織這樣一篇技術文章。

其實個人感覺技術性的文章是最難寫的,細節往往很難拿捏。有些技術細節沒有解釋到位,害怕讀的人難以理解。反之,有些簡單的東西怕講解太多,增加了不必要的篇章。

授人以魚不如授人以漁

故而,這篇文章我會盡量少貼代碼,多談思考過程。

2. 編碼的緣由

我最近在重新搭建自己的個人網站,想把自己在簡書寫的所有文章數據都導入到那個網站上。

這個時候有些朋友可能會不樂意了:“如果要獲取自己所寫的所有文章為什么要寫代碼?簡書不是已經提供了下載當前用戶所有文章的功能了嗎?“

兄弟說得在理,首先我得聲明簡書確實是一個很棒的寫作平臺,他能夠滿足絕大多數用戶的需求,但是對于我這種“懶”癌晚期的程序員來說卻還是有點不太夠。我有以下幾點考慮

  • 把幾十篇文章一篇篇復制粘貼到個人網站的后臺,我想我會瘋掉的(我現在有68篇文章)。
  • 當簡書的文章需要更新的時候,我不得不再去自己的網站后臺手動更新對應文章,這個工作有點重復了。
  • 簡書下載的文章并沒有提供對應文章的發布時間所屬文集, 字數這些元數據,我并不想手動為每一篇文章設置對應的元數據值。

秉持著

Don't Repeat Yourself

這個原則,讓我們開始這次爬蟲之旅吧。

3. 功能介紹

簡單概括一下爬蟲的功能

  1. 向服務端發送請求獲取作者的所有文章。
  2. 從文章中提取出自己需要的數據(文章內容,標題,發布日期......)
  3. 組織數據,存儲到對應的數據庫中。

本文重點會放在頁面的請求,解析以及數據的組織上,至于如何把數據存進數據庫,不會多加講解。有興趣的可以直接看源碼

4. 技術棧

我最后選擇使用node.js來寫這個爬蟲,畢竟要重新去學習python或者ruby這些服務端語言需要耗費我不少時間。只是這樣當然這樣還不夠。

工欲善其事,必先利其器

為了少寫些代碼我還需要一個較為成熟的爬蟲框架。這里使用的是node-crawler。個人覺得這是一個比較cool的爬蟲框架,而且前端人員用起來定會覺得倍感親切-我們可以用我們最親切的jQuery語法來解析響應返回的頁面。

5. 數據模型

在要爬一樣東西之前,首先我們肯定得確認要爬取的東西是什么。這里為了簡化文章,我把需要爬取的內容定為以下三個字段(源碼里面可不止這幾個字段)。

  • 文章標題
  • 文章發布日期
  • 文章內容(markdown格式)

我只需要想辦法從響應返回的頁面里提取出上面三種數據就可以了。至于最后把數據存到什么數據庫里面,怎么存,那便看個人喜好了。

6. 制定爬蟲策略

(1)基本信息爬取

首先進入對應作者的簡書主頁,類似這個頁面。我們會看到有一堆文章列表,看看能不能提取到我們需要的信息?

好吧,一大堆問題,除了文章標題之外其他內容似乎都難以爬取。不管怎么樣都得去文章詳情頁看看了

看來詳情頁面還算能夠滿足我的需求。故而,我決定基本信息的提取采用如下策略

  • 通過爬蟲爬取用戶首頁的所有文章條目,提取出每一篇文章對應詳情頁的鏈接。
  • 通過爬蟲框架發送請求,分別請求每一篇文章的詳情頁面。
  • 解析詳情頁面的內容,提取出我們需要的信息。

(2)面對滾動加載,該如何獲得所有文章?

在進入特定作者的簡書主頁的時候,我發現其實簡書并沒有加載該作者所有的文章,他只是加載了文章列表的一部分。如果需要獲取更多的文章列表,需要向下滾動,向服務器端請求更多的頁面。

剛開始我是站在前端的立場上去考慮這個問題,真的很蛋疼。我最初的想法是使用一些庫如phantomjs去模擬瀏覽器的行為。我打算模擬瀏覽器滾動行為,等數據加載完成之后再繼續滾動,直到不再往服務端請求數據為止。我就真的這么做了,后來才發現這是個噩夢,這意味著我得做下面的事情:

  • 加載頁面。
  • 模擬瀏覽器滾動行為加載更多文章數據。
  • 監聽請求行為,請求完成之后再繼續滾動。
  • 判斷什么時候滾到最底部。

先不說有沒有程序庫支持上面的功能,以上策略明眼人一看就覺得不靠譜,估計也就只有我傻乎乎地跑去嘗試了。我多次嘗試后發現,要模擬滾動行為都相當困難,而且使用phantomjs的時候開發環境經常會卡死。直覺告訴我或許還有更好的實現方式。當對一個問題百思不得其解的時候,或許可以嘗試跳出原來的思維定勢,從另一個角度去考慮這個問題。問題往往會簡單不少。

后來我從后端的角度上去考慮這個事情

問: 加載更多這個行為的本質是什么?
答: 向服務端發送請求,獲取更多頁面數據。

那我只需要知道頁面滾動的時候瀏覽器向服務端發送請求的url以及對應的參數,我不就可以用爬蟲來迭代發送這個請求,進而達到獲取完整的文章列表的目的了嗎?

OK,馬上去Network看看,它可以看到我們發送的網絡請求。我在Network下清除歷史記錄后滾動頁面,有以下發現。

服務端響應后返回的是渲染過的列表數據。果然跟我預料的一樣。然后我再看看完整的url

原來它是使用了分頁參數page,來向服務端請求分頁數據。我通過代碼來封裝這個過程

var i = 1;
var queue = []

while( i < 10) {
  var uriObject = {
    uri: 'http://www.lxweimin.com/u/a8522ac98584?order_by=shared_at&&page=' + i,
  queue.push(uriObject);
  i ++;
}

這只是代碼片段的一部分,把10個url存進隊列數組里。最終也只能獲取10頁的數據,不過這就有個問題了如果真實數據不到10頁,那該如何處理?
我試著請求第100頁的數據,看看會發生什么。發現簡書的服務端返回了一個302 的狀態碼,然后瀏覽器跳轉到個人動態的頁面去了。

這個狀態碼很有用,我可以針對這個狀態碼判斷我們的請求的頁碼參數page的值有沒有超出指定的頁數。
我可以預設更多的請求,如果請求返回這個狀態碼,則不對請求的數據進行任何處理(因為已經超出頁數的范圍)。反之則對返回的數據進行解析,提取出我們需要的關鍵數據。

當然這種做法相當粗暴,會發送許多不必要的請求。接下來有時間我會對這部分代碼進行優化。

7. 頁面解析

請求發送完了,等服務端響應之后我們便可獲取到我們需要的頁面了。接下來要做的就是對頁面結果進行解析,提取出我們需要的內容。上面也說過了 node-crawler這個爬蟲框架內置了jQuery,這讓我們頁面解析工作變得簡單。

(1)獲取文章詳情頁面的url

先來看看文章列表每個條目的html結構(簡書的程序員也做了注釋)。

我們只需要提取出 ul.note-list里面的每一條li a.title,然后再提取出它們的href屬性對應的值,便是我們需要獲取的url了。這種操作對jQuery來說簡直易如反掌。下面是我的代碼片段,我把所有url提出來后都存儲在articlesLink這個數組里面,供后面的程序使用。

let articlesLink = [];
$('ul.note-list').find('li').each((i, item) => {
  var $article = $(item);
  let link = $article.find('.title').attr('href');
  articlesLink.push(link);
})

(2)從詳情頁面中提取數據

再look look詳情頁面的結構

從頁面結構看,我們可以很簡單地提取出標題,還有發布日期這兩個字段的內容

let title = $article.find('.title').text();
let date = $article.find('.publish-time').text().replace('*', '');

有些更新過的文章,發布日期最后就會有個*號,避免干擾我需要把他們都處理掉。但是文章主體的提取就有一些問題了。

我最后期望得到的是markdown格式的字符串。這點我可以通過 to-markdown這個package把html轉換成markdown。但是現在問題是這個包似乎是無法解析div這個標簽的。我考慮著把文章主體里面的所有div標簽都刪掉,然后把處理過的字符串通過to-markdown轉換成對應的markdown格式的字符串,便可以得到我們期望的數據了。

既然有了jQuery這個神器,實現起來不會很麻煩。不過我還想要刪除含有類名image-caption的標簽-這個是簡書默認設置,有的時候它有點礙事,可以考慮刪掉。

以下是我的代碼片段:

var toMarkdown = require('to-markdown');

// 刪除圖片的標題
let $content = $article.find('.show-content');
$content.find('.image-caption').remove();
$content.find('div').each(function(i, item) {
  var children = $(this).html();
  $(this).replaceWith(children);
})

// 獲取markdown格式的文章

let articleBody = toMarkdown($content.html());

最后,只需要把他們放到對象里面:

let article;
article = {
  title: title,
  date: new Date(date),
  articleBody: articleBody
}

至于如何把上面的數據存入數據庫,方法有很多,考慮到文章篇幅問題,這里就不多做敘述了。個人比較推崇MongoDB。它是一款目前用得最多的非關系型數據庫,靈活性很強。對于構建漸進式的應用,我覺得這是一個比較好的選擇。在經常修改的表結構的情景下,起碼你不用去維護一大堆的migrations文件。

最后入庫結果如下,剛好是68篇文章

結尾

不知不覺這篇文章已經占去了好幾個小時,即便這篇文章我已經去除了不必要的代碼細節,著重對自己的思考過程以及遇到的問題進行了總結,但還是寫了不少字。目測總結能力還有待提高啊~~~~

注意:本文只是在總結一些經驗以及思考過程,github的源代碼也只是起參考作用,并不是即插即用的程序包。您可以根據您的情況來寫出滿足自己需求的爬蟲,相信你能做得比我更好。

Happy Coding and Writing !!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,618評論 25 708
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽默認的外補...
    _Yfling閱讀 13,774評論 1 92
  • 為什么要編寫可維護的javascript? 軟件生命周期80%的成本消耗在了維護上。 幾乎所有的軟件維護者都不是它...
    其心閱讀 635評論 2 6
  • 這是一篇關于如何深度學習的文章,如果不懂深度學習,看問題的能力停留在表面,那我們的事業或者人生也大多只能停留在底層...
    瑜璟閱讀 1,808評論 4 56
  • 這幾天加班多,回家晚,老公也學習到深夜,等到睡覺的時候幾乎都過了半夜,結果就是早上起不來。 七點多,兒子已經醒來,...
    DGbean閱讀 209評論 0 0