• 【新手向】使用nodejs抓取百度贴吧内容


    参考教程:https://github.com/alsotang/node-lessons 1~5节

    1. 通过superagent抓取页面内容

    superagent
        .get('http://www.cnblogs.com/wenruo/')
        .end(function(err, res) {
            if (err) {
                reject(err)
            } else {
                console.log(res.text)
            }
        })

    OK 这样就获得了一份HTML代码。

    因为获取HTML是异步的,所以我们封装一个函数,返回一个Promise。

    // 获取页面html
    function getHTML(url) {
        return new Promise(function(resolve, reject) {
            superagent.get(url)
                .end(function(err, res) {
                    if (err) {
                        reject(err)
                    } else {
                        resolve(res.text)
                    }
                })
        })
    }

    2. 通过cheerio筛选页面数据

    总不能通过正则一点一点匹配出数据吧,有这样一个库: cheerio( https://github.com/cheeriojs/cheerio ),有了它,我们可以像jQuery一样轻松的从这个HTML代码中获取需要数据。

    现在随便找了一个贴吧的帖子。

    因为我们要获取一个帖子的全部内容,所以要首先要获取帖子的页数,然后分别爬取每一页的内容。通过检查元素找到数据对应的html中的位置,找到所对应的一个类  l_reply_num 然后发现其下有两个span,我们获取第二个的数据,就是总页数。

    代码如下,这里通过  +  将字符串转为数字。

    function getPage(html) {
        let $ = cheerio.load(html)
        return +$('.l_reply_num span').eq(1).text()
    }

    其他的数据,如标题,昵称,层数等,都可以通过同样的方法获取。

    3. 控制并发数量

    贴吧的高楼可以有几百上千页,我们能通过 pages.forEach(page => { getHTML(page) }) 同时发起多个异步请求获取数据,但是,网站有可能会因为你发出的并发连接数太多而当你是在恶意请求,把你的 IP 封掉。

    这时我们可以通过 async ( https://github.com/caolan/async ) 来实现控制并发的数量,使用方法也很简单:

    var async = require("async")
    
    async.mapLimit(urls, 5, function(url, callback) {
        const response = fetch(url)
        callback(response.body)
    }, (err, results) => {
        if (err) throw err
        // results is now an array of the response bodies
        console.log(results)
    })

    通过遍历数组,分别对其中的每一项发起请求,5为控制的并发数量。results是callback中返回数据的集合。

    当然上面的代码假设fetch是同步函数了,否则callback应该放在回调函数里面。

    4. 结果保存到文件

    得到的数据很大,总不能在控制台看,一定要放到文件里。

    function writeFile(filename, content, cb) {
        fs.writeFile(filename, content, function(err) {
            if (err) {
              return console.error(err);
            }
            cb && cb()
        })
    }

    包含三个参数,文件名,存储内容和回调函数。

    整体代码如下:

    let superagent = require('superagent')
    let cheerio = require('cheerio')
    let async = require('async')
    let fs = require('fs')
    
    // 获取页面html
    function getHTML(url) {
        return new Promise(function(resolve, reject) {
            superagent.get(url)
                .end(function(err, res) {
                    if (err) {
                        reject(err)
                    } else {
                        resolve(res.text)
                    }
                })
        })
    }
    
    // 获取帖子页数
    function getPage(html) {
        let $ = cheerio.load(html)
        return +$('.l_reply_num span').eq(1).text()
    }
    
    // 获取帖子标题
    function getTitle(html) {
        let $ = cheerio.load(html)
        return $('.core_title_txt').text()
    }
    
    // 获取帖子一页内容
    function getOnePage(url) {
        return getHTML(url).then(html => {
            let result = []
            let $ = cheerio.load(html)
            $('#j_p_postlist .l_post').each(function(idx, element) {
                let $element = $(element)
                let name = $element.find('.d_name a').text()
                let content = $element.find('.d_post_content').text()
                let floor = $element.find('.tail-info').eq($element.find('.tail-info').length-2).text()
                let time = $element.find('.tail-info').eq($element.find('.tail-info').length-1).text()
    
                name = name.replace(/[s
    	
    ]/g, '')
                content = content.replace(/[s
    	
    ]/g, '')
                if (floor) {
                  result.push(`${floor}(${name}/${time})
    ${content}
    
    `)
                }
            })
            return result.join('')
        }, err => {
            console.error(err)
        })
    }
    
    // 将内容写入到文件
    function writeFile(filename, content, cb) {
        fs.writeFile(filename, content, function(err) {
            if (err) {
              return console.error(err);
            }
            cb && cb()
        })
    }
    
    function getContent(url) {
        console.log('抓取中...')
        // 帖子后面可能会加 只看楼主 和 页码 选项 这里只添加只看楼主选项 将页码项删除
        let hasSeeLZ = false
        if (url.includes('?')) {
            let search = url.split('?')[1].split('&')
            url = url.split('?')[0]
            for (let query of search) {
                if (query.includes('see_lz')) {
                    hasSeeLZ = true
                    url = url + '?' + query
                    break
                }
            }
        }
        // 开始抓取数据
        getHTML(url).then(html => {
            let page = getPage(html)
            let title = getTitle(html) + (hasSeeLZ ? ' -- [只看楼主]' : '')
    
            // 控制最大并发为 5
            async.mapLimit([...new Array(page).keys()], 5, function(idx, callback) {
                let pageUrl = url + (hasSeeLZ ? '&' : '?') + 'pn=' + (idx+1)
                getOnePage(pageUrl).then(res => {
                    callback(null, res)
                })
            }, function(err, res) {
                if (err) {
                    return console.error(err)
                }
                writeFile('result.txt', title + '
    
    ' + res.join(''), () => { console.log('抓取完成!') })
            })
        })
    }
    
    let queryUrl = 'https://tieba.baidu.com/p/3905448690?see_lz=1'
    getContent(queryUrl)

    效果展示(真的是随便找的贴 内容没看过……):

    原贴内容:

    抓取结果:

  • 相关阅读:
    shp转geojson
    Vue如何使用$refs
    Cesium加载轨迹
    Nodejs调用Python函数时传递参数
    JavaScript字符串截取:截取'final:'之后的字符串,然后再按照“,”分割。。
    Cesium平移操作
    架空输电线路障碍物巡检的无人机低空摄影测量方法研究
    cesium沿着路线飞行
    业界常用四种命名法则
    电脑优化
  • 原文地址:https://www.cnblogs.com/wenruo/p/9499801.html
Copyright © 2020-2023  润新知