参考教程: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\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, 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 + '\n\n' + res.join(''), () => { console.log('抓取完成!') })
})
})
} let queryUrl = 'https://tieba.baidu.com/p/3905448690?see_lz=1'
getContent(queryUrl)

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

原贴内容:

抓取结果:

最新文章

  1. Redis指南
  2. SQL语句性能测试
  3. 工具mark
  4. iOS nib file owner
  5. 【特别推荐】Node.js 入门教程和学习资源汇总
  6. vim vi 及其相关插件的使用
  7. 从python中copy与deepcopy的区别看python引用
  8. 6.ipv6地址配置
  9. 转载 SharePoint【Site Definition 系列】– 创建Content Type
  10. BTrace系列
  11. windows bat命令编写大全
  12. Kruskal-Wallis Test and Friedman test
  13. js堆栈溢出错误
  14. Cocos2d-x lua游戏开发之安装Lua到mac系统
  15. 2016计蒜之道复赛B题:联想专卖店促销
  16. jmockit学习总结
  17. Hibernate最佳实战
  18. mysql 锁查询
  19. 使用vmware提示无法打开内核设备 \\.\Global\vmx86: 系统找不到指定的文件
  20. KDD Cup 99网络入侵检测数据的分析

热门文章

  1. Windows下Nginx Virtual Host多站点配置详解
  2. AngularJs $rootScope.Scope 作用域操作
  3. Windows 10 Weather App无法正常显示解决方法
  4. yii2 批量插入or更新
  5. WWF3的持续化<第五篇>
  6. HDU 2859 Phalanx (dp)
  7. Hibernate中的一对一关系详解(1)
  8. JDK自己主动拆箱下,三目运算符的潜规则
  9. 在Ubuntu Desktop打开终端的2种方式
  10. Tomcat与Nginx、Apache结合的相关实践
  11. Nginx+Tomcat+Memcached实现会话保持
  12. uiautomatorviewer工具的安装与使用
  13. BZOJ1131[POI2008]Sta——树形DP
  14. Linux CentOS中防火墙的关闭及开启端口
  15. django POST表单的使用
  16. IDEA中上传项目到GIt
  17. 全面认识一下.NET 4.0的缓存功能 (转)
  18. 【Android】Android实现监听返回键,主键(HOME),菜单键
  19. R序列seq
  20. 状态机中的RAM注意的问题--减少扇出的办法