中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么使用NodeJs爬蟲抓取古代典籍

發布時間:2021-11-15 15:07:16 來源:億速云 閱讀:106 作者:iii 欄目:web開發

這篇文章主要講解了“怎么使用NodeJs爬蟲抓取古代典籍”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么使用NodeJs爬蟲抓取古代典籍”吧!

項目實現方案分析

項目是一個典型的多級抓取案例,目前只有三級,即 書籍列表, 書籍項對應的 章節列表,一個章節鏈接對應的內容。 抓取這樣的結構可以采用兩種方式, 一是  直接從外層到內層 內層抓取完以后再執行下一個外層的抓取,  還有一種就是先把外層抓取完成保存到數據庫,然后根據外層抓取到所有內層章節的鏈接,再次保存,然后從數據庫查詢到對應的鏈接單元  對之進行內容抓取。這兩種方案各有利弊,其實兩種方式我都試過, 后者有一個好處,因為對三個層級是分開抓取的,  這樣就能夠更方便,盡可能多的保存到對應章節的相關數據。 可以試想一下 ,如果采用前者 按照正常的邏輯

對一級目錄進行遍歷抓取到對應的二級章節目錄, 再對章節列表進行遍歷 抓取內容,到第三級 內容單元抓取完成 需要保存時,如果需要很多的一級目錄信息,就需要  這些分層的數據之間進行數據傳遞 ,想想其實應該是比較復雜的一件事情。所以分開保存數據 一定程度上避開了不必要的復雜的數據傳遞。

目前我們考慮到 其實我們要抓取到的古文書籍數量并不多,古文書籍大概只有180本囊括了各種經史。其和章節內容本身是一個很小的數據  ,即一個集合里面有180個文檔記錄。  這180本書所有章節抓取下來一共有一萬六千個章節,對應需要訪問一萬六千個頁面爬取到對應的內容。所以選擇第二種應該是合理的。

項目實現

主程有三個方法 bookListInit ,chapterListInit,contentListInit,  分別是抓取書籍目錄,章節列表,書籍內容的方法對外公開暴露的初始化方法。通過async  可以實現對這三個方法的運行流程進行控制,書籍目錄抓取完成將數據保存到數據庫,然后執行結果返回到主程序,如果運行成功  主程序則執行根據書籍列表對章節列表的抓取,同理對書籍內容進行抓取。

項目主入口

/**  * 爬蟲抓取主入口  */ const start = async() => {     let booklistRes = await bookListInit();     if (!booklistRes) {         logger.warn('書籍列表抓取出錯,程序終止...');         return;     }     logger.info('書籍列表抓取成功,現在進行書籍章節抓取...');      let chapterlistRes = await chapterListInit();     if (!chapterlistRes) {         logger.warn('書籍章節列表抓取出錯,程序終止...');         return;     }     logger.info('書籍章節列表抓取成功,現在進行書籍內容抓取...');      let contentListRes = await contentListInit();     if (!contentListRes) {         logger.warn('書籍章節內容抓取出錯,程序終止...');         return;     }     logger.info('書籍內容抓取成功'); } // 開始入口 if (typeof bookListInit === 'function' && typeof chapterListInit === 'function') {     // 開始抓取     start(); }

引入的 bookListInit ,chapterListInit,contentListInit, 三個方法

booklist.js

/**  * 初始化入口  */ const chapterListInit = async() => {     const list = await bookHelper.getBookList(bookListModel);     if (!list) {         logger.error('初始化查詢書籍目錄失敗');     }     logger.info('開始抓取書籍章節列表,書籍目錄共:' + list.length + '條');     let res = await asyncGetChapter(list);     return res; };

chapterlist.js

/**  * 初始化入口  */ const contentListInit = async() => {     //獲取書籍列表     const list = await bookHelper.getBookLi(bookListModel);     if (!list) {         logger.error('初始化查詢書籍目錄失敗');         return;     }     const res = await mapBookList(list);     if (!res) {         logger.error('抓取章節信息,調用 getCurBookSectionList() 進行串行遍歷操作,執行完成回調出錯,錯誤信息已打印,請查看日志!');         return;     }     return res; }

內容抓取的思考

書籍目錄抓取其實邏輯非常簡單,只需要使用async.mapLimit做一個遍歷就可以保存數據了,但是我們在保存內容的時候 簡化的邏輯其實就是 遍歷章節列表  抓取鏈接里的內容。但是實際的情況是鏈接數量多達幾萬 我們從內存占用角度也不能全部保存到一個數組中,然后對其遍歷,所以我們需要對內容抓取進行單元化。

普遍的遍歷方式 是每次查詢一定的數量,來做抓取,這樣缺點是只是以一定數量做分類,數據之間沒有關聯,以批量方式進行插入,如果出錯  則容錯會有一些小問題,而且我們想一本書作為一個集合單獨保存會遇到問題。因此我們采用第二種就是以一個書籍單元進行內容抓取和保存。

這里使用了 async.mapLimit(list, 1, (series, callback) => {})  這個方法來進行遍歷,不可避免的用到了回調,感覺很惡心。async.mapLimit()的第二個參數可以設置同時請求數量。

/*   * 內容抓取步驟:  * ***步得到書籍列表, 通過書籍列表查到一條書籍記錄下 對應的所有章節列表,   * 第二步 對章節列表進行遍歷獲取內容保存到數據庫中   * 第三步 保存完數據后 回到***步 進行下一步書籍的內容抓取和保存  */  /**  * 初始化入口  */ const contentListInit = async() => {     //獲取書籍列表     const list = await bookHelper.getBookList(bookListModel);     if (!list) {         logger.error('初始化查詢書籍目錄失敗');         return;     }     const res = await mapBookList(list);     if (!res) {         logger.error('抓取章節信息,調用 getCurBookSectionList() 進行串行遍歷操作,執行完成回調出錯,錯誤信息已打印,請查看日志!');         return;     }     return res; } /**  * 遍歷書籍目錄下的章節列表  * @param {*} list   */ const mapBookList = (list) => {     return new Promise((resolve, reject) => {         async.mapLimit(list, 1, (series, callback) => {             let doc = series._doc;             getCurBookSectionList(doc, callback);         }, (err, result) => {             if (err) {                 logger.error('書籍目錄抓取異步執行出錯!');                 logger.error(err);                 reject(false);                 return;             }             resolve(true);         })     }) }  /**  * 獲取單本書籍下章節列表 調用章節列表遍歷進行抓取內容  * @param {*} series   * @param {*} callback   */ const getCurBookSectionList = async(series, callback) => {      let num = Math.random() * 1000 + 1000;     await sleep(num);     let key = series.key;     const res = await bookHelper.querySectionList(chapterListModel, {         key: key     });     if (!res) {         logger.error('獲取當前書籍: ' + series.bookName + ' 章節內容失敗,進入下一部書籍內容抓取!');         callback(null, null);         return;     }     //判斷當前數據是否已經存在     const bookItemModel = getModel(key);     const contentLength = await bookHelper.getCollectionLength(bookItemModel, {});     if (contentLength === res.length) {         logger.info('當前書籍:' + series.bookName + '數據庫已經抓取完成,進入下一條數據任務');         callback(null, null);         return;     }     await mapSectionList(res);     callback(null, null); }

數據抓取完了 怎么保存是個問題

這里我們通過key 來給數據做分類,每次按照key來獲取鏈接,進行遍歷,這樣的好處是保存的數據是一個整體,現在思考數據保存的問題

1、可以以整體的方式進行插入

優點 : 速度快 數據庫操作不浪費時間。

缺點 : 有的書籍可能有幾百個章節 也就意味著要先保存幾百個頁面的內容再進行插入,這樣做同樣很消耗內存,有可能造成程序運行不穩定。

2、可以以每一篇文章的形式插入數據庫。

優點 : 頁面抓取即保存的方式 使得數據能夠及時保存,即使后續出錯也不需要重新保存前面的章節,

缺點 : 也很明顯 就是慢 ,仔細想想如果要爬幾萬個頁面 做 幾萬次*N 數據庫的操作 這里還可以做一個緩存器一次性保存一定條數  當條數達到再做保存這樣也是一個不錯的選擇。

/**  * 遍歷單條書籍下所有章節 調用內容抓取方法  * @param {*} list   */ const mapSectionList = (list) => {     return new Promise((resolve, reject) => {         async.mapLimit(list, 1, (series, callback) => {             let doc = series._doc;             getContent(doc, callback)         }, (err, result) => {             if (err) {                 logger.error('書籍目錄抓取異步執行出錯!');                 logger.error(err);                 reject(false);                 return;             }             const bookName = list[0].bookName;             const key = list[0].key;              // 以整體為單元進行保存             saveAllContentToDB(result, bookName, key, resolve);              //以每篇文章作為單元進行保存             // logger.info(bookName + '數據抓取完成,進入下一部書籍抓取函數...');             // resolve(true);          })     }) }

兩者各有利弊,這里我們都做了嘗試。 準備了兩個錯誤保存的集合,errContentModel, errorCollectionModel,在插入出錯時  分別保存信息到對應的集合中,二者任選其一即可。增加集合來保存數據的原因是 便于一次性查看以及后續操作, 不用看日志。

(PS ,其實完全用 errorCollectionModel 這個集合就可以了 ,errContentModel這個集合可以完整保存章節信息)

//保存出錯的數據名稱 const errorSpider = mongoose.Schema({     chapter: String,     section: String,     url: String,     key: String,     bookName: String,     author: String, }) // 保存出錯的數據名稱 只保留key 和 bookName信息 const errorCollection = mongoose.Schema({     key: String,     bookName: String, })

我們將每一條書籍信息的內容 放到一個新的集合中,集合以key來進行命名。

感謝各位的閱讀,以上就是“怎么使用NodeJs爬蟲抓取古代典籍”的內容了,經過本文的學習后,相信大家對怎么使用NodeJs爬蟲抓取古代典籍這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

进贤县| 英山县| 胶州市| 重庆市| 荥经县| 遂平县| 凤台县| 正蓝旗| 林芝县| 土默特左旗| 永川市| 三河市| 长海县| 舒城县| 昭通市| 静宁县| 班玛县| 南投县| 迁西县| 阿拉善左旗| 新昌县| 宣化县| 改则县| 重庆市| 登封市| 广饶县| 灵宝市| 乐清市| 周至县| 泰安市| 江源县| 府谷县| 扬州市| 乌什县| 天长市| 冕宁县| 宝山区| 大方县| 天祝| 巴楚县| 乳山市|