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

前端之家收集整理的这篇文章主要介绍了【新手向】使用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(  ),有了它,我们可以像jQuery一样轻松的从这个HTML代码中获取需要数据。

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

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

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

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

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

3. 控制并发数量

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

这时我们可以通过 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,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,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\r\t\n]/g,'')
        content = content.replace(/[\s\r\t\n]/g,'')
        if (floor) {
          result.push(`${floor}(${name}/${time})\n${content}\n\n`)
        }
    })
    return result.join('')
},err => {
    console.error(err)
})

}

// 将内容写入到文件
function writeFile(filename,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()],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 + '\n\n' + res.join(''),() => { console.log('抓取完成!') })
    })
})

}

let queryUrl = 'https://tieba.baidu.com/p/3905448690?see_lz=1'
getContent(queryUrl)

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

原贴内容

抓取结果:

猜你在找的Node.js相关文章