nodejs爬蟲--抓取CSDN某用戶全部文章

来源:https://www.cnblogs.com/xueyubao/archive/2020/02/11/node-spider-csdn.html
-Advertisement-
Play Games

最近正在學習node.js,就像搞一些東西來玩玩,於是這個簡單的爬蟲就誕生了。 ...


最近正在學習node.js,就像搞一些東西來玩玩,於是這個簡單的爬蟲就誕生了。

準備工作

  1. node.js爬蟲肯定要先安裝node.js環境
  2. 創建一個文件夾
  3. 在該文件夾打開命令行,執行npm init初始化項目

正式開始

安裝依賴

  • express 用來搭建一個簡單http伺服器,也可以使用node原生api
  • cheerio 相當於node版的jQuery,用來解析頁面
  • superagent 用來請求目標頁面
  • eventproxy 解決同時處理多個頁面的問題

直接使用npm install express cheerio superagent eventproxy來安裝依賴包,當然你也可以用別的方法。

創建建好目錄

node-spider-csdn
├─ .gitignore 
├─ node_modules 
├─ README.md 
├─ index.js             項目入口
├─ package-lock.json
├─ package.json
└─ routes
  └─ csdn.js            爬蟲主要代碼

創建一個Http伺服器

index.js文件中,實例化一個express對象,啟動一個Http服務

const express = require('express');

const app = express();

app.listen(3000, function() {
    console.log('running in http://127.0.0.1:3000');
});

這樣就啟動了一個簡單的Http本地服務,執行node index.js後通過http://127.0.0.1:3000就可以訪問到這個伺服器。有關Express的更多內容可以參考官方文檔

編寫csdn.js模塊

先引入csdn.js文件並且添加路由

const express = require('express');
const csdn = require('./routes/csdn.js');

const app = express();

app.use(csdn);

app.listen(3000, function() {
    console.log('running in http://127.0.0.1:3000');
});

然後開始編寫csdn.js

整體結構

// 引入需要的第三方包
const cheerio = require('cheerio');
const superagent = require('superagent');
const express = require('express');
const eventproxy = require('eventproxy');

const router = express.Router(); // 掛載路由
const ep = new eventproxy();

router.get('/csdn/:name', function(req, res) {
    const name = req.params.name; // 用戶id
    // 具體實現...
});

// 將router暴露出去
module.exports = router;

分析頁面

整體結構寫好後就要開始分析CSDN用戶文章頁面的HTML了。

隨便找一個人的博客,經過觀察發現:

  • 原創文章的完整url:https://blog.csdn.net/l1028386804/article/list/2?t=1
  • CSDN的文章列表是40篇一頁
  • 分頁控制項是動態生成的,所以無法直接通過HTML解析獲得

然後我們通過開發者工具查看文章列表結構,可以發現:

  • 文章信息都在類名為article-item-box的盒子中
  • id信息在該盒子的data-articleid屬性中

還有一些其他的信息都很容易能查到,比如博主原創文章總數值等,可以在以後需要的時候再過來查看。

獲取所有文章頁面

因為無法直接獲得分頁信息,所以我們通過文章總數 / 每頁文章數來獲取所有的頁面。

首先獲取文章的總數:

/**
 * 獲取總文章數目
 * @param {String} url 頁面路徑
 * @param {Function} callback 回調
 */
let getArticleNum = function (url, callback) {
    superagent.get(url).end(function (err, html) {
        if (err) {
            console.log(`err = ${err}`);
        }
        let $ = cheerio.load(html.text);
        let num = parseInt($('.data-info dl').first().attr('title'));

        callback(num);
    });
};

然後利用簡單的迴圈獲取所有文章頁面:

// ...
router.get('/csdn/:name', function(req, res) {
    const name = req.params.name;
    getArticleNum(`https://blog.csdn.net/${name}`, function (num) {
        let pages = []; // 保存要抓取的頁面

        let pageNum = Math.ceil(num / 40); // 計算一共有多少頁面

        for (let i = 1; i <= pageNum; i++) {
            pages.push(`https://blog.csdn.net/${name}/article/list/${i}?t=1`);
        }
        // ...
    });
});
// ...

我們可以通過console.log()或者res.send()來查看獲取的網址是否正確

遍歷獲取所有頁面的HTML

// ...
router.get('/csdn/:name', function (req, res) {
    const name = req.params.name;

    getArticleNum(`https://blog.csdn.net/${name}`, function (num) {
        let pages = [];
        let articleData = []; // 保存所有文章數據
        
        let pageNum = Math.ceil(num / 40); // 計算一共有多少頁面

        for (let i = 1; i <= pageNum; i++) {
            pages.push(`https://blog.csdn.net/${name}/article/list/${i}?t=1`);
        }

        // 獲取所有頁面的文章信息
        pages.forEach(function (targetUrl) {
            superagent.get(targetUrl).end(function (err, html) {
                if (err) {
                    console.log(`err ${err}`);
                }
                let $ = cheerio.load(html.text);
                
                // 當前頁面的文章列表
                let articlesHtml = $('.article-list .article-item-box');

                // 遍歷當前頁的文章列表
                for (let i = 0; i < articlesHtml.length; i++) {
                    // 解析獲取文章信息
                    // push到articleData中
                    // ...
                }
            });
        });
    });
});
// ...

解析文章信息

因為獲取到的有些文本中空格太多,所以需要用到正則表達式來去除多餘的空格。

cheerio對於Document的操作和jQuery基本一樣,所以有前端基礎的可以很輕鬆上手。

/**
 * 解析html字元串,獲取文章信息
 * @param {String} html 包含文章信息的html
 * @param {Number} index 文章索引
 */
let analysisHtml = function (html, index) {
    return {
        id: html.eq(index).attr('data-articleid'),
        title: html.eq(index).find('h4 a').text().replace(/\s+/g, '').slice(2),
        link: html.eq(index).find('a').attr('href'),
        abstract: html.eq(index).find('.content a').text().replace(/\s+/g, ''),
        shared_time: html.eq(index).find('.info-box .date').text().replace(/\s+/, ''),
        read_count: html.eq(index).find('.info-box .read-num .num').first().text().replace(/\s+/, ''),
        comment_count: html.eq(index).find('.info-box .read-num .num').last().text().replace(/\s+/, '')
    };
};
// ...
// 遍歷當前頁的文章列表
for (let i = 0; i < articlesHtml.length; i++) {
    let article = analysisHtml(articlesHtml, i);
    articleData.push(article);
    // ...
}
// ...

我們已經獲取到所有文章的信息數據,但是因為獲取各個頁面的文章時是併發非同步進行的,所以要同時利用這些數據特殊的方法。

處理併發非同步操作

這裡我使用的是“計數器”eventproxy,還有很多其他的方法都可以解決這個問題。

// ...
pages.forEach(function (targetUrl) {
    superagent.get(targetUrl).end(function (err, html) {
        if (err) {
            console.log(`err ${err}`);
        }
        let $ = cheerio.load(html.text);

        let articlesHtml = $('.article-list .article-item-box');

        for (let i = 0; i < articlesHtml.length; i++) {
            let article = analysisHtml(articlesHtml, i);
            articleData.push(article);

            ep.emit('blogArtc', article); // 計數器
        }
    });
});

// 當所有'blogArtc'完成後,觸發回調
ep.after('blogArtc', num, function (data) {
    res.json({
        status_code: 0,
        data: data
    });
});
// ...

這樣,一個簡單的node爬蟲就寫好了,執行node index.js啟動服務後,在瀏覽器中輸入http://127.0.0.1:3000/csdn/xxxx就可以獲得xxxx(這是id)的全部文章了。

完整代碼

參考文章


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ./mongo1.查看所有資料庫show dbs2.切換資料庫use 資料庫名3.查詢所有集合show collections4.查詢所有文檔db.文檔名.find()db.文檔名.find().pretty() //格式化顯示5.查詢一條,並且增加上查詢條件,例如:db.model.findOne ...
  • 使用jdbc將mysql資料庫中的內容封裝為指定對象的list集合 ...
  • 一、問題來由 ViewPager控制項很大程度上滿足了開發者開發頁面左右移動切換的功能,使用非常方便。但是使用中發現,在刪除或者修改數據的時候,PagerAdapter無法像BaseAdapter那樣僅通過notifyDataSetChanged方法通知刷新View。有人提出一種解決方案:給Viewp ...
  • 一、手風琴效果 <style> *{ margin:0; padding:0; } ul{ width: 960px; height: 300px; margin:100px auto; border:1px solid red; ​ } ul li { list-style: none; widt ...
  • 1.HTML基礎標簽圖片常見代碼形式<img src="圖片路徑地址" alt="屬性名" title="占位符">常見的圖片格式為以下三種:.jpg(圖片有損壓縮,影響畫質)、.png(圖片無損壓縮、容積大、具有透明通道)、.gif(動圖)。圖片路徑地址分為本地圖片和網路圖片,本地圖片中分為絕對路 ...
  • 由於JavaScript 的 replace 只能替換一次,因此另外編寫一個能現替換全部匹配內容方法,代碼如下: /* 把 content 中所有的 searchValue 替換為 replaceValue */ function replaceAll(content,searchValue,rep ...
  • 不是 HTML 標簽。它為瀏覽器提供一項信息(聲明),即 HTML 是用什麼版本編寫的。 HTML是一門超文本標記語言,瀏覽器解析html標記後,展示頁面內容,目前流行HTML5,俗稱H5。靜態網頁的拓展名htm或html新建網頁的步驟:1、新建文本文檔,更改拓展名為htm或html2、編寫網頁的基... ...
  • 一、定寬高 1、絕對定位和負margin值: <section class="absolute"> <div></div> </section> <style> section{ display:block; } section.absolute { width: 100px; height: 10 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...