美文网首页
入坑TypeScript(一):使用TypeScript写nod

入坑TypeScript(一):使用TypeScript写nod

作者: Martain | 来源:发表于2020-04-17 10:46 被阅读0次

    入坑TypeScript(一):使用TypeScript写node脚本爬取小说

    一、为何入坑TypeScript?

    ​ 虽然我不是专业的前端工程师,但是对于TypeScript早有所耳闻,但是对于javascript众多的框架以及第三方库来说,我并没有特别在意(或许这就是专业和业余的差距吧~),我的前端技术栈目前仅仅是 vue.js 以及会点 nodejs 而已,最近的遭遇让我不得不去接触下TypeScript,当然,都跑来写博客了,那要不是非常痛恨ts,要不是就是对ts喜出望外,显然,我就是后者。我如此坚定的入坑原因以下两点:

    • “听说”

      ​ 在群里听到一些前端大佬说牛逼一定的前端或者js(或node)工程师ts是必须会的(也许你们不一定认同,只是我的圈子里面我认为他们说的有道理而已~),甚至有大佬在讨论说TypeScript解决了许多JavaScript的坑, 虽然我平时不是经常写前端,但是前端多少也会一点,于是乎,TypeScript便在我的心里埋下了一颗种子。

    • “企业用”

      ​ 近来在网上无聊逛了几个公司的前端产品,发现了很多公司提供的web版的SDK基本都是以TypeScript写的,或者就是也提供JavaScript版本的但是推荐你用TypeScript版本的;甚至我去下载他们的Demo都是用React+TypeScript写的,让我阅读起来异常难受(React不会也是一部分原因吧 o(╥﹏╥)o),我一直以为TypeScript还只是没有发展、还不够成熟的一套东西而已(估计是我孤弱寡闻吧...)

    于是乎,我认真的逛了逛官网~

    二、我对TypeScript的第一印象

    ​ 从简单的阅读了官网以及找朋友讨论了下之后,我对TypeScript是满怀期待的,吸引我的是它的严谨(变量要显示地指定类型)、支持Class、支持枚举、支持接口。开始我是喜欢Javascript这个动态类型的编程的,觉得没有那么繁杂的定义,不用掌握很多东西都能出一点成果,写了一段时间之后也开始厌恶它比较难维护,相对于现在的我来说,我更注重的是代码的质量,设计的质量,而不是仅仅是为了完成某个功能而已。

    三、开始学吧

    ​ 官网文档简单的看了下,对于现在的我来说,着实是没有耐心慢慢地、细细的学,还是喜欢项目驱动地来边做边学,敲好一个朋友在做他的毕业设计,是一个阅读的app,之前有听他提到过用的是某网站的接口来获取书本资源,于是乎,我觉得来写个小爬虫(虽然我知道可能不足以称之为爬虫~)把书本资源保存到本地吧。

    3.1 前期准备

    • 接口提供地址::link:点击进入
    • 使用到的第三方库:axiosfs
    • 环境:node

    3.2 分析接口

    ​ github上那位大佬提供了三个接口:

    • 获取书本列表:/books

    • 获取书本详情:/book/{id}

    • 获取章节详情:/book/{bookId}/{cid}

      根据这几个接口很容易就能想出如何使用这三个接口爬取数据:

      • 获取书本列表,拿到books
      • 遍历books,拿到bookId去拿书本详情bookDetail
      • 书本详情里面肯定会有章节列表chepters,遍历chepters,拿到chepterId去获取章节详情ChepterDetail

      顺着这个思路就能获取到书本的数据了

    3.3 定义实体

    ​ 根据接口的分析,我们可以抽象出4个实体类,他们是书本实体Book 、书本详情BookDetail、章节Chepter、章节详情ChepterDetail,这里就很好的用上了TypeScript的class的知识点

    • Book Entity

      class Book {
          _id: string;
          title: string;
          author: string;
          shortIntro: string;
          cover: string;
          cat: string;
          followerCount: string;
          zt: string;
          updated: string;
          lastchapter: string; 
      }
      
    • BookDetail Entity

      class BookDetail {
          _id: string;
          title: string;
          author: string;
          shortIntro: string;
          cover: string;
          cat: string;
          followerCount: string;
          zt: string;
          updated: string;
          lastchapter: string;
          wordCount: string;
          retentionRatio: string;
          chapters: Chapter[]
      }
      
    • Chapter Entity

      class Chapter {
          cid: string;
          wordCount: string;
          title: string;
      }
      
    • ChapterDetail Entity

      class ChapterDetail{
          title:string;
          content:string;
          bookDetail:BookDetail;
      }
      

      3.4 封装方法

      ​ 因为js的单线程以及网络请求异步的原因,如果直接调用方法使用网络请求去爬取数据的话很容易就会出问题,这里使用到了asyncawait,达到同步请求的功能。(这里折腾了很久,因为对asyncawait 不是很熟悉,o(╥﹏╥)o)

      ​ 网络请求这里使用的是axios ,因为api比较简单,所以这里将所有的api封装成了一个枚举值,也是TypeScript的一个特性enum :

      import axios from 'axios' 
      const request = axios.create({
          timeout: 1000 * 60
      })
      enum api {
          books = "https://novel.juhe.im/books",
          bookDetail = "https://novel.juhe.im/book/",
          chapterDetail = "https://novel.juhe.im/book/"
      }
      

    ​ 这里我们封装了三个方法,获取书本列表、获取书本详情、获取章节详情。调用关系就像分析接口的步骤那样,这里我们只是加入了对请求数据的保存功能。

    • getChapterDetail 获取章节详情

      async function getChapterDetail(bookTitle: string, bookId: string, chapterId: string) {
          try {
              let res = await request.get(api.chapterDetail + bookId + "/" + chapterId)
              let chapterDetail:ChapterDetail = res.data 
              let csavePath = fileSavePath + bookTitle + "/" + chapterDetail.title + chapterId
              csavePath = csavePath.replace(' ', '')
              let _mkResult = fs.mkdirSync(csavePath, 0o777)
              console.log("\t\t创建目录:" + csavePath + "==>" + _mkResult);
              let fileName = csavePath + "/" + chapterDetail.title + "_" + chapterId + ".json";
              fileName = fileName.replace(' ', '')
              fs.writeFileSync(fileName, JSON.stringify(chapterDetail))
              console.log("\t\t\t保存文件:" + fileName);
              return res
          } catch (error) {
              console.log("getChapterDetail error", bookTitle, chapterId, error);
          }
      }
      
    • getBookDetail 获取书本详情

      async function getBookDetail(book: Book) {
          try {
              let res = await request.get(api.bookDetail + book._id)
              let bookDetail: BookDetail = res.data
              let bookId = bookDetail._id
              let title = bookDetail.title
              let chapters = bookDetail.chapters
              let savePath = fileSavePath + title
              let mkResult = fs.mkdirSync(savePath, 0o777)
              console.log("\t创建目录:" + savePath + "==>" + mkResult);
              fs.writeFileSync(savePath + "/" + title + ".json", JSON.stringify(bookDetail))
              for (const chapter of chapters) {
                  await getChapterDetail(title, bookId, chapter.cid)
              }
              return res
          } catch (error) {
              console.log("getBooDetail errpr", book, error);
          }
      }
      
    • getBooks 获取书本列表

      async function getBooks() {
          try {
              let res = await request.get(api.books, {})
              let books: Book[] = res.data.books
              for (const book of books) {
                  await getBookDetail(book)
              }
              return res
          } catch (error) {
                    console.log(error)
          }
      }
      

      ​ 这里只是对文件做了简单的保存,获取到一个book的话, 通过book的title创建一个以title为名的文件夹,并将bookDetail保存在这个文件夹中同名json文件中。在获取章节详情的时候,会在这个book的文件夹中创建章节名的文件夹,并把章节详情保存在文件夹中的同步json文件中。

      ​ 如图:

      文件夹结构一 文件夹结构二

      这里有个问题就是爬起来还是比较慢的,而且接口文件存在部分问题,比如title为空的情况,以及网络原因导致无法获取的情况,不过用来学习也够了。

    四、完整代码

    import axios from 'axios'
    import * as fs from 'fs'
    const request = axios.create({
        timeout: 1000 * 60
    })
    const fileSavePath = "book/"
    fs.mkdir(fileSavePath, err => {
        console.log(err);
    })
    enum api {
        books = "https://novel.juhe.im/books",
        bookDetail = "https://novel.juhe.im/book/",
        chapterDetail = "https://novel.juhe.im/book/"
    }
    class Book {
        _id: string;
        title: string;
        author: string;
        shortIntro: string;
        cover: string;
        cat: string;
        followerCount: string;
        zt: string;
        updated: string;
        lastchapter: string; 
    }
    
    class BookDetail {
        _id: string;
        title: string;
        author: string;
        shortIntro: string;
        cover: string;
        cat: string;
        followerCount: string;
        zt: string;
        updated: string;
        lastchapter: string;
        wordCount: string;
        retentionRatio: string;
        chapters: Chapter[]
    }
    class Chapter {
        cid: string;
        wordCount: string;
        title: string;
    }
    
    class ChapterDetail{
        title:string;
        content:string;
        bookDetail:BookDetail;
    }
    async function getBookDetail(book: Book) {
        try {
            let res = await request.get(api.bookDetail + book._id)
            let bookDetail: BookDetail = res.data
            let bookId = bookDetail._id
            let title = bookDetail.title
            let chapters = bookDetail.chapters
            let savePath = fileSavePath + title
            let mkResult = fs.mkdirSync(savePath, 0o777)
            console.log("\t创建目录:" + savePath + "==>" + mkResult);
            fs.writeFileSync(savePath + "/" + title + ".json", JSON.stringify(bookDetail))
            // chapters.forEach(chapter=>{
            //     getChapterDetail(title,bookId,chapter.cid)
            // })
            for (const chapter of chapters) {
                await getChapterDetail(title, bookId, chapter.cid)
            }
            return res
        } catch (error) {
            console.log("getBooDetail errpr", book, error);
        }
    }
    async function getChapterDetail(bookTitle: string, bookId: string, chapterId: string) {
        try {
            let res = await request.get(api.chapterDetail + bookId + "/" + chapterId)
            let chapterDetail:ChapterDetail = res.data 
            let csavePath = fileSavePath + bookTitle + "/" + chapterDetail.title + chapterId
            csavePath = csavePath.replace(' ', '')
            let _mkResult = fs.mkdirSync(csavePath, 0o777)
            console.log("\t\t创建目录:" + csavePath + "==>" + _mkResult);
            let fileName = csavePath + "/" + chapterDetail.title + "_" + chapterId + ".json";
            fileName = fileName.replace(' ', '')
            fs.writeFileSync(fileName, JSON.stringify(chapterDetail))
            console.log("\t\t\t保存文件:" + fileName);
            return res
        } catch (error) {
            console.log("getChapterDetail error", bookTitle, chapterId, error);
        }
    } 
    async function getBooks() {
        try {
            let res = await request.get(api.books, {})
            let books: Book[] = res.data.books
            for (const book of books) {
                await getBookDetail(book)
            }
            return res
        } catch (error) {
    
        }
    } 
    let res = getBooks()
    console.log(res); 
    

    五、总结

    5.1 学了如何使用TypeScript编写node脚本

    ​ 因为之前用过nodejs+js来写脚本,所以使用ts来代替js其实也就是照搬照套,区别除了语法之外,就是执行流程上需要使用tsc xxx.ts 编译成xxx.js 之后,再执行node xxx.js,才能正常运行。关于如何使用第三方库的问题,我也是直接使用npm去安装第三方库的。如果要使用node 里面的httpfs 这类的包的话,需要执行npm install @types/node -D 安装相关的库,至于axios的话就直接执行npm install axios 了。

    5.2 async与await的基本用法

    ​ 这两个关键字一般都是配合一起使用的,因为我了解甚少,这里就不给出详细解释以免误人子弟 o(╥﹏╥)o,后面有空了会对这个专门写个文章学习下把,可以先参考阮一峰的教程文章

    5.3 forEach是同步的,但是forEach里面加await是无法同步执行的

    ​ 这个问题也是我在使用await和async的时候遇到的,因为当时对这个不是很了解,我想要的效果是获取书本列表之后,一本一本的去爬,就是爬第一本的时候,要等这本书的所有章节都爬完了再开始爬第二本书,也是这里才遇到这些问题,也是靠大神指点的,后来将forEach 改成了for of

    5.4 下一步计划

    • 学习js的同步异步原理,学会使用asyncawait
    • 学习Promise.all啥的

    也许我永远也追不上他们的脚步,但是我可以超越昨天的自己。

    相关文章

      网友评论

          本文标题:入坑TypeScript(一):使用TypeScript写nod

          本文链接:https://www.haomeiwen.com/subject/yncevhtx.html