开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情
什么是反爬虫?
网络爬虫,是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成。但是当网络爬虫被滥用后,互联网上就出现太多同质的东西,原创得不到保护。于是,很多网站开始反网络爬虫,想方设法保护自己的内容。
他们根据ip访问频率,浏览网页速度,账户登录,输入验证码,flash封装,ajax混淆,js加密,图片,css混淆等五花八门的技术,来对反网络爬虫。
防的一方不惜工本,迫使抓的一方在考虑成本效益后放弃,抓的一方不惜工本,防的一方在考虑用户流失后放弃
为什么需要反爬虫?
-
网络爬虫会根据特定策略尽可能多的“爬过”网站中的高价值信息,占用服务器带宽,增加服务器的负载
-
恶意利用网络爬虫对Web服务发动DoS攻击,可能使Web服务资源耗尽而不能提供正常服务
-
恶意利用网络爬虫将免费查询的资源批量抓走,各种敏感信息,造成网站的核心数据被窃取,导致公司丧失竞争力 。
-
状告爬虫成功的几率小,爬虫在国内还是个擦边球,就是有可能可以起诉成功,也可能完全无效。
爬虫行为主要还是分为三大类:基于身份识别进行反爬、基于爬虫行为进行反爬、基于数据加密进行反爬。
我们并非是专业的爬虫工程书,无法做到很多通过云计算、防火墙、服务端等手段去做反爬虫,作为一名前端工程师,我们只能进行基于爬虫行为进行反爬,即站在web前端的角度去分析常用的web反爬虫技术方案,本文就带你来看看有哪些方案是可以执行的。
0x1: 反iframe嵌入策略
假如你运营的另一个网站曾经被利用iframe过,被做成了镜像站,更可恶的是搜索引擎竟然收录了他们的页面,而且排名还不错,你会不会感觉到很气愤?虽然后来可以利用JS对域名判断解决了这种问题,但是当你知道的时候,你的竞争力优势早已丧失殆尽!
文章「网站被镜像的解决办法及预防措施!」但是总感觉针对iframe会更有效。接下来带大家分析如何禁止网站被iframe嵌入,防止被别人做成镜像站。
禁止iframe嵌套有一下几种方法:
-
添加
X-Frame-Options响应头 -
设置Content-Security-Policy
-
通过窗口判断
添加X-Frame-Options响应头:
X-Frame-Options HTTP响应头是用来确认是否浏览器可以在frame或iframe标签中渲染一个页面,网站可以用这个头来保证他们的内容不会被嵌入到其它网站中,以来避免点击劫持。
<meta http-equiv="X-FRAME-OPTIONS" content="DENY">
<!--
DENY:表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
SAMEORIGIN:表示该页面可以在相同域名页面的 frame 中展示。
ALLOW-FROM uri:表示该页面可以在指定来源的 frame 中展示。
-->
如此设置后,页面被iframe嵌入后,请求会不成功,类似于google搜索的下图:
设置Content-Security-Policy
Content-Security-Policy, 叫做内容安全策略,简称CSP,限定网页允许加载的资源策略,一定程度上防范外部的xss等攻击。
它可以设置很多限定策略,这里我们是要限定iframe的嵌套,所以用"Content-Security-Policy": "frame-ancestors 'self'"。
const http = require('http')
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8',
"Content-Security-Policy": "frame-ancestors 'self'"
})
res.end('hello 我是cp3!')
}).listen(8081)
console.log('服务已开启,请打开http://127.0.0.1:8081')
生效后会发现请求会不成功,类似于google搜索的下图:
通过窗口判断
根据当前的页面的顶级窗口window.top和自身窗口window.self是否相等。如果不相等,则是因为嵌入了iframe,因为iframe的自身窗口和顶级的窗口是不相等的。
if (window.top != window.self) {
window.top.location = window.self.location; // 替换顶级窗口的地址
}
0x2: 蜜罐数据陷阱策略
在我们进行网页开发的时候,可以在页面插入假的、不可见的蜜罐数据来抓住爬虫,加入你的 robots.txt中。
<div class="search-result" style="display:none">
<h3 class="search-result-title">This search result is here to prevent scraping</h3>
<p class="search-result-excerpt">If you're a human and see this, please ignore it. If you're a scraper, please click
the link below :-)
Note that clicking the link below will block access to this site for 24 hours.</p>
<a class"search-result-link" href="/scrapertrap/scrapertrap.php">I'm a scraper !</a>
</div>
<!-- (The actual, real, search results follow.) -->
如上案例,一个来获取所有内容的爬虫将会被找到,就像获取其他结果一样,访问链接,查找想要的内容。一个真人将不会看到(因为使用 CSS 隐藏),而且更不会访问这个链接。而正规或者期望的爬虫比如谷歌的蜘蛛将不会访问这个链接,因为你可以将 /scrapertrap/ 加入你的 robots.txt 中(不要忘记增加)。
你可以让你的 scrapertrap.php 做一些比如限制这个 IP 访问的事情,或者强制这个 IP 之后的请求出验证码。
0x3: font-face解析策略
谈到font-face,也许很多人并不熟悉,但是说到应用此技术的公司,大家肯定都很熟悉了,58同城、猫眼电影、快手、抖音等,都采用了font-face策略来应对爬虫。
以猫眼电影为例,打开审查元素,发现页面内的票房和口碑分数的数字均变成了乱码。所以即便爬虫找到了正确位置,也依然无法获得正确信息。
仔细观察审查元素信息,在styles栏里发现了font-family,这就是自定义stonefont字体,可以把乱码显示成正常的数字。
<style>
@font-face {
font-family: stonefont;
src: url('//vfile.meituan.net/colorstone/b182ca67871bd0440e63694d7506a4ac3428.eot');
src: url('//vfile.meituan.net/colorstone/b182ca67871bd0440e63694d7506a4ac3428.eot?#iefix') format('embedded-opentype'),
url('//vfile.meituan.net/colorstone/f72563b7836b2b9b08399f876a09b9e22280.woff') format('woff');
}
.stonefont {
font-family: stonefont;
}
</style>
那么font-face它是怎样把数据进行替换的呢?我们先把woff文件打开(网上随便搜一个在线的字体编辑器),会发现woff文件最终形态如下图所示:
我们可以看到woff文件中每个字符都有一个编码对应,woff实际上就是编码和字符的映射表。我们再来看看页面中的被替换的词是什么形式。
<!-- 审查元素 -->
<div class="movie-index-content box">
<span class="stonefont">.</span>
<span class="unit">亿</span>
</div>
<!-- 网页源代码 -->
<div class="movie-index-content box">
<span class="stonefont">.</span>
<span class="unit">亿</span>
</div>
在这里,我们对比下可以发现,页面源码中的被替换字的就是woff文件中字符的编码加上&#x,所以大家可以发现字体替换的原理就是这样,我们使用一个简单的等式来表现:"替换数据"="&#x{woff文件中被替换数据的编码}"。
大部分网站使用字体反爬的方式是使用固定的字体文件来做数据替换,这就是最初的时候的字体反爬,也是最简单的方式。
写死的woff文件太容易让人解析, 所以需要进行进一步的策略升级:每次都更换新的woff文件,woff文件不更换字体信息,只更换字符编码。这样,每次的字符编码都不一样,解析的时候就不能使用同一套字符编码字典去解析了。
0x4: backgroundImage雪碧图策略
什么是雪碧图?简单说来就是通过把所有图片合成一张大图,然后以移位方式展示图片其中的某一部分。雪碧图的好处就不说了,其实雪碧图还可以用于web前端的反爬虫方向。
backgroundImage雪碧图策略同样也被很多行业大咖公司使用,比如早期的美团,比如自如租房等。我们以自如租房为例,打开审查元素发现,自如的敏感数据,采用的是style: background-image使用雪碧图,然后调整对应的位移位置的方式。
打开background-image的url查看,发现雪碧图长这样的。
那么为了展示价格要怎么做呢,前端代码怎么写呢?假设展示的价格是2090,然后继续看下雪碧图中数字的顺序、html代码中background-position以及css图片展示大小(30px),就可以推出,显示数字与background-position的关系是:
0px 6
-30px 9
-60px 4
-90px 8
-120px 5
-150px 2
-180px 7
-210px 3
-240px 0
-270px 1
通过上面的位置字典,就可以很轻松的使用代码去进行转换。
如果是固定顺序的雪碧图方式,那么就会很容易的被别人解析出来,所以我们需要雪碧图“动起来”,当雪碧图每次都是随机生成的,所以只有网站知道每个position对应的数字是多少,而我们却无法得知,从而应对一些低级别的爬虫技术。
0x5: 隐式 Style–CSS策略
先来唠唠什么是 隐式 Style–CSS:
CSS中, ::before 创建一个伪元素,其将成为匹配选中的元素的第一个子元素。常通过 content 属性来为一个元素添加修饰性的内容。
我们用汽车之家网站作为案例,来分析一下隐式 Style–CSS在反爬方向的使用。
从 HTML 源码中可以看到,所有的 span 标签的类名都是 hs_kw 加上一个数字再加上_configXX拼接的,我们可以试着搜索 hs_kw 看看。
于是我们就搜到了一些 hs_kw 相关的 JS 代码:
在浩瀚的js代码中我们终于发现了class的生成规则,下面的内容便是直接操作DOM,赋值style伪元素内容,完成了反爬最主要的逻辑
function $GetClassName$($index$) {
return '.hs_kw' + $index$ + '_configzT';
}
function $RuleCalss1$() {
return '::before { content:'
}
0x6: JS反调试策略
前面的反爬虫策略,我们都用到了Chrome 的F12去查看网页加载的过程,或者是调试JS的运行过程。那么有没有前端方向从根源上解决反爬虫的方式呢?那么JS反调试(反debug)策略了解一下。
我们以www.aqistudy.cn/这个网站作为例子,来分…
网站就加了反调试的策略,只有我们打开F12,就会暂停在一个“debugger”代码行,无论怎样都跳不出去。它看起来像下面这样:
并且会发现,页面上的内容已经被改变了,变成了如下入所示,index.html中往下翻找,可以找到如下内容,可以看到在 endebug() 方法中就是修改页面的代码:
不管我们点击多少次继续运行,它一直在这个“debugger”这里,每次都会多出一个VMxx的标签,观察“Call Stack”发现它好像陷入了一个函数的递归调用。这个“debugger”让我们无法调试JS。但是关掉F12窗口,网页就正常加载了。
解决这种JS反调试的方法我们称之为“反-反调试”,其策略是:通过“Call Stack”找到把我们带入死循环的函数,重新定义它。
var debugflag = false;
endebug(false, function () {
document.write('检测到非法调试, 请关闭调试终端后刷新本页面重试!');
document.write("<br/>");
document.write("Welcome for People, Not Welcome for Machine!");
debugflag = true;
});
if (!debugflag && !window.navigator.webdriver) {
loadTab();
}
那么 endebug这个方法都是些什么内容呢?通过all file search可以找到如下内容:
function endebug(off, code) {
if (!off) { !
function(e) {
function n(e) {
function n() {
return u
}
function o() {
window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized ? t("on") : (a = "off", console.log(d), console.clear(), t(a))
}
function t(e) {
u !== e && (u = e, "function" == typeof c.onchange && c.onchange(e))
}
function r() {
l || (l = !0, window.removeEventListener("resize", o), clearInterval(f))
}
"function" == typeof e && (e = {
onchange: e
});
var i = (e = e || {}).delay || 500,
c = {};
c.onchange = e.onchange;
var a, d = new Image;
d.__defineGetter__("id",
function() {
a = "on"
});
var u = "unknown";
c.getStatus = n;
var f = setInterval(o, i);
window.addEventListener("resize", o);
var l;
return c.free = r,
c
}
var o = o || {};
o.create = n,
"function" == typeof define ? (define.amd || define.cmd) && define(function() {
return o
}) : "undefined" != typeof module && module.exports ? module.exports = o: window.jdetects = o
} (),
jdetects.create(function(e) {
var a = 0;
var n = setInterval(function() {
if ("on" == e) {
setTimeout(function() {
if (a == 0) {
a = 1;
setTimeout(code)
}
},
200)
}
},
100)
})
}
}
解密出来可以发现这段代码就是用于检测开发者工具有没有打开,若打开了,则触发反调试,修改页面。
0x7: nginx反爬虫策略
作为一名前端工程师,nginx也是我们必须要掌握的内容之一。
有人统计,世界上约有三分之一的网址采用了Nginx。在大型网站的架构中,Nginx被普遍使用,如 百度、阿里、腾讯、京东、网易、新浪、大疆等。Nginx 安装简单,配置简洁,作用却无可替代。Nginx 是运维和后端的必修课,也是前端进阶的必修课。
对nginx还不太熟悉的小伙伴请先看一下nginx文档,下面,我们就用nginx来分析一下在反爬虫方向的实践。
- 首先,进入到 nginx 安装目录下的 conf 目录,将如下代码保存为 agent_deny.conf。
- 进入nginx文件,打开agent_deny.conf准备写入。
cd /usr/local/nginx/conf
vi agent_deny.conf
- 开始写入agent_deny.conf,主要是针对几种情况禁止抓取。
#禁止 Scrapy 等工具的抓取
if ($http_user_agent ~* (Scrapy|Curl|HttpClient)) {
return 403;
}
#禁止指定 UA 及 UA 为空的访问
if ($http_user_agent ~* "FeedDemon|Indy Library|Alexa Toolbar|AskTbFXTV|AhrefsBot|CrawlDaddy|CoolpadWebkit|Java|Feedly|UniversalFeedParser|ApacheBench|Microsoft URL Control|Swiftbot|ZmEu|oBot|jaunty|Python-urllib|lightDeckReports Bot|YYSpider|DigExt|HttpClient|MJ12bot|heritrix|EasouSpider|Ezooms|^$" ) {
return 403;
}
#禁止非 GET|HEAD|POST 方式的抓取
if ($request_method !~ ^(GET|HEAD|POST)$) {
return 403;
}
- 最后,在网站nginx相关配置中的 location / { 之后插入如下代码:
include agent_deny.conf;
- 保存后,执行如下命令,平滑重启 nginx 即可:
/usr/local/nginx/sbin/nginx -s reload
- 重启之后 开始模拟访问
没有加上 #禁止Scrapy|curl等工具的抓取之前
加上禁止Scrapy|curl等工具的抓取 之后
加上 #禁止指定UA及UA为空的访问
附录:UA收集
FeedDemon 内容采集
BOT/0.1 (BOT for JCE) sql注入
CrawlDaddy sql注入
Java 内容采集
Jullo 内容采集
Feedly 内容采集
UniversalFeedParser 内容采集
ApacheBench cc攻击器
Swiftbot 无用爬虫
YandexBot 无用爬虫
AhrefsBot 无用爬虫
YisouSpider 无用爬虫(已被UC神马搜索收购,此蜘蛛可以放开!)
jikeSpider 无用爬虫
MJ12bot 无用爬虫
ZmEu phpmyadmin 漏洞扫描
WinHttp 采集cc攻击
EasouSpider 无用爬虫
HttpClient tcp攻击
Microsoft URL Control 扫描
YYSpider 无用爬虫
jaunty wordpress爆破扫描器
oBot 无用爬虫
Python-urllib 内容采集
Python-requests 内容采集
Indy Library 扫描
FlightDeckReports Bot 无用爬虫
Linguee Bot 无用爬虫
0x8: CDN边缘计算策略
內容分发网络(Content Delivery Network 或 Content Distribution Network,简称 CDN ) 通过将源站内容分发至 最接近用户 的节点,从而 降低核心系统负载(系统、网络) ,使用户可就近取得所需内容,提高用户访问的响应速度。这种技术方案解决了因分布、带宽、服务器性能带来的访问延迟问题,适用于图片小文件、大文件下载、音视频点播、全站加速和安全加速等场景。
传统的 CDN 服务是纯粹的缓存和分发服务,缺乏可以直接提供给您的计算能力。当你了解过CDN的边缘节点后,你就会发现CDN边缘节点拥有天然的高可用、高伸缩、全球负载均衡的特性,边缘的计算服务可应用于更多的使用场景,其中就有Deny bot:边缘反爬虫服务。
边缘计算反爬虫一般是根据一些静态或者动态的规则、在离爬虫发起端足够近的路由节点方面就可以拦截掉一些流量。
案例借鉴与WAF-JS
signatures.json:
{
"signatures": [
"bot",
"check",
"cloud",
"crawler",
"download",
"monitor",
"preview",
"scan",
"spider",
"google",
"qwantify",
"yahoo",
"facebookexternalhit",
"flipboard",
"tumblr",
"vkshare",
"whatsapp",
"curl",
"perl",
"python",
"wget",
"siege",
"heritrix",
"ia_archiver"
]
}
'use strict'
class WAFJS{
constructor(config){
// loading signatures file
const { signatures } = require ('./signatures.json')
// declaring holders for config related data
this.allowedMethods = config.allowedMethods
this.allowedContentTypes = config.contentTypes
this.botSigs = signatures
// declaring base regex expression to check bot signatures
this.isBotCheckRegex = () => new RegExp(`(${this.botSigs.join('|')})`, 'i')
}
isBotCheck(userAgent){
return this.isBotCheckRegex().test(userAgent)
}
extendBotSigs(signatures){
this.botSigs = [...new Set(this.botSigs.concat(signatures))]
}
removeBotSig(signature){
this.botSigs = this.botSigs.filter(sig => sig !== signature)
}
reqCheck(requestMethod, contentType){
return !(this.allowedMethods.indexOf(requestMethod) < 0 || this.allowedContentTypes.indexOf(contentType) < 0)
}
wafChecks(userAgent, requestMethod, contentType){
return (!this.isBotCheck(userAgent) && this.reqCheck(requestMethod, contentType))
}
}
module.exports.WAFJS = WAFJS
waf可以嵌入CDN的worker中执行边缘计算,在CDN回源阶段之前,计算中会去匹配设定的指纹或者匹配规则,如果命中,则不允许访问CDN的资源内容。
该策略算是一种在加载资源之前的一种特殊保护机制!
有bug?想补充?
感谢大家观看这篇文章,有任何问题或想和我交流,请直接留言,
发现文章有不妥之处,也可指出交流,感谢阅读~