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

1. 通过superagent抓取页面内容

  1. superagent
  2. .get('http://www.cnblogs.com/wenruo/')
  3. .end(function(err, res) {
  4. if (err) {
  5. reject(err)
  6. } else {
  7. console.log(res.text)
  8. }
  9. })

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

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

  1. // 获取页面html
  2. function getHTML(url) {
  3. return new Promise(function(resolve, reject) {
  4. superagent.get(url)
  5. .end(function(err, res) {
  6. if (err) {
  7. reject(err)
  8. } else {
  9. resolve(res.text)
  10. }
  11. })
  12. })
  13. }

2. 通过cheerio筛选页面数据

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

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

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

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

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

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

3. 控制并发数量

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

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

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

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

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

4. 结果保存到文件

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

  1. function writeFile(filename, content, cb) {
  2. fs.writeFile(filename, content, function(err) {
  3. if (err) {
  4. return console.error(err);
  5. }
  6. cb && cb()
  7. })
  8. }

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

整体代码如下:

  1. let superagent = require('superagent')
  2. let cheerio = require('cheerio')
  3. let async = require('async')
  4. let fs = require('fs')
  5.  
  6. // 获取页面html
  7. function getHTML(url) {
  8. return new Promise(function(resolve, reject) {
  9. superagent.get(url)
  10. .end(function(err, res) {
  11. if (err) {
  12. reject(err)
  13. } else {
  14. resolve(res.text)
  15. }
  16. })
  17. })
  18. }
  19.  
  20. // 获取帖子页数
  21. function getPage(html) {
  22. let $ = cheerio.load(html)
  23. return +$('.l_reply_num span').eq(1).text()
  24. }
  25.  
  26. // 获取帖子标题
  27. function getTitle(html) {
  28. let $ = cheerio.load(html)
  29. return $('.core_title_txt').text()
  30. }
  31.  
  32. // 获取帖子一页内容
  33. function getOnePage(url) {
  34. return getHTML(url).then(html => {
  35. let result = []
  36. let $ = cheerio.load(html)
  37. $('#j_p_postlist .l_post').each(function(idx, element) {
  38. let $element = $(element)
  39. let name = $element.find('.d_name a').text()
  40. let content = $element.find('.d_post_content').text()
  41. let floor = $element.find('.tail-info').eq($element.find('.tail-info').length-2).text()
  42. let time = $element.find('.tail-info').eq($element.find('.tail-info').length-1).text()
  43.  
  44. name = name.replace(/[\s\r\t\n]/g, '')
  45. content = content.replace(/[\s\r\t\n]/g, '')
  46. if (floor) {
  47. result.push(`${floor}(${name}/${time})\n${content}\n\n`)
  48. }
  49. })
  50. return result.join('')
  51. }, err => {
  52. console.error(err)
  53. })
  54. }
  55.  
  56. // 将内容写入到文件
  57. function writeFile(filename, content, cb) {
  58. fs.writeFile(filename, content, function(err) {
  59. if (err) {
  60. return console.error(err);
  61. }
  62. cb && cb()
  63. })
  64. }
  65.  
  66. function getContent(url) {
  67. console.log('抓取中...')
  68. // 帖子后面可能会加 只看楼主 和 页码 选项 这里只添加只看楼主选项 将页码项删除
  69. let hasSeeLZ = false
  70. if (url.includes('?')) {
  71. let search = url.split('?')[1].split('&')
  72. url = url.split('?')[0]
  73. for (let query of search) {
  74. if (query.includes('see_lz')) {
  75. hasSeeLZ = true
  76. url = url + '?' + query
  77. break
  78. }
  79. }
  80. }
  81. // 开始抓取数据
  82. getHTML(url).then(html => {
  83. let page = getPage(html)
  84. let title = getTitle(html) + (hasSeeLZ ? ' -- [只看楼主]' : '')
  85.  
  86. // 控制最大并发为 5
  87. async.mapLimit([...new Array(page).keys()], 5, function(idx, callback) {
  88. let pageUrl = url + (hasSeeLZ ? '&' : '?') + 'pn=' + (idx+1)
  89. getOnePage(pageUrl).then(res => {
  90. callback(null, res)
  91. })
  92. }, function(err, res) {
  93. if (err) {
  94. return console.error(err)
  95. }
  96. writeFile('result.txt', title + '\n\n' + res.join(''), () => { console.log('抓取完成!') })
  97. })
  98. })
  99. }
  100.  
  101. let queryUrl = 'https://tieba.baidu.com/p/3905448690?see_lz=1'
  102. getContent(queryUrl)

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

原贴内容:

抓取结果:

【新手向】使用nodejs抓取百度贴吧内容的更多相关文章

  1. Python3---爬虫---抓取百度贴吧

    前言 该文章主要描述如何抓取百度贴吧内容.当然是简单爬虫实现功能,没有实现输入参数过滤等辅助功能,仅供小白学习. 修改时间:20191219 天象独行 import os,urllib.request ...

  2. PHP网络爬虫实践:抓取百度搜索结果,并分析数据结构

    百度的搜索引擎有反爬虫机制,我先直接用guzzle试试水.代码如下: <?php /** * Created by Benjiemin * Date: 2020/3/5 * Time: 14:5 ...

  3. Python抓取百度百科数据

    前言 本文整理自慕课网<Python开发简单爬虫>,将会记录爬取百度百科"python"词条相关页面的整个过程. 抓取策略 确定目标:确定抓取哪个网站的哪些页面的哪部分 ...

  4. python3 - 通过BeautifulSoup 4抓取百度百科人物相关链接

    导入需要的模块 需要安装BeautifulSoup from urllib.request import urlopen, HTTPError, URLError from bs4 import Be ...

  5. selenium-java web自动化测试工具抓取百度搜索结果实例

    selenium-java web自动化测试工具抓取百度搜索结果实例 这种方式抓百度的搜索关键字结果非常容易抓长尾关键词,根据热门关键词去抓更多内容可以用抓google,百度的这种内容容易给屏蔽,用这 ...

  6. C#.Net使用正则表达式抓取百度百家文章列表

    工作之余,学习了一下正则表达式,鉴于实践是检验真理的唯一标准,于是便写了一个利用正则表达式抓取百度百家文章的例子,具体过程请看下面源码: 一:获取百度百家网页内容 public List<str ...

  7. 用PHP抓取百度贴吧邮箱数据

    注:本程序可能非常适合那些做百度贴吧营销的朋友. 去逛百度贴吧的时候,经常会看到楼主分享一些资源,要求留下邮箱,楼主才给发. 对于一个热门的帖子,留下的邮箱数量是非常多的,楼主需要一个一个的去复制那些 ...

  8. Python爬虫之小试牛刀——使用Python抓取百度街景图像

    之前用.Net做过一些自动化爬虫程序,听大牛们说使用python来写爬虫更便捷,按捺不住抽空试了一把,使用Python抓取百度街景影像. 这两天,武汉迎来了一个德国总理默克尔这位大人物,又刷了一把武汉 ...

  9. python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息,抓取政府网新闻内容

    python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI,采用Python语言编写 ...

随机推荐

  1. Dockerfile制作自定义镜像

    本文介绍最精简的Dockerfile文件构建镜像,Docker启动的时候可以启动一个shell脚本 1.首先编写Dockerfile文件 说明 1.启动的这个shell脚本一定是不退出的,比如服务器的 ...

  2. java将图片传为设定编码值显示(可做刺绣)

    import java.awt.Color; import java.awt.image.BufferedImage;import java.io.File;import java.io.IOExce ...

  3. Java使用quartz实现作业调度

    在spring boot中使用quartz实现作业调度的功能,简单易用. 什么是Quartz? Quartz是Java领域最著名的.功能丰富的.开放源码的作业调度工具,几乎可以在所有的Java应用程序 ...

  4. java39

    String a= "hello.a.java;b.java;hello.java;hello.toha;"; //将每个分号的内容取出来 String[] res=a.split ...

  5. linux shell数组赋值方法(常用)

    http://blog.csdn.net/shaobingj126/article/details/7395161 Bash中,数组变量的赋值有两种方法: (1) name = (value1 ... ...

  6. windows kafka 环境搭建踩坑记

    版本介绍(64位): Windows 10 JDK1.8.0_171 zookeeper-3.4.8/ kafka_2.11-0.10.0.1.tgz 点击链接进行下载 1. JDK安装和环境搭建 自 ...

  7. day 35 线程

    内容回顾 # 互斥锁 #在同一个进程中连续锁多次 #进程的数据共享 #进程之间可以共享数据 #提供共享数据的类是Manager #但是它提供的list|dict 这些数据类型 #针对+= -= *= ...

  8. 通过PRINT过程制作报表

    通过PRINT过程制作报表 PRINT过程是SAS中用于输出数据集内容的最简单常用的过程,它可将选择的观测和字段以简单的矩形表格形式输出. 1.1 制作简单报表 使用PRINT过程最简单的语法形式如下 ...

  9. 关于String类学习的一些笔记(本文参考来自程序员考拉的文章)

    String 类继承自 Object 超类,实现的接口有:Serializable.CharSequence.Comparable<String> 接口,具体如下图: 一.常用的Strin ...

  10. nginx server

    配置nginx 首先apt install nginx 然后安装php apt-get install php7.0-fpm php7.0-mysql php7.0-common php7.0-mbs ...