node爬虫、前端爬虫

爬虫的常见方式
  1. 通过模拟接口调用 获取返回数据方式
  2. 通过对页面标签获取 页面上面的数据值
第一种方式

相对来说爬取简单、快捷,但是对于处理过反爬虫网站来说不能很好的获取到,
这里我们以掘金社区列表为例子:

首先我们需要对接口的分析
在这里插入图片描述
然后建立接口连接调用接口,这里有些需要注意的是最好按照当前请求头配置 啥也不说直接上代码

	
const fs = require('fs')
const axios = require("axios");
const url = 'https://web-api.juejin.im/query'
const param = {
  extensions: {
    query: {
      id: '21207e9ddb1de777adeaca7a2fb38030'
    }
  },
  operationName: "",
  query: "",
  variables: {
    first: 20,
    after: "",
    order: "POPULAR"
  }
}
async function testPost() {
  let response = await axios({
    method: "POST",
    headers: {
      'X-Agent': 'Juejin/Web',
      'Accept': '*/*',
      'Content-Type': 'application/json',
      'Host': 'web-api.juejin.im',
      'Origin': 'https://juejin.im',
      'Referer': 'https://juejin.im/',
      'Sec-Fetch-Dest': 'empty',
      'Sec-Fetch-Mode': 'cors',
      'Sec-Fetch-Site': 'same-site',
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
    },
    url: url,
    data: JSON.stringify(param)
  })
  console.log(response.data)
  writefile(response.data, 'index')
}
testPost()

writefile = function (result, fileName) {
  fs.writeFile(`./${fileName}.json`, JSON.stringify(result), { 'flag': 'a' }, function(err) {
      if(err) throw err
      console.log('写入成功')
  })
}

方式二

在实际需求中并不是所有的网站数据都是在接口中返回,或者说完全不是接口返回这时候就需要第二种方式来实现。通过网页上的标签找到对应需要的数据

这里推荐使用的是谷歌出来的 Puppeteer

什么是Puppeteer?

简而言之,这货是一个提供高级API的node库,能够通过devtool控制headless模式的
chrome或者chromium,它可以在headless模式下模拟任何的人为操作

还是以掘金列表为例,首先确定你需要爬取的字段或者需要的数据。分析下页面DOM结构。

首先起个项目安装 Puppeteer

npm install Puppeteer -s

在项目中新建的index.js

// 启动浏览器
const browers = await puppeteer.launch({
  headless: false, // 打开可视化的配置
  defaultViewport: { // 视口大小设置
    width: 1920,
    height: 1080,
  },
  slowMo: 300
})
// 打开一个新页面
const Page = await browers.newPage();
// 打开并进入需要爬取的页面
await Page.goto('https://juejin.im/timeline/recommended')
console.log('document loaded success')

对于第一页爬取加载出来必然是会比较好去拿,这里的页面分页是需要下拉加载获取然后动态生成上去的这时候就体现的 puppeteer 有点他可以模拟用户的 操作

// 延迟使用
const sleep = (times) => {
    return new Promise((resolve)=> {
      setTimeout(()=> {
        resolve(1)
      }, times)
    })
  }

  await sleep(3000)
  await Page.evaluate(()=>{
    window.scrollBy(0, document.body.scrollHeight)
  })
  console.log(`1 page load success`);

  await sleep(3000)
  await Page.evaluate(()=>{
    window.scrollBy(0, document.body.scrollHeight)
  })
  console.log(`2 page load success`);
  

通过程序去模拟用户下拉操作 window.scrollBy

然后就是对dom里面的数据进行需要性获取了

await sleep(3000)
await Page.evaluate(()=>{
	let dats = []
	let listSelecter = [...document.querySelectorAll('.entry-list .item .entry-box')]
	for(let item of listSelecter) {
		// 文章类型
		let articleType = item.querySelector('.info-box .meta-row .post').innerText
		// 作者名字
		let articleAuthor = item.querySelector('.info-box .meta-row .username a').innerText
		// 作者名称跳转对应作者详情页 路由
		let author_url = item.querySelector('.info-box .meta-row .username a').href
		// 文章列表标题
		let articleTitle = item.querySelector('.info-box .title-row .title').innerText
		// 点赞数
		let giveLikeNum = item.querySelector('.info-box .action-row .like .title-box .count').innerText
		// 评论数  做判断有的会不存在评论数 点赞数也一样
		let commentNum = item.querySelector('.info-box .action-row .comment .count') ? item.querySelector('.info-box .action-row .comment .count').innerText : 0
		// 文章详情页面路由地址
		let detail_url = item.querySelector('.entry-link').href
		// 列表展示图地址
		let article_list_img = item.querySelector('.thumb') ? item.querySelector('.thumb').getAttribute('data-src') : ''
		// 文章所属 类型标签
		let tagList = [...item.querySelectorAll('.info-box .meta-row .meta-list>.tag a')]
		let articleTag = []  
		for (let i of tagList) {
		  articleTag.push({
		    tag_url: i.href,
		    tag_name: i.innerText
		  })
		}
		let listInfo = {
			articleType: articleType,
			articleAuthor: articleAuthor,
			articleTitle: articleTitle,
			giveLikeNum: giveLikeNum,
			commentNum: commentNum,
			detail_url: detail_url,
			author_url: author_url,
			articleTag: articleTag,
			article_list_img: article_list_img
		}
		dats.push(listInfo)
	}
	return dats
})

这里主要用到了基础的一些 puppeteer的API 跟多的 中文文档 Puppeteer

全部代码

const puppeteer = require('puppeteer');
// const { resolve } = require('path');

const pullUrlData = async function() {

  // 启动浏览器
  const browers = await puppeteer.launch({
    headless: false,
    defaultViewport: {
      width: 1920,
      height: 1080,
    },
    slowMo: 300
  })

  // 打开一个新页面
  const Page = await browers.newPage();

  await Page.goto('https://juejin.im/timeline/recommended')
  console.log('document loaded success')

  const sleep = (times) => {
    return new Promise((resolve)=> {
      setTimeout(()=> {
        resolve(1)
      }, times)
    })
  }


  await sleep(3000)
  await Page.evaluate(()=>{
    window.scrollBy(0, document.body.scrollHeight)
  })
  console.log(`1 page load success`);

  await sleep(3000)
  await Page.evaluate(()=>{
    window.scrollBy(0, document.body.scrollHeight)
  })
  console.log(`2 page load success`);

  await sleep(3000)
  let list = await Page.evaluate((Page)=>{
    let dats = []
    let listSelecter = [...document.querySelectorAll('.entry-list .item .entry-box')]
    for (let item of listSelecter) {
      let articleType = item.querySelector('.info-box .meta-row .post').innerText
      let articleAuthor = item.querySelector('.info-box .meta-row .username a').innerText
      let author_url = item.querySelector('.info-box .meta-row .username a').href
      let articleTitle = item.querySelector('.info-box .title-row .title').innerText
      let giveLikeNum = item.querySelector('.info-box .action-row .like .title-box .count').innerText
      let commentNum = item.querySelector('.info-box .action-row .comment .count') ? item.querySelector('.info-box .action-row .comment .count').innerText : 0
      let detail_url = item.querySelector('.entry-link').href
      let article_list_img = item.querySelector('.thumb') ? item.querySelector('.thumb').getAttribute('data-src') : ''
      let tagList = [...item.querySelectorAll('.info-box .meta-row .meta-list>.tag a')]
      let articleTag = []
      
      for (let i of tagList) {
        articleTag.push({
          tag_url: i.href,
          tag_name: i.innerText
        })
      }
      let listInfo = {
        articleType: articleType,
        articleAuthor: articleAuthor,
        articleTitle: articleTitle,
        giveLikeNum: giveLikeNum,
        commentNum: commentNum,
        detail_url: detail_url,
        author_url: author_url,
        articleTag: articleTag,
        article_list_img: article_list_img
      }
      dats.push(listInfo)
    }
    return dats
  })
  console.log('isOk',list)
}

pullUrlData()

对比、思考

在学习中,有了解相关类似插件Crawler、cheerio、puppeteer
Crawler、cheerio
优点:比较轻量,速度快
缺点:都是只能爬取单链接页面,无法进行模拟操作。
例如:瓜子二手车直卖网 https://www.guazi.com/sh/buy/o2/#bread 页面分页有规律可行 可以进行链接修改来处理
puppeteer
优点:可以模拟用操作,爬取单页应用,插入脚本代码执行,只要浏览器能操作的都可以进行模拟
缺点:每次都需要加载chrime内核,所以会对电脑性能占用较大。

扩展

  • 并不是所有的网站都需要 这种 模式来 爬取可能某些数据是需要 DOM来获取的可以相互结合爬取,

  • 除了模拟用户滚动操作以外 分页若是 点击页码来做处理 page.click(selector[, options])

  • 需要登录的网站来处理模拟登录操作,或者是设置 headless: false 打开调试窗口来手动操作登录、设置等待时间来完成登录操作后 在执行爬取操作。

  • 附上将处理好的数据存入MySql数据库

    const mysql = require('mysql')
    // 创建连接池
    const connection = mysql.createConnection({
      host: '127.0.0.1',
      user: 'username',
      password: '123456',
      database: 'dataName' // 数据库名称
    })
    
    // 将写入的表的字段 结构例子
    const addSql = "INSERT INTO subject(id, exam_id, subject_name, subject_value) values (?,?,?,?)"
    let addparams = [1, 2, 3, 4]
    connection.query(addSql, addparams, function(err,data){
       if(err){
         console.log(err,"数据库连接错误");
       }
     })
        
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值