欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Node 实现爬虫 ?

程序员文章站 2022-05-30 12:16:14
...

前言:


  • 什么是爬虫 ?

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

  • 爬虫一般步骤 ?

目标:确定你想要获取的数据

  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 库 去操作。它的本质原理,就是模拟浏览器去请求页面,然后在进行数据的爬取,具体使用细节可以去查看对应的官方文档。