Node 实现爬虫 ?

什么是爬虫 ?

通过 模拟浏览器 的 请求,服务器就会对根据我们的请求返回我们想要的数据。将 数据解析出来,并且进行 保存

爬虫一般步骤 ?

  • 目标:确定你想要获取的数据
  1. 确定你想要获取的数据在那个页面(一般详细的数据会在详情页)。

  2. 确定在哪些页面可以链接到这些页面(一般分类列表页面会有详情页的链接数据)。

  3. 寻找页面之间数据之间的规律 。

  • 分析页面:
  1. 获取数据的方式(正则,或者 cheerio 库)。

  2. 数据是通过 ajax 请求的数据,还是 html 自带的数据 。

  3. 如果是通过 ajax 请求的数据,需要分析这个 ajax 请求的链接是什么链接,一般请求的数据都为 json 格式的数据,会比较容易解析 。

  4. 如果数据在 html 里面,就用 cheerio 通过选择器将内容选中 。

  • 编写单个数据获取的案例:(多个循环)
  1. 解析出分类页的链接地址 。

  2. 解析出列表页的链接地址 。

  3. 解析出详情页的链接地址 。

  4. 解析详情页里面想要获取的数据 。

  5. 将数据保存起来(本地文件 / 数据库)。

  • 如果遇到阻碍,进行对反爬虫对抗:
  1. User-Agent 是否是正常浏览器的信息,将请求头设置成跟浏览器一样的内容 。

  2. 因为爬虫爬取速度过快,会导致封号(IP),可以降低速度进行解决。或者可以使用代理进行解决 。

  3. 设置验证码,只有通过人为的验证码后,才将凭证给到你。就需要浏览器的真实操作,可以使用无界浏览器帮助。

请求数据的库 ?

  1. axios 。

  2. request 。

  3. puppetter 无界浏览器(完全模拟浏览器)。


Node 通过正则爬取电影数据 ?

// 0,请求数据方法封装
let request = require('request')
function req(path) {
    return new Promise(function(resolve, reject) {
        request.get(path, function(err, response, body) {
            if (err) {
                reject(err)
            } else {
                resolve({ response, body })
            }
        })
    })
}

// 1,获取起始页的所有分类
let httpUrl = 'https://www.1905.com/vod/list/n_1_t_1/o3p1.html'
async function getClassUrl() {
    // 请求拿到爬取数据
    let { response, body } = await req(httpUrl)
        //console.log(body)
        // 解析正文内容
    let reg = /<span class="search-index-L">类型(.*?)<div class="grid-12x">/igs
    let result = reg.exec(body)[1]
    let reg1 = /<a href="javascript\:void\(0\);" onclick="location\.href='(.*?)';return false;" >(.*?)<\/a>/igs
    var res;
    while (res = reg1.exec(result)) {
        getMovies(res[1], res[2])
    }
}
getClassUrl()

// 2,获取分类里的电影链接
async function getMovies(url, movieType) {
    let { response, body } = await req(url)
    let reg = /<a class="pic-pack-outer" target="_blank" href="(.*?)" .*?><img/igs
    let arrList = []
    var res;
    while (res = reg.exec(body)) {
        arrList.push(res[1])
        getInfo(res[1])
    }
    //console.log(movieType, arrList)
}

// 3,根据电影链接获取电影的详细信息
async function getInfo(url) {
    let { response, body } = await req(url)
    console.log('--', body)
    let reg = /<span id="playerBoxIntroCon" class="active">(.*?)<a/igs
        //console.log('--', reg.exec(body))
    var res;
    let arrList = []
    while (res = reg.exec(body)) {
        arrList.push(res[1])
    }
    //console.log(arrList)
}

Node 通过 cheerio 库爬取表情包 ?

// Node 爬取表情包
// cheerio 是 jquery 核心功能的一个快速灵活而又简单的实现,主要是为了在服务器端需要对DOM元素进行操作的地方
// cheerio 是 node.js 的提取页面模块,为服务器特别定制
// 1,安装 npm i cheerio 
// 2, 导入 require('cheerio)
const axios = require('axios')
const cheerio = require('cheerio')
const fs = require('fs')
const path = require('path')

let httpUrl = 'https://www.doutula.com/article/list/?page=1'
axios.get(httpUrl).then((res) => {
    // cheerio 解析 html
    let $ = cheerio.load(res.data)
    $('#home .col-sm-9>a').each((index,item) => {
        let pageUrl = $(item).attr('href')
        parsePage(pageUrl,index)
    })
})

async function parsePage(url,index){
    let res = await axios.get(url)
    let $ = cheerio.load(res.data)
    $('.pic-content img').each((i,item) => {
        let imgUrl = $(item).attr('src')
        let extName = path.extname(imgUrl)
        let imgPath = './img/pic-'+index+'-'+'M'+'-'+i+extName
        // 创建写入图片流
        let ws =  fs.createWriteStream(imgPath)
        axios.get(imgUrl,{responseType:'stream'}).then((res) => {
           res.data.pipe(ws)
           res.data.on('close',()=>{
               ws.close()
           })
        })
    })
}

Node 通过 cheerio 库爬取音乐 ?  

// cheerio 爬取音乐
// 目标:下载音乐
// 1,获取音乐相关的信息,通过音乐相关的信息获取 mp3 列表

let axios = require('axios')
let cheerio = require('cheerio')
let fs = require('fs')
// 选定的网页路径
let httpUrl = 'https://www.xiami.com/list?page=1&query=%7B%22dataType%22%3A%22recommend%22%7D&scene=main&type=collect'
// 拿到总的页面数据
axios.get(httpUrl).then((res) => {
    // 页面数据解析
    let $ = cheerio.load(res.data)
    let obj = {}
    $('.adaptive-list>.collect-item').each((index,item) => {
        let mp3Url = 'https://ww.xiami.com'+$(item).find('.wrapper>a').attr('href')
        let name = $(item).find('.info>.name>a').text()
        let author = $(item).find('.info>.author>a').text()
        obj.url = mp3Url
        obj.name = name
        obj.author = author
        console.log('数据:',obj)
        // 具体的文件下载保存
        downLoad(obj)
    })
})
// 文件下载,保存到指定目录下操作
function downLoad (obj){
    axios.get(obj.url,{responseType:'stream'}).then(function(res){
        // 写入流方式进行
        let ws = fs.createWriteStream('./mp3/'+obj.name+'.mp3')
        res.data.pipe(ws)
    })
}

Node 对于 puppeteer 库的基础使用 ?

// puppeteer 库的使用(无头浏览器)
let puppeteer = require('puppeteer')
// 打开浏览器
async function test(){
    // puppeteer.launch(options) 实例开启浏览器
    // 可以传入一个options对象,配置为无界面浏览器(性能更高,更快),也可以配置为有界面浏览器(一般用于调试开发)
    let options = {
        // 设置为有界面,为true,为无界面
        headless:false,
        // 设置视窗的宽高
        defaultViewport:{
            width:1200,
            height:800
        }
    }
    let browser = await puppeteer.launch(options)
    // 打开页面,返回新的页面对象
    let page = await browser.newPage()
    // 访问页面
    await page.goto('https://www.dytt8.net/index.htm')
    // 截屏
    await page.screenshot({path:'screenshot.png'})
    // 获取页面内容
    page.$$eval('#menu li a',(elements) => {
        elements.forEach((item,index) => {
            console.log(item.innerHTML)
        })
    })
    page.on('console',(e) => {
        console.log(e)
    })
}
test()

Node 通过 puppeteer 库爬取电子书 ?

let puppeteer = require('puppeteer')
let axios = require('axios')
let url = require('url')
let httpUrl = 'https://sobooks.cc/';

(async function(){
    // 开发测试阶段浏览器配置
let debugOptions = {
    // 设置视窗的宽高
    defaultViewport:{
        width:1200,
        height:800
    },
    // 设置为有界面,如果为true,即为无界面
    headless:false,
    // 设置放慢每个步骤的毫秒数
    slowMo:250
}
// 无头浏览器配置,效率更高(开发完后使用)
let options = {
    headless:true
}
// 开启一个浏览器 
let browser = await puppeteer.launch(debugOptions)

// 目标: 获取https://sobooks.cc/ 所有书名和电子书的链接
// 进入网站,获取整个网站列表页的页数
async function getAllNum(){
    let page = await browser.newPage()
      // 截取谷歌请求
      await page.setRequestInterception(true)
      // 监听请求事件,并对请求进行拦截
      page.on('request',interceptedRequest => {
          // 通过url模块对请求的地址进行解析
          let urlObj = url.parse(interceptedRequest.url())
          if(urlObj.hostname == "googleads.g.doubleclick.net"){
              // 如果是谷歌的广告请求,放弃此次请求
              interceptedRequest.abort()
          }else{
              interceptedRequest.continue()
          }
      })
    await page.goto(httpUrl)
    // 设置选择器,获取总页数
    let pageNum = await page.$eval('.pagination li:last-child span',(element) => {
        let text = element.innerHTML
        text = text.substring(1,text.length-2).trim()
        return text
    })
    page.close()
    return pageNum
}

let pageNum = await getAllNum()

// 获取列表页的所有链接
async function pageList(num){
    let pageListUrl = 'https://sobooks.cc/page/'+num
    let page = await browser.newPage()
      // 截取谷歌请求
      await page.setRequestInterception(true)
      // 监听请求事件,并对请求进行拦截
      page.on('request',interceptedRequest => {
          // 通过url模块对请求的地址进行解析
          let urlObj = url.parse(interceptedRequest.url())
          if(urlObj.hostname == "googleads.g.doubleclick.net"){
              // 如果是谷歌的广告请求,放弃此次请求
              interceptedRequest.abort()
          }else{
              interceptedRequest.continue()
          }
      })
    await page.goto(pageListUrl)
    // $eval 找一个符合条件的元素,$$eval 找所有符合条件的元素
    let arrList = await page.$$eval('.card .card-item .thumb-img>a',(elements) => {
       let arr = []
        elements.forEach((element,index) => {
            var obj = {
                href:element.getAttribute('href'),
                title:element.getAttribute('title')
            }
            arr.push(obj)
        })
        return arr
    })
    page.close()
    // 通过获取的数组的地址和标题去请求书籍的详情页
    arrList.forEach((pageObj,i) => {
        //getPageInfo(pageObj)
    })
    //return arrList
}
let pageArr = await pageList(1)
//console.log('pageArr',pageArr)
// 进入每个电子书的详情页获取下载电子书的网盘地址
async function getPageInfo(pageObj){
    let page = await browser.newPage()
    // 截取谷歌请求
    await page.setRequestInterception(true)
    // 监听请求事件,并对请求进行拦截
    page.on('request',interceptedRequest => {
        // 通过url模块对请求的地址进行解析
        let urlObj = url.parse(interceptedRequest.url())
        if(urlObj.hostname == "googleads.g.doubleclick.net"){
            // 如果是谷歌的广告请求,放弃此次请求
            interceptedRequest.abort()
        }else{
            interceptedRequest.continue()
        }
    })
    await page.goto(pageObj.href)
    //$() 只能获取属性,不能获取文本
    let eleA = await page.$('.dltable tr:nth-child(3) a:last-child')
    let aHref = await eleA.getProperty('href')
    aHref = aHref._remoteObject.value
    console.log('aHref',aHref)
}
getPageInfo({href:"https://sobooks.cc/books/14620.html"})
//
})()

总结:使用 正则,分析好页面结构,基本的数据都能爬取的到,但是使用它爬取数据过程比较比较繁琐。个人比较 推荐 使用 cheerio 库 去做数据的爬取,它用法简单,和 jQuery 用法一致,对于熟悉前端开发的同学而言,基本没有新的学习成本,也更好理解。但是也存在局限性,比如一些网站有较强的反爬虫机制,可以考虑使用 puppeteer 库 去操作。它的本质原理,就是模拟浏览器去请求页面,然后在进行数据的爬取,具体使用细节可以去查看对应的官方文档 。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值