• nodejs爬虫--抓取CSDN某用户全部文章


    最近正在学习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)的全部文章了。

    完整代码

    参考文章

  • 相关阅读:
    Windows PowerShell 2.0之进程管理
    PowerShell 2.0远程管理之交互式远程线程
    PowerShell 2.0解析、格式化及显示远程输出
    PowerShell 2.0语言远程管理之理解线程配置
    PowerShell 2.0远程管理之隐式远程管理
    PowerShell 2.0如何将远程线程保存在本地
    Windows PowerShell 2.0之服务管理
    PowerShell 2.0远程管理开发使用CredSSP处理多跳授权
    通过PowerShell操作事件日志
    (译)Silverlight教程第七部分: 使用控件模板定制控件的观感
  • 原文地址:https://www.cnblogs.com/xueyubao/p/node-spider-csdn.html
Copyright © 2020-2023  润新知