从一个白嫖的角度作一个node爬虫

故事背景

今天原本是想写一篇webpack的博客的,而后呢因为担忧拉下或者讲不清楚一些细节,本着负责任的原则,想在网上复习一下再写css

而后我找到了一个webpack的网站,可是正在我看的津津有味的时候,网页忽然给我蹦出来一个弹窗html

image.png

大概是这么个意思,我在控制台删除元素或者试图取消该网站的监听事件都无果,删不干净,总之就是不让你舒舒服服的往下看,不嫌墨迹的话也能看node

不是说好的技术无界限的嘛,为啥又要这样。。。,难不成还要买一本?jquery

当发现他实际上是有完整文章渲染出来,只是阻拦了往下浏览的时候,我得钱包提醒了我一下白嫖的时候到了webpack

这么多网页所有手动保存是比较墨迹的,显然写一个node爬虫是比较不错的选择ios

注: 最后因为考虑到爬这种加密的可能不太合适,因此后来找了一个其余的一个公开资源的网站,毕竟博客仍是要写的o( ̄▽ ̄)ブ

核心模块

主要用到的模块以下web

  • cheerio //页面抓取
  • axios //请求处理
  • fs //文件模块
npm i cheerio axios
const cheerio = require('cheerio') 获取页面内容
const axios = require('axios'); 
const fs = require('fs');

这里请求的工具选择的axios,由于平时用到的比较多,比较熟悉npm

fs 操做本地文件,node的一个内置的模块axios

而后cheerio浏览器

cheerio

cheerio是jquery核心功能的一个快速灵活而又简洁的实现,主要是为了用在服务器端须要对DOM进行操做的地方

在node环境里边直接抓dom是比较墨迹的,cheerio这个模块能够用来解析html,和jquery同样。

有了这个就好说了,首先把整个网站的导航拿下来,而后根据网站的导航地址遍历对应的链接,最后根据本身的需求把须要的内容下载到本地就能够了

怎么拿到页面的html

  • 咱们平时在写业务的时候,都是发送请求到服务端,而后服务端返回给数据,那么html怎么获取
  • 回想一下咱们打开一个网址以后,浏览器事怎么把html渲染到页面上的,好比说我打开一个百度的首页

image.png

  • 能够在控制台里边看到,有一个和域名同样的请求,类型是document,打开详情后能够看到里边是baidu的首页,也就是说其实就是一个get请求,只是返回的东西不同

获取页面导航

上边说了那么多,如今进入正题

// 配置一下请求地址
`axios.defaults.baseURL = 'https://xxxxxx.cn/'`

`axios.get('/').then(res => {console.log(res.data)})`

尝试着请求一下首页,获得结果以下

image.png

是咱们想要的东西没错

而后接下来 咱们按照jquery的方法 找到导航那块html

image.png

假如说咱们须要的模块在.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'))
        })
    })
}

打印一下结果
image.png

获取页面内容

在获取完导航的路由以后剩下的就是遍历取内容了,首先建立一个保存文件的文件夹路径

// 建立保存的路径文件夹
    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('<link rel="stylesheet" href="https://xxxxxx/style.css"></link>')`

还有图片加载不出来的问题,同理查找全部的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}`));`

把文件流写入到文件里边生成图片,运行一下看看效果

image.png

运行结果

ok,至此一个简单的爬虫就完成了,最后放上效果图

image.pngimage.pngimage.pngimage.png

喜欢的点个赞把

完事撒花o( ̄▽ ̄)ブ,过几天继续webpack,若有不足之处,请斧正

相关文章
相关标签/搜索