目录
1. 基本概念
目标
什么是爬虫
爬虫的作用
什么是爬虫
可以把互联网比做成一张“大网”,爬虫就是在这张大网上不断爬取信息的程序。所以一句话总结:爬虫是请求网站并提取数据的自动化程序
爬虫的基本工作流程如下:
1. 向指定的URL发送http请求
2. 获取响应(HTML、XML、JSON、二进制等数据)
3. 处理数据(解析DOM、解析JSON等)
4. 将处理好的数据进行存储

- 相关岗位:
 - 数据分析
 - 大数据应用
 - 运营
 - 人工智能
 
爬虫的作用
- 搜索引擎
 - 自动化程序
 - 自动获取数据
 - 自动签到
 - 自动薅羊毛
 - 自动下载
 - ...
 
抢票程序
爬虫就是一个探测程序,它的基本功能就是模拟人的行为去各个网站转悠,点点按钮,找找数据,或者 ,把看到的信息背回来。就像一只虫子在一幢楼里不知疲倦地爬来爬去。
可以简单地想象:每个爬虫都是你的“分身”。就像孙悟空拔了一撮汗毛,吹出一堆猴子一样。
我们每天使用的百度和Google,其实就是利用了这种爬虫技术:每天放出无数爬虫到各个网站,把他们 的信息抓回来,存到数据库中等用户来检索。
  

抢票软件,自动帮你不断刷新 12306 网站的火车余票。一旦发现有票,就马上下单,然后你自己来付款。
在现实中几乎所有行业的网站都会被爬虫所 “ 骚扰 ” ,而这些骚扰都是为了方便用户
 
  
 
 
当然,有些网站是不能被过分骚扰的,其中排第一的就是出行类行业。
12306 之所以会出如此变态的验证码,就是因为被爬虫折磨的无可奈何

正所谓道高一尺魔高一丈,某些爬虫工具,为了解决这种变态验证码,甚至推出了 “ 打码平台 ”
原理是爬虫不断工作,只要遇到二维码,就通过打码平台下发任务,打码平台另一边就雇佣一大堆网络闲人,只要看到有验证码来了,就人工选一下验证码,让人工和智能完美结合!
小结
什么是爬虫 爬虫是请求网站并提取数据的自动化程序
zhe爬虫的作用
- 自动化程序
 - 抢票软件
 - 自动收集信息
 - 自动签到
 - ...
 
2. 使用爬虫批量下载图片
目标
以 尚硅谷师资团队介绍_师资培训规模-尚硅谷 网站目标为例,最终目的是下载网站中所有
老师的照片:
步骤
- 1. 发送 http 请求,获取整个网页内容
 - 2. 通过 cheerio 库对网页内容进行分析
 - 3. 提取 img 标签的 src 属性
 - 4. 使用download库进行批量图片下载
 
发送一个HTTP请求
使用 axios (推荐)
使用 node 官方 api
使用 axios
axios.get()
async function getData() {
  // 获取html
  let res = await axios.get("http://www.atguigu.com/teacher.shtml");
}
getData() 
 
使用 node 官方 api
node 的核心模块 http 模块即可发送请求,摘自 node 官网 api :
 
 
 
const http = require("https");
// 创建请求对象
let req = http.request("https://www.itheima.com/teacher.html#aweb", (res) => {
  // 准备chunks
  let chunks = [];
  res.on("data", (chunk) => {
    // 监听到数据就存储
    chunks.push(chunk);
  });
  res.on("end", () => {
    // 结束数据监听时讲所有内容拼接
    console.log(Buffer.concat(chunks).toString("utf-8"));
  });
});
// 发送请求
req.end(); 
小结
使用 axios 或 node 原生 api 发起请求 , 得到的结果就是整个 HTML 网页内容
3. 解析 HTML 并下载图片
学习目标:
- 使用 cheerio 加载 HTML
 - 回顾 jQueryAPI
 - 加载所有的 img 标签的 src 属性
 - 使用 download 库批量下载图片
 
cheerio 库简介
 
  
 
 
简而言之,就是可以再服务器上用这个库来解析 HTML 代码,并且可以直接使用和 jQuery 一样的 api
官方 demo 如下:
const cheerio = require('cheerio');
const $ = cheerio.load('<h2 class="title">Hello world</h2>');
$('h2.title').text('Hello there!');
$('h2').addClass('welcome');
$.html();
//=> <html><head></head><body><h2 class="title welcome">Hello there!</h2></body></html> 
 同样也可以通过 jQuery 的 api 来获取 DOM 元素中的属性和内容
使用cheerio库解析HTML
- 1. 分析网页中所有 img 标签所在结构
 
- 2. 使用 jQuery API 获取所有 img 的 src 属性
 
async function getData() {
  // 获取html
  let res = await axios.get("http://www.atguigu.com/teacher.shtml");
  // 这会将 HTML 字符串加载到 Cheerio 中并返回 Cheerio 对象
  const $ = cheerio.load(res.data);
  // 正则匹配文件格式
  const imgReg = /\.(jpg|jpeg|png|gif|bmp)$/;
  // 获取所有的image标签
  const imagesArray = Array.from($(".con ul").find("img"));
  //拼接访问路径 编码 过滤图片格式
  let imagsUrl = imagesArray
    .map((img) => "http://www.atguigu.com/" + encodeURI($(img).attr("src")))
    .filter((img) => imgReg.test(img));
}
getData() 
   使用 download 库批量下载图片
// 发送请求
import axios from "axios";
// 解析html
import * as cheerio from "cheerio";
// 下载图片
import download from "download";
// 文件模块
import fs from "fs";
async function getData() {
  // 获取html
  let res = await axios.get("http://www.atguigu.com/teacher.shtml");
  // 这会将 HTML 字符串加载到 Cheerio 中并返回 Cheerio 对象
  const $ = cheerio.load(res.data);
  // 正则匹配文件格式
  const imgReg = /\.(jpg|jpeg|png|gif|bmp)$/;
  // 获取所有的image标签
  const imagesArray = Array.from($(".con ul").find("img"));
  //拼接访问路径 编码 过滤图片格式
  let imagsUrl = imagesArray
    .map((img) => "http://www.atguigu.com/" + encodeURI($(img).attr("src")))
    .filter((img) => imgReg.test(img));
  //创建文件目录
  if (!fs.existsSync("dist")) {
    fs.mkdirSync("dist");
  }
  imagsUrl.forEach(async (img) => {
    try {
      const response = await download(img); // 使用download函数下载文件
      const filePath = `dist/${decodeURI(img).split("/").pop()}`; //解码 获取图片后缀
      fs.writeFileSync(filePath, response); // 使用writeFileSync写入文件
    } catch (error) {
      console.error("Error downloading image:", error);
    }
  });
}
getData();
 
注意事项:如有中文文件名,需要使用 base64 编码
小结
- 使用 cheerio 解析 HTML
 - 获取所有图片的 src
 - 使用 download 库批量下载图片
 
4. Selenium 的基本使用
简介
Selenium是一个Web应用的自动化测试框架,可以创建回归测试来检验软件功能和用户需求,通过框架 ,可以编写代码来启动浏览器进行自动化测试,换言之,用于做爬虫就可以使用代码启动浏览器,让真正的浏览器去打开网页,然后去网页中获取想要的信息!从而实现真正意义上无惧反爬虫手段!
步骤
- 1. 根据平台下载需要的 webdriver
 - 2. 项目中安装 selenium-webdriver 包
 - 3. 根据官方文档写一个小 demo
 
根据平台选择 webdriver
- Chrome chromedriver(.exe)
 - Internet Explorer IEDriverServer.exe
 - Edge MicrosoftWebDriver.msi
 - Firefox geckodriver(.exe)
 - Safari safaridriver
 
选择版本和平台

下载后放入项目根目录
如果使用版本超过 114 以上的 chrome 浏览器, 可以在此处下载最新的 Chromedriver:
官方下载总站(所有版本):
安装selenium-webdriver的包
自动打开百度搜索 “测试自动化”
import { Builder, Browser, By, Key, until } from "selenium-webdriver";
(async function example() {
  console.log(111);
  //构建一个 webdriver 浏览器
  let driver = await new Builder().forBrowser(Browser.FIREFOX).build();
  console.log(driver);
  try {
    // 打开百度
    await driver.get("https://www.baidu.com");
    // 代码操作浏览器 获取元素并写入一个关键字webdriver
    // By.name 属性选择器name
    // By.css; css 选择器
    // sendKeys 发送文本内容到文本框
    // Key.RETURN 按下回车
    //  await driver.findElement(By.name("q")).sendKeys("webdriver", Key.RETURN);
    await driver.findElement(By.css("#kw")).sendKeys("测试自动化", Key.RETURN);
  } finally {
    // await driver.quit(); 退出浏览器
  }
})();
 
 
小结
根据当前浏览器版本选择对应的 driver 下载
参考官网文档实现自动打开百度输入搜索词
5. 使用 Selenium
在使用 Selenium 实现爬虫之前,需要搞清楚一个问题:
为什么要用 Selenium 来做爬虫?
- 目前的大流量网站,都会有些对应的反爬虫机制
 
- 而通过Selenium可以操作浏览器,打开某个网址,接下来只需要学习其API,就能获取网页中需要的内容了!
 - 反爬虫技术只是针对爬虫的,例如检查请求头是否像爬虫,检查IP地址的请求频率(如果过高则封杀)等手段
 
而Selenium打开的就是一个自动化测试的浏览器,和用户正常使用的浏览器并无差别,所以再厉害的反 爬虫技术,也无法直接把它干掉,除非这个网站连普通用户都想放弃掉(12306曾经迫于无奈这样做过)
前几次可能会获取到数据,但多几次则会出现操作频繁请稍后再试的问题
Selenium API
核心对象:
- Builder
 - WebDriver
 - WebElement
 
辅助对象:
- By
 - Key
 
Builder
用于构建 WebDriver 对象的构造器
let driver = await new Builder().forBrowser(Browser.FIREFOX).build(); 
    
   
  
 
 
WebDriver
通过构造器创建好 WebDriver 后就可以使用 API 查找网页元素和获取信息了:
findElement() 查找元素 , 返回一个 WebElement 对象 传入 By.css() 可以通过选择器获取元素
WebElement
- getText() 获取文本内容
 - sendKeys() 发送一些按键指令
 - click() 点击该元素
 
应用 获取前端薪资统计
1.自动打开拉勾网搜索"前端"
- 1. 使用 driver 打开拉勾网主页
 - 2. 找到全国站并点击一下
 - 3. 输入“前端”并回车
 
2.模拟登录
默认是未登录状态(电商搜索功能也有此类限制,主要就是为了反爬虫)
- 运行 selenium 打开目标网站
 - 快速完成登录操作
 - 登录后会将身份标识存储在 Cookie 中
 - 延时 20 秒, 使用 selenium 导出所有 Cookie
 - 这就需要我们在打开浏览器之后的 20 秒内快速完成登录操作(考验手速的时候到了, 可自行设置更长的时间)
 - 导出后注释掉以上代码
 - 加上注入 Cookie 的代码
 - 打开 Cookies 查看是否导入成功
 
3.获取招聘信息中的薪资
搜索前端并获取当前页面所有公司的最低薪资
使用driver.findElement()找到所有条目项,根据需求分析页面元素,获取其文本内容即可:
4.自动点击不同城市
- 定义城市数组和初始索引
 - 搜索前端后先点击初始城市
 - 后续配合自动翻页再完善功能
 
5.自动翻页
- 1. 定义初始页码和最大页码(拉勾最多 30)
 - 2. 开始获取数据时打印当前正在获取的城市及页码数
 - 3. 获取完一页数据后,当前页码自增,然后判断是否达到最大页码
 - 4. 查找下一页按钮并调用点击 api ,进行自动翻页
 - 5. 翻页后递归调用获取数据的函数
 
import { Builder, Browser, By, Key, until } from "selenium-webdriver";
import delay from "delay";
import fs from "fs";
//构建一个 webdriver 浏览器
let driver = await new Builder().forBrowser(Browser.FIREFOX).build();
(async function start() {
  try {
    // getcooike();
    // return;
    // 打开网站
    await driver.get("https://www.lagou.com");
    console.log("打开网站");
    // 最大化
    await driver.manage().window().maximize();
    // 删除所有cooike
    await driver.manage().deleteAllCookies();
    console.log("删除cooikes完成");
    //读取 cookies
    const cookies = JSON.parse(fs.readFileSync("./cookies.json", "utf-8"));
    // 循环添加cookie
    for (let index = 0; index < cookies.length; index++) {
      // 因为浏览器对于带有SameSite=None的cookie要求必须同时设置Secure属性,以确保安全性
      cookies[index].secure = true;
      await driver.manage().addCookie(cookies[index]);
    }
    console.log("写入cooikes");
    await delay(3000);
    // 重新打开让cooikes 生效
    await driver.get("https://www.lagou.com");
    console.log("刷新页面");
    await delay(5000);
    // 获取输入框搜索前端
    await driver
      .findElement(By.css("#search_input"))
      .sendKeys("前端", Key.RETURN);
    console.log("搜索前端");
    await delay(5000);
    // 获取所有窗口句柄
    const handles = await driver.getAllWindowHandles();
    // 切换到新打开的标签页
    await driver.switchTo().window(handles[1]); // 假设新标签页是第二个句柄
    await delay(5000);
    console.log("切换tab标签栏目");
    await gitCity();
    await getData();
  } catch (e) {
    console.log(e);
  } finally {
    console.log("退出浏览器");
    await driver.quit(); //
  }
})();
let low = 0; // 10K 以下的岗位数量
let mid = 0; // 10K ~ 20K 的岗位数量
let high = 0; // 20K 以上的岗位数量
let currentPage = 1; // 当前第几页
const maxPage = 3; // 最大页数 30 页
const cityArr = ["北京", "上海", "广州", "深圳", "武汉"];
let cityIndex = 0; //城市索引
const results = []; //数据
// 获取数据
async function getData() {
  // 获取所有条目
  console.log(`---正在获取${cityArr[cityIndex]}的第${currentPage}页数据---`);
  await delay(10000);
  // 获取每一页的所有数据
  const items = await driver.findElements(By.css(".item__10RTO"));
  // console.log(items.length);
  // 获取所有的岗位薪资
  const moneys = await Promise.all(
    items.map((item) => item.findElement(By.css(".money__3Lkgq")).getText())
  );
  // console.log(moneys);
  // 进行数量统计
  moneys.forEach((item) => {
    const money = item.split("-")[0].slice(0, -1);
    money < 10 && low++;
    money >= 10 && money < 20 && mid++;
    money >= 20 && high++;
  });
  // console.log(low, mid, high);
  // 获取完当前页的数据了
  if (currentPage < maxPage) {
    // 点击下一页进行页码累加
    await driver
      .findElement(By.css(".lg-pagination-next .lg-pagination-item-link"))
      .click();
    currentPage++;
    await getData();
  } else {
    // 说明已经把当前城市获取完成了
    // 存储结果
    results.push({
      city: cityArr[cityIndex],
      low,
      mid,
      high,
    });
    // 写入到本地
    fs.writeFileSync("./results.json", JSON.stringify(results));
    console.log(cityArr[cityIndex], "数据写入成功");
    console.log(results);
    // 城市索引自增并判断还有没有城市, 没有就直接结束
    // ++i是先进行自增操作,然后再进行运算,换句话说,表达式++i的值是i自增1之后的值
    if (!cityArr[++cityIndex]) {
      console.log("数据拉取完毕");
      return;
    }
    // 让指定的城市被点击
    await gitCity();
    // 重置条件
    currentPage = 1;
    low = mid = high = 0;
    // 继续递归调用
    await getData();
  }
}
// 获取城市
async function gitCity() {
  // 获取所有可以点击的城市
  const cities = await driver.findElements(By.css(".option__2xJt5"));
  // 查找文本内容为北京的 div 让它被点击
  for (let i = 0; i < cities.length; i++) {
    const city = cities[i];
    const text = await city.getText();
    if (text === cityArr[cityIndex]) {
      console.log(text, "要点击的元素");
      await city.click();
      break;
    }
  }
}
//保存cookies
async function getcooike() {
  await driver.get("https://www.lagou.com/wn?");
  // 删除所有cooike
  await driver.manage().deleteAllCookies();
  // 最大化
  await driver.manage().window().maximize();
  await delay(5000);
  // 搜索
  await driver
    .findElement(By.css("#search_input"))
    .sendKeys("前端", Key.RETURN);
  // 延迟10秒 登陆准备cooike
  await delay(20000);
  const cookies = await driver.manage().getCookies();
  console.log(cookies);
  fs.writeFileSync("cookies.json", JSON.stringify(cookies));
}
 
 
6. 搭建服务器并实现数据可视化
使用 express 快速构建服务器
使用 echarts 渲染图表
构建服务器
import express from 'express'
import fs from 'fs'
// 创建一个 express 服务器
const server = express()
// 静态资源托管
server.use(express.static('./public'))
server.get('/salary', (req, res) => {
  const results = fs.readFileSync('./results.json', 'utf-8')
  res.send(results)
})
server.listen(3000, () => {
  console.log('http://localhost:3000')
}) 
 echarts 渲染图表
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>ECharts</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.4.3/echarts.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
</head>
<body>
  <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
  <div id="main" style="width: 600px;height:400px;"></div>
  <script type="text/javascript">
    async function init() {
      const res = await axios.get('/salary')
      console.log(res)
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));
      // 指定图表的配置项和数据
      var option = {
        title: {
          text: '各城市就业薪资分布'
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          }
        },
        legend: {},
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: {
          type: 'value',
          boundaryGap: [0, 0.01]
        },
        yAxis: {
          type: 'category',
          data: res.data.map(item => item.city)
        },
        series: [
          {
            name: '10K 以下',
            type: 'bar',
            data: res.data.map(item => item.low)
          },
          {
            name: '10K ~ 20K',
            type: 'bar',
            data: res.data.map(item => item.mid)
          },
          {
            name: '20K以上',
            type: 'bar',
            data: res.data.map(item => item.high)
          }
        ]
      };
      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
    }
    init()
  </script>
</body>
</html> 
  
 课程总结
爬虫神通广大,用途非常广泛,主要的目标是为了实现自动化程序,解放程序员的双手,帮助程序员自动获取一些数据,测试一些软件,甚至自动操作浏览器做很多事情也不乏有些不法分子拿爬虫做一些违法的事情,在此老师希望大家学会爬虫使用在正道上,获取一些我们需要的数据来进行分析
同时,在爬取目标网站之前,建议大家浏览该网站的 robots.txt ,来确保自己爬取的数据在对方允许范围之内
课程内容涵盖:
- 1. 爬虫简介
 - 2. 爬虫的作用
 - 3. 使用 axios 爬取数据
 - 4. 使用 cheerio 库进行 DOM 解析一个服务端解析 HTML 的库,与 jQuery API 设计相同
 - 5. 使用 download 库进行文件下载
 - 6. 使用Selenium实现爬虫
 - 7. 使用 echarts 实现数据可视化
 










