一个用Promise写的http小爬虫

node学习(一) http小爬虫 crawler.js

一、实现功能

二、效果图

crawler

三、主要流程

根据课程id获取课程信息

因为爬取每个网页的时间是不能确定的,所以使用Promise来异步获取课程信息。

每个Promise对象中发起两个https请求,分别用来获取该课程的学习人数和课程的章节信息的HTML代码。最后将学习人数和原网页代码一并交给resolve()进一步处理。

坑:本来以为可以通过一个爬取原网页HTML代码的请求就可以取到这两个信息,但是原网页中的课程学习人数是通过aJax获取的,无法直接从爬取的原网页HTML代码中筛选出来。最后只能发送一个专门用来获取学习人数…


function getPageAsync(id) {
    
    return new Promise((resolve, reject) => {
       
        let url = baseUrl+id
        console.log('正在爬取' + url +'\n')

        let Lnumber = 0;
        let html = ''
        getNumber(id).then(number => {
            Lnumber = number
        })

        https.get(url, res => {
           
            res.on('data', data => {
                html += data
            })
            res.on('end', () => {
                resolve({html,Lnumber})
            })
        }).on('error', err => {
            console.log(err + ':获取课程信息出错!')
            reject(err)
        })

    })
}

数据整理,从爬取的内容中筛选出有用的信息

使用了cheerio这个包,node中的jQuery。可以很方便的操纵爬取过来的网页源代码。
对爬取的每一个网页的信息进行整理(主要整理出课程名称title、学习人数number、章节信息videos)后,把数据放到courseData对象中,最后return出去。

function filterChapter(page) {
    
    const $ = cheerio.load(page.html)
    const chapters = $('.chapter')
    let title = $('.course-infos h2').text()
    let number = page.Lnumber
 
    let courseData = {
        title,
        number,
        videos:[]
    }

    chapters.each(function () {
        let chapter = $(this)
        let chapterTitle = chapter.find('h3').text()
        let videos = chapter.find('.video').children('li')
        let chapterData = {
            chapterTitle,
            videos: []
        }
        videos.each(function () {
            let video = $(this).find('.J-media-item')
            let videoTitle = video.text().trim().split('(')[0]
            let id = video.attr('href').split('video/')[1]
            chapterData.videos.push({
                title: videoTitle,
                id
            })
        })
        courseData.videos.push(chapterData)
    })
    return courseData
}

输出打印整理后的信息

function printCourseInfo(coursesData) {
    
    coursesData.forEach(courseData => {
        console.log(''+courseData.number +''+'人学过 '+courseData.title+ '\n')
    })
    coursesData.forEach(courseData => {
        
        console.log('\n')
        console.log('----------------------------------------------------------------------------------\n')
        console.log('                           ' + courseData.title + '                               \n')
        console.log('----------------------------------------------------------------------------------\n')
        courseData.videos.forEach(item =>{
            let chapterTitle = item.chapterTitle
            console.log( chapterTitle + '\n')
            item.videos.forEach(video => {
                console.log('【id ' + video.id + '' + video.title)
            })
        })
        
    })
}

整体骨架

将每一个用来爬取网页信息的Promise对象放到fetchCourseArray中,使用Promise.all()方法来执行所有的Promise。 然后将爬取过来的数据进行整理、打印输出。

   let fetchCourseArray = []
    videosIds.forEach(id =>{
        fetchCourseArray.push(getPageAsync(id))
    })

    Promise.all(fetchCourseArray)
            .then((pages) =>{
                let coursesData = []
            
                pages.forEach(page => {
                    let courses = filterChapter(page)
                    coursesData.push(courses) 
                })
                coursesData.sort((a,b) =>{
                    return a.number < b.number
                })
                printCourseInfo(coursesData)
            })

其他

这个爬虫依然是根据慕课网相关教程进击Node.js基础(二)做的。

课程录制的时间比较早了,其中的一些效果已经不能用课程中的方法来实现了,比如获取学习人数。

Scott老师讲的还是比较透彻的,尤其是讲HTTP的时候,把我在计算机网络课上听得云里雾里的地方用很幽默的例子讲明白。

最好玩的是Scott老师喜欢拿慕课网开刀…

完整课程练习代码