故事背景
今天本来是想写一篇webpack的博客的,然后呢由于担心拉下或者讲不清楚一些细节,本着负责任的原则,想在网上复习一下再写
然后我找到了一个webpack的网站,但是正在我看的津津有味的时候,网页突然给我蹦出来一个弹窗
大概是这么个意思,我在控制台删除元素或者试图取消该网站的监听事件都无果,删不干净,总之就是不让你舒舒服服的往下看,不嫌墨迹的话也能看
不是说好的技术无界限的嘛,为啥又要这样。。。,难不成还要买一本?
当发现他其实是有完整文章渲染出来,只是阻拦了往下浏览的时候,我得钱包提醒了我一下白嫖的时候到了
这么多网页全部手动保存是比较墨迹的,显然写一个node爬虫是比较不错的选择
注: 最后由于考虑到爬这种加密的可能不太合适,所以后来找了一个其他的一个公开资源的网站,毕竟博客还是要写的o( ̄▽ ̄)ブ
核心模块
主要用到的模块如下
- cheerio //页面抓取
- axios //请求处理
- fs //文件模块
npm i cheerio axios
const cheerio = require('cheerio') 获取页面内容
const axios = require('axios');
const fs = require('fs');
这里请求的工具选择的axios,因为平时用到的比较多,比较熟悉
fs 操作本地文件,node的一个内置的模块
然后cheerio
cheerio
cheerio是jquery核心功能的一个快速灵活而又简洁的实现,主要是为了用在服务器端需要对DOM进行操作的地方
在node环境里边直接抓dom是比较墨迹的,cheerio这个模块可以用来解析html,和jquery一样。
有了这个就好说了,首先把整个网站的导航拿下来,然后根据网站的导航地址遍历对应的连接,最后根据自己的需求把需要的内容下载到本地就可以了
怎么拿到页面的html
- 我们平时在写业务的时候,都是发送请求到服务端,然后服务端返回给数据,那么html怎么获取
- 回想一下我们打开一个网址之后,浏览器事怎么把html渲染到页面上的,比如说我打开一个百度的首页
- 可以在控制台里边看到,有一个和域名一样的请求,类型是document,打开详情后可以看到里边是baidu的首页,也就是说其实就是一个get请求,只是返回的东西不一样
获取页面导航
上边说了那么多,现在进入正题
// 配置一下请求地址
`axios.defaults.baseURL = 'https://xxxxxx.cn/'`
`axios.get('/').then(res => {console.log(res.data)})`
尝试着请求一下首页,得到结果如下
是我们想要的东西没错
然后接下来 我们按照jquery的方法 找到导航那块html
假如说我们需要的模块在.summary
这个class下边,然后需要获取他里边所有有内容的标题路径的时候,代码如下
/**
* @Date: 2020-04-10 16:11:02
* @information: 获取页面内容
*/
async init() {
let result = await axios.get('/')
let page = result.data
let $ = cheerio.load(page)
let navNode = $('.summary')
// 递归标题和路径
navNode.children().each((navIndex, item) => {
let itemNode = $(item)
let ulNode = itemNode.find('ul')
if (!ulNode.length) return
// 获取所有大标题
let title = itemNode.find('a').first().text().replace(/\s/g, "")
this.urlMap.set(title, new Map())
let contentNode = $(ulNode)
contentNode.children().each((contentIndex, item) => {
let body = $(item).find('a')
let contentMap = this.urlMap.get(title)
contentMap.set(body.text().replace(/\s/g, ""), body.attr('href'))
})
})
}
获取页面内容
在获取完导航的路由之后剩下的就是遍历取内容了,首先创建一个保存文件的文件夹路径
// 创建保存的路径文件夹
let writeUrl = `xxxxxx`
//在创建之前判断一下是不是已经有了这个文件夹
let hasDir = fs.existsSync(writeUrl);
!hasDir && fs.mkdirSync(writeUrl);
然后从刚才获取的导航的map 里边读取路径
```
// 建立子文件夹和内容
this.urlMap.forEach((item, titleIndex) => {
let dirName = `${writeUrl}\\${titleIndex}`
let hasSubDir = fs.existsSync(dirName);
!hasSubDir && fs.mkdirSync(dirName)
// 抓文件并写文件
item.forEach(async (item, index) => {
let result = await axios.get(`/${encodeURI(item)}`)
let page = result.data
let $ = cheerio.load(page)
let contentNode = $('.search-noresults')
fs.writeFile(`${dirName}\\${index}.html`, contentNode, () => {
// console.log(`${index}:写入成功`)
})
})
})
```
把请求到的页面拿到对应dom,直接往html里边一塞,完活,打开看一下
emm 看倒是能看 不过样式实在是太难看了,打开爬取的网站的源代码找到他的资源路径加进去
` contentNode.before('')`
还有图片加载不出来的问题,同理查找所有的img标签,如果有图片的话把src属性的值拿下来取对应的地址下载
```
let imgNodeArr = contentNode.find('img')
if (!imgNodeArr.length) return
imgNodeArr.each(async (i, el) => {
let src = $(el).attr('src')
let imgPath = `/${encodeURI(titleIndex.split("/")[0].replace('第', "").replace('章', ""))}/${encodeURI(src)}`
if ($(el).attr('src').includes('http')) return
// 下载图片存储到本地
let result = await axios.get(`${imgPath}`, { responseType: 'stream' })
let hasImgDir = fs.existsSync(`${dirName}\\img`);
!hasImgDir && fs.mkdirSync(`${dirName}\\img`)
result.data.pipe(fs.createWriteStream(`${dirName}/${src}`));
// console.log(`写入图片成功:${dirName}\\${src}`)
})
```
http地址的图片就不需要下载了,既然他能拿到,那么咱们直接用就好了
这里请求的时候一定要加{ responseType: 'stream' }
,不然的话图片下载回来会打不开格式
`result.data.pipe(fs.createWriteStream(`${dirName}/${src}`));`
把文件流写入到文件里边生成图片,运行一下看看效果
运行结果
ok,至此一个简单的爬虫就完成了,最后放上效果图
喜欢的点个赞把
完事撒花o( ̄▽ ̄)ブ,过几天继续webpack,如有不足之处,请斧正