前端优化

浅谈反爬虫

鹏展博

3872字约13分钟

http安全优化

2024-01-14

初版,待校对。

爬虫 与 反爬虫 是一场持久的攻防战,不仅仅是 商业上的斗争,也是 技术人的斗争,是看不见硝烟的战场。 爬虫不存在一劳永逸的技术手段,反爬虫也难以找到能够杜绝被爬取的防御手段,双方都是在不断地斗争, 进攻者不断变换攻击手段,防御者也不断地针对攻击手段更新防御手段。

爬虫的行为很难被杜绝,反爬虫的大多数措施,都是在不断地增加 爬取 的难度,使得 爬虫的成本不断上升, 直至行为带来的收益低于付出的成本。

本文只 探讨 反爬虫 的一些 技术方案,对一些 涉及到 安全保密 的技术方案,不会给出方案细节和实现方法。


爬虫

爬虫是一种通过爬取网页内容来获取信息的程序。

在当下,很多的电商类、内容类的网站,一方面承受来自正常的用户访问行为带来的压力, 一方面也承受了来自各种各样爬虫的爬取行为带来的远超用户访问的压力。

不仅耗费了大量的服务器的资源和带宽,还被第三方非法获取了大量具有商业价值的数据, 从而带来巨大的损失。

常见的爬虫策略

常见的网页爬虫策略包括以下几种:

宽度优先遍历策略

这是最常见的爬取策略,从起始网页开始,先尽可能多地抓取起始网页所在的主页链接, 然后对这些链接进行宽度优先的遍历,尽可能抓取所有可抓取的网页。

非完全PageRank策略

利用PageRank算法来确定抓取网页的优先级,优先抓取PageRank值高的网页。

OPIC策略

该策略考虑了网页的重要性,根据网页的重要性来分配抓取的优先级。

大站优先策略

该策略优先抓取大型网站的网页,因为大型网站通常包含更多的有价值信息。

网页更新策略

该策略根据网页的更新频率来分配抓取优先级,优先抓取更新频繁的网页。

分布式集群爬虫

对于大规模的数据抓取任务,可以使用分布式集群爬虫来进行。该类型爬虫能够同时从多个节点进行数据抓取,提高抓取效率。

除此之外,还有 使用代理IP池、模拟登陆等策略。

常见爬取内容的技术方案

爬取网页内容的技术方案有很多种,其中一些常见的方法:

HTTP请求库

Python有很多强大的HTTP请求库,如 requestsurllib3http.client 等, 这些库可以发送各种类型的HTTP请求,包括 GETPOST 等。

浏览器自动化工具

SeleniumPyppeteer 等,这些工具可以模拟用户在浏览器中的操作,从而抓取动态网页内容。

代理IP

在爬取网页时,可能会遇到IP被封禁的情况,通过代理IP来避免被封禁。

多线程或多进程

多线程或多进程可以提高爬虫的效率,但需要注意线程安全或进程安全。

分布式爬虫

分布式爬虫可以同时从多个节点进行数据抓取

数据库存储数据

在爬取大量数据时,使用数据库来存储数据,如MySQL、MongoDB等。

缓存技术

使用缓存技术避免重复抓取网页

正则表达式或BeautifulSoup等解析工具

这些工具可以帮助从网页中提取需要的数据。

反爬虫

反爬虫 是为了应对 爬虫 而进行的一系列防御措施。

从 爬虫的策略,以及 技术方案 来看,爬虫程序常见的特征包括:

  1. 高频访问
  2. 抓取 html 内容
  3. 抓取 数据接口
  4. 模拟用户行为,如借助 Selenium、Pyppeteer 等工具

反爬虫策略

基于这些特征,常见的反爬虫策略有:

  1. User-Agent + Referer检测
  2. 账号及Cookie验证
  3. 验证码
  4. IP限制频次

然而由于 Selenium、Pyppeteer 等工具的存在,像 User-Agent + Referer 检测 很容易就被绕过。

国内外都存在一些贩卖虚拟手机号的灰色产业,甚至还有一些针对各种企业的账号体系的 "养号" 产业,导致 账号和 Cookie 验证 需要更加复杂的策略。

而 IP 限制频次,很容易就能通过 代理IP池 进行绕过,当然,我们也可以 建立 黑名单 IP池 来封禁这些 被识别为 爬虫的IP。

而且现在的 图像识别技术越来越成熟,包括 AI识图 也越来越准确,一些简单的图片噪点验证码早已完全 失去了其作用,成为只会影响用户体验的方案。当然现在也有很多 如 滑动验证码、人脸识别 等方案能够 提供更好的反爬虫保护。


以上这些策略,是在 爬虫触及内容前 做的防御措施。而如果 爬虫攻破了这些防御措施,那么我们 还可以在 内容上 做防御措施。

内容防御

内容防御 是指,在 爬取 内容前,对内容进行一些防御措施。主要从两个方向上进行防御:

  1. 让爬虫 看不到 内容
  2. 让爬虫 读不到 内容

看不到 内容

通常来说,对于一个新发布、或 版本更新后 的网页,爬虫以及爬虫的开发者需要重新爬取该网页。 对于爬虫开发者,则需要先查看网页做了哪些更新,然后调整 爬虫程序,再进行爬取。 在这个过程中,爬虫开发者 可能会通过 浏览器,打开 调试控制台 查看 网页源代码、数据接口 等。

为了应对这一行为,我们可以针对 打开 调试控制台 这一行为,根据其特性,或者其行为,进行一些 防御措施。

调试控制台

第一种常见的是, 在源代码中 加入 debugger 语句,当打开控制台时,就会进入 调试模式。

我们可以利用 无限循环 来实现 无限调试。

function bun() {
  setInterval(() => {
    debugger
  }, 50)
}
try {
  bun()
} catch (err) {}

当 打开控制台 时,由于程序被 debugger 阻止,无法进行断点调试,网页的请求也看不到。

无限 debugger 反制措施

无限 debugger 通常只能 防止 新手小白 ,但对于有一定经验的 技术人员来说,基本不会起到什么作用。

技术人员可以通过控制台中的 Deactivate breakpoints 按钮或者使用快捷键 Ctrl + F8 关闭无限 debugger; 还可以通过添加 add script ignore list 需要忽略执行代码行或文件。


于是,又衍生了一些更加复杂的变式:

通过 格式化代码 将 debugger 写在一行中,Deactivate breakpoints 按钮或者使用快捷键 Ctrl + F8 就 无法关闭 debugger,但 add script ignore list 依然能够阻止 debugger 运行。

function bun() {
  setInterval(() => {
    debugger
  }, 50)
}
try {
  bun()
} catch (err) {}

进一步的,可以通过将 debugger 改写成 Function("debugger")(); 的形式来应对 add script ignore list

function bun() {
  setInterval(() => {
    Function('debugger')()
  }, 50)
}
try {
  bun()
} catch (err) {}

还可以继续将代码写得更加复杂:

function bun() {
  setInterval(() => {
    ;(function () {
      return false
    })
      ['constructor']('debugger')
      ['call']()
  }, 50)
}
try {
  bun()
} catch (err) {}

再进行 代码加密混淆 等。

检测窗口大小变化

当打开控制台时,如果是 窗口内打开,控制台会吸附在 侧栏,从而引起 浏览器窗口变化。 可以通过检测 外部窗口大小 和 内部窗口大小 的差值,来侧面判断是否打开了控制台。

if (window.outerHeight - window.innerHeight > 200 || window.outerWidth - window.innerWidth > 200) {
  // 替换网页内容
  document.body.innerHTML = '检测到非法调试,请关闭后刷新重试!'
}

当检测到打开控制台后,我们可以直接替换网页的所有内容,或者重定向到一个新的空白窗口。 使之 看不到 正确的内容。

但其缺陷就是,如果 控制台模式是选择 窗口外打开的,并不会引起 浏览器窗口变化。则无法判断是否打开了控制台。


我们可以 综合以上两点,形成一个方案:

function bun() {
  if (window.outerHeight - window.innerHeight > 200 || window.outerWidth - window.innerWidth > 200) {
    // 替换网页内容
    document.body.innerHTML = '检测到非法调试,请关闭后刷新重试!'
  }
  setInterval(() => {
    ;(function () {
      return false
    })
      ['constructor']('debugger')
      ['call']()
  }, 50)
}
try {
  bun()
} catch (err) {}

但其实认真想一想,基于是否打开控制台 来判断是否有潜在爬虫行为,虽然有作用,但只有一点点

在 现代浏览器 Chrome ,从 117 版本开始, 提供了 Override content 的功能,它不仅可以在 Network 面板中发起 Mock 请求,还可以直接在 Sources 面板中,直接 替换当前网页的资源内容。 这意味着,即使我们 在代码中加入了 这些检测,爬虫开发者 完全可以直接在 Sources 面板中,直接 修改网页内容,删除掉 这些代码。

再者,还有比如 FiddlerCharles 等抓包工具,可以直接抓取到 内容,还包括 伪装 https 证书等。

基于 是否打开控制台 的检测 形同虚设。可能带来的唯一收益就是 防新手小白,其负面收益 可能是 进一步 提高了 网站的所有者、维护者自己 的调试、排查成本。

所以是否有这么做的必要,只能是见仁见智。

读不到 内容

当无论如何都不能防止爬虫触及内容,被爬取到网页内容或者数据,还可以继续在内容本身上进行防御。

在内容本身上进行防御,前提是保证正常的用户访问行为呈现到内容应该都是正确的, 再通过其他措施使爬虫行为读取到的内容是不正确的。

在这一方面,不同类型的网站会运用到不同的技术手段。

对于 内容类的网站:

以图片为主要内容的

通常是给 图片添加 水印 ,水印包括 可见水印隐藏式水印

以文字创作为主要内容的

常见的方法包括, 在 文字内容 中,插入不可见的字符,或者 重新建立 一套 Unicode 字符映射表, 使源码中的字符与网页实际渲染的字符不一致。

但如果 重新建立所有的字符映射表,其工作量将会变得很大,增加了很多不必要的复杂度,所以通常来说, 只需要对 部分关键的内容进行处理。


字符混淆策略

通过对 关键内容的 字符,采用 一些技术手段进行处理,使得源代码中的 字符与网页实际渲染的字符不一致, 确保用户看到的内容为真实内容,而爬虫获取的内容为包含混淆字符的内容。

font-face 字符集

font-face 在 CSS 中被用于定义字体。

iconfont 中的图标字体。 将关键内容的字符,使用 SVG 进行渲染,整合成一个 字体文件,再通过 font-face 定义字体,在网页源代码中,引入该字体库后,使用 unicode 编码编写内容,再 渲染为正常的内容。

这种做法,由于关键内容被替换为了 unicode,爬虫只能爬取到 unicode码,而不是真实的内容,它还需要解析 对应的字体库进行 编码映射,才能获取到真实内容,提高了爬取的复杂度。还可以通过 动态字体库,通过不定期的 更新 字体库的编码映射关系,来增加进一步爬取的复杂度。

background-image 拼凑

背景图片的拼凑,一般是运用在一些 关键内容为 数字、字母 的场景上,因为其 字符数量较少,转换为图片一般 不会占用过多的资源,还可以使用 “雪碧图” 合并为一张图片,通过 背景定位 的方式控制显示的内容。

这种方式爬取到的内容只有 一组空的 标签,爬虫还需要进一步的 读取 CSS 、获取图片、定位信息等才能分析获取 内容。

字符穿插

字符穿插,主要是通过在 正常的内容之间,插入不会被渲染的字符,但这些 字符在源代码内容中,是可读取的。

比如, 12234 之间插入随机的其他 数字:

<span>1</span><span>2</span><span>2</span><span>3</span><span>4</span>

插入随机数字

<span>1</span><span>3</span><span>2</span><span>2</span><span>3</span><span>6</span><span>4</span>

然后通过 类名、选择器等方式,根据某些规则,将插入的随机数字进行设置为 display: node 等隐藏不显示。

这种方式 由于在 正常的内容中插入了正常的内容,如果爬虫不知道规则的话,会误认为获取到了正确的内容,

伪元素隐藏

借助 伪元素 ::before::after ,将 关键内容 填充到 CSS 属性 content 中。

元素定位穿插

元素定位穿插,是将 正确的内容 进行 打乱重组,然后再通过 定位 的方式调整到 正确的顺序位置上。

shadowDOM 隐藏

shadowDOM, 通过 Element.attachShadow() 方法给指定的元素挂载一个 Shadow DOM, 将 关键内容 写入到 Shadow DOM 中。 由于 Shadow DOM 的 特性,可以指定其 modefalse, 拒绝从 js 外部访问关闭的 shadow root 节点。

即使 爬虫 通过 SeleniumPyppeteer 也读取不到内容。缺点就是 shadowDOM 的兼容性问题, 可能会导致部分用户也看不到内容。


当 爬虫行为 在商业上 带来的收益十分巨大时,就会投入更多的资源去应对反爬虫的措施,做出更有 针对性的爬虫程序。

这一现象特别是在 电商类、平台类 的网站尤为严重。为了打出 价格优势,需要及时的获取 竞争平台 的商品价格,调整自身平台的价格、优惠策略等。包括了 价格监控、价格变动监控、价格预警 等等 一些列措施。

往往当 一方的反爬虫 方案进行技术调整后,另一方的爬虫一旦获取不到内容,就会立即发出邮件、短信等 进行预警通知。双方就形成了一种长时间的持久的互为攻防的拉锯战。

从现状来看,反爬虫 是很难完全实现 防御住爬虫爬取到内容的,而且爬虫 也有各种手段 判断是否 爬取到了正确的内容。

既然杜绝不了,那么除了常规的必要的反爬虫措施之外,给 爬虫 一些 真实内容,又有何不可呢, 然后我们可以在 真实内容中,掺杂上一些不那么真实的内容,又有何不可呢?