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

不愧本心

故事背景

今天本来是想写一篇webpack的博客的,然后呢由于担心拉下或者讲不清楚一些细节,本着负责任的原则,想在网上复习一下再写

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

image.png

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

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

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

这么多网页全部手动保存是比较墨迹的,显然写一个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渲染到页面上的,比如说我打开一个百度的首页

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,如有不足之处,请斧正

阅读 2.2k

前端笔记
坚持分享,共同进步

急需100个bug续命

469 声望
12 粉丝
0 条评论
你知道吗?

急需100个bug续命

469 声望
12 粉丝
宣传栏