Python爬虫抓取技术的一些经验


Posted in Python onJuly 12, 2019

前言

web是一个开放的平台,这也奠定了web从90年代初诞生直至今日将近30年来蓬勃的发展。然而,正所谓成也萧何败也萧何,开放的特性、搜索引擎以及简单易学的html、css技术使得web成为了互联网领域里最为流行和成熟的信息传播媒介;但如今作为商业化软件,web这个平台上的内容信息的版权却毫无保证,因为相比软件客户端而言,你的网页中的内容可以被很低成本、很低的技术门槛实现出的一些抓取程序获取到,这也就是这一系列文章将要探讨的话题—— 网络爬虫 。

Python爬虫抓取技术的一些经验

有很多人认为web应当始终遵循开放的精神,呈现在页面中的信息应当毫无保留地分享给整个互联网。然而我认为,在IT行业发展至今天,web已经不再是当年那个和pdf一争高下的所谓 “超文本”信息载体 了,它已经是以一种 轻量级客户端软件 的意识形态的存在了。而商业软件发展到今天,web也不得不面对知识产权保护的问题,试想如果原创的高质量内容得不到保护,抄袭和盗版横行网络世界,这其实对web生态的良性发展是不利的,也很难鼓励更多的优质原创内容的生产。

未授权的爬虫抓取程序是危害web原创内容生态的一大元凶,因此要保护网站的内容,首先就要考虑如何反爬虫。

从爬虫的攻防角度来讲

最简单的爬虫,是几乎所有服务端、客户端编程语言都支持的http请求,只要向目标页面的url发起一个http get请求,即可获得到浏览器加载这个页面时的完整html文档,这被我们称之为“同步页”。

作为防守的一方,服务端可以根据http请求头中的User-Agent来检查客户端是否是一个合法的浏览器程序,亦或是一个脚本编写的抓取程序,从而决定是否将真实的页面信息内容下发给你。

这当然是最小儿科的防御手段,爬虫作为进攻的一方,完全可以伪造User-Agent字段,甚至,只要你愿意,http的get方法里, request header的 Referrer 、 Cookie 等等所有字段爬虫都可以轻而易举的伪造。

此时服务端可以利用浏览器http头指纹,根据你声明的自己的浏览器厂商和版本(来自 User-Agent ),来鉴别你的http header中的各个字段是否符合该浏览器的特征,如不符合则作为爬虫程序对待。这个技术有一个典型的应用,就是 PhantomJS 1.x版本中,由于其底层调用了Qt框架的网络库,因此http头里有明显的Qt框架网络请求的特征,可以被服务端直接识别并拦截。

除此之外,还有一种更加变态的服务端爬虫检测机制,就是对所有访问页面的http请求,在 http response 中种下一个 cookie token ,然后在这个页面内异步执行的一些ajax接口里去校验来访请求是否含有cookie token,将token回传回来则表明这是一个合法的浏览器来访,否则说明刚刚被下发了那个token的用户访问了页面html却没有访问html内执行js后调用的ajax请求,很有可能是一个爬虫程序。

如果你不携带token直接访问一个接口,这也就意味着你没请求过html页面直接向本应由页面内ajax访问的接口发起了网络请求,这也显然证明了你是一个可疑的爬虫。知名电商网站Amazon就是采用的这种防御策略。

以上则是基于服务端校验爬虫程序,可以玩出的一些套路手段。

Python爬虫抓取技术的一些经验

基于客户端js运行时的检测

现代浏览器赋予了JavaScript强大的能力,因此我们可以把页面的所有核心内容都做成js异步请求 ajax 获取数据后渲染在页面中的,这显然提高了爬虫抓取内容的门槛。依靠这种方式,我们把对抓取与反抓取的对抗战场从服务端转移到了客户端浏览器中的js运行时,接下来说一说结合客户端js运行时的爬虫抓取技术。

刚刚谈到的各种服务端校验,对于普通的python、java语言编写的http抓取程序而言,具有一定的技术门槛,毕竟一个web应用对于未授权抓取者而言是黑盒的,很多东西需要一点一点去尝试,而花费大量人力物力开发好的一套抓取程序,web站作为防守一方只要轻易调整一些策略,攻击者就需要再次花费同等的时间去修改爬虫抓取逻辑。

此时就需要使用headless browser了,这是什么技术呢?其实说白了就是,让程序可以操作浏览器去访问网页,这样编写爬虫的人可以通过调用浏览器暴露出来给程序调用的api去实现复杂的抓取业务逻辑。

其实近年来这已经不算是什么新鲜的技术了,从前有基于webkit内核的PhantomJS,基于Firefox浏览器内核的SlimerJS,甚至基于IE内核的trifleJS,有兴趣可以看看这里和这里 是两个headless browser的收集列表。

这些headless browser程序实现的原理其实是把开源的一些浏览器内核C++代码加以改造和封装,实现一个简易的无GUI界面渲染的browser程序。但这些项目普遍存在的问题是,由于他们的代码基于fork官方webkit等内核的某一个版本的主干代码,因此无法跟进一些最新的css属性和js语法,并且存在一些兼容性的问题,不如真正的release版GUI浏览器运行得稳定。

这其中最为成熟、使用率最高的应该当属 PhantonJS 了,对这种爬虫的识别我之前曾写过一篇博客,这里不再赘述。PhantomJS存在诸多问题,因为是单进程模型,没有必要的沙箱保护,浏览器内核的安全性较差。另外,该项目作者已经声明停止维护此项目了。

如今Google Chrome团队在Chrome 59 release版本中开放了headless mode api,并开源了一个基于Node.js调用的headless chromium dirver库,我也为这个库贡献了一个centos环境的部署依赖安装列表。

Headless Chrome可谓是Headless Browser中独树一帜的大杀器,由于其自身就是一个chrome浏览器,因此支持各种新的css渲染特性和js运行时语法。

基于这样的手段,爬虫作为进攻的一方可以绕过几乎所有服务端校验逻辑,但是这些爬虫在客户端的js运行时中依然存在着一些破绽,诸如:

基于plugin对象的检查

if(navigator.plugins.length === 0) {
 console.log('It may be Chrome headless');
}

基于language的检查

if(navigator.languages === '') {
 console.log('Chrome headless detected');
}

基于webgl的检查

var canvas = document.createElement('canvas');
var gl = canvas.getContext('webgl');
var debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
var vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
var renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
if(vendor == 'Brian Paul' && renderer == 'Mesa OffScreen') {
 console.log('Chrome headless detected');
}

基于浏览器hairline特性的检查

if(!Modernizr['hairline']) {
 console.log('It may be Chrome headless');
}

基于错误img src属性生成的img对象的检查

var body = document.getElementsByTagName('body')[0];
var image = document.createElement('img');
image.src = 'http://iloveponeydotcom32188.jg';
image.setAttribute('id', 'fakeimage');
body.appendChild(image);
image.onerror = function(){
 if(image.width == 0 && image.height == 0) {
 console.log('Chrome headless detected');
 }
}

基于以上的一些浏览器特性的判断,基本可以通杀市面上大多数 Headless Browser 程序。在这一层面上,实际上是将网页抓取的门槛提高,要求编写爬虫程序的开发者不得不修改浏览器内核的C++代码,重新编译一个浏览器,并且,以上几点特征是对浏览器内核的改动其实并不小,如果你曾尝试过编译Blink内核或Gecko内核你会明白这对于一个“脚本小子”来说有多难~

更进一步,我们还可以基于浏览器的 UserAgent 字段描述的浏览器品牌、版本型号信息,对js运行时、DOM和BOM的各个原生对象的属性及方法进行检验,观察其特征是否符合该版本的浏览器所应具备的特征。

这种方式被称为 浏览器指纹检查 技术,依托于大型web站对各型号浏览器api信息的收集。而作为编写爬虫程序的进攻一方,则可以在 Headless Browser 运行时里预注入一些js逻辑,伪造浏览器的特征。

另外,在研究浏览器端利用js api进行 Robots Browser Detect 时,我们发现了一个有趣的小技巧,你可以把一个预注入的js函数,伪装成一个Native Function,来看看下面代码:

var fakeAlert = (function(){}).bind(null);
console.log(window.alert.toString()); // function alert() { [native code] }
console.log(fakeAlert.toString()); // function () { [native code] }

爬虫进攻方可能会预注入一些js方法,把原生的一些api外面包装一层proxy function作为hook,然后再用这个假的js api去覆盖原生api。如果防御者在对此做检查判断时是基于把函数toString之后对[native code]的检查,那么就会被绕过。所以需要更严格的检查,因为bind(null)伪造的方法,在toString之后是不带函数名的,因此你需要在toString之后检查函数名是否为空。

这个技巧有什么用呢?这里延伸一下,反抓取的防御者有一种Robot Detect的办法是在js运行时主动抛出一个alert,文案可以写一些与业务逻辑相关的,正常的用户点确定按钮时必定会有一个1s甚至更长的延时,由于浏览器里alert会阻塞js代码运行(实际上在v8里他会把这个isolate上下文以类似进程挂起的方式暂停执行),所以爬虫程序作为攻击者可以选择以上面的技巧在页面所有js运行以前预注入一段js代码,把alert、prompt、confirm等弹窗方法全部hook伪造。如果防御者在弹窗代码之前先检验下自己调用的alert方法还是不是原生的,这条路就被封死了。

反爬虫的银弹

目前的反抓取、机器人检查手段,最可靠的还是验证码技术。但验证码并不意味着一定要强迫用户输入一连串字母数字,也有很多基于用户鼠标、触屏(移动端)等行为的行为验证技术,这其中最为成熟的当属Google reCAPTCHA,基于机器学习的方式对用户与爬虫进行区分。

基于以上诸多对用户与爬虫的识别区分技术,网站的防御方最终要做的是封禁ip地址或是对这个ip的来访用户施以高强度的验证码策略。这样一来,进攻方不得不购买ip代理池来抓取网站信息内容,否则单个ip地址很容易被封导致无法抓取。抓取与反抓取的门槛被提高到了ip代理池经济费用的层面。

机器人协议

除此之外,在爬虫抓取技术领域还有一个“白道”的手段,叫做robots协议。Allow和Disallow声明了对各个UA爬虫的抓取授权。

不过,这只是一个君子协议,虽具有法律效益,但只能够限制那些商业搜索引擎的蜘蛛程序,你无法对那些“野爬爱好者”加以限制。

写在最后

对网页内容的抓取与反制,注定是一个魔高一尺道高一丈的猫鼠游戏,你永远不可能以某一种技术彻底封死爬虫程序的路,你能做的只是提高攻击者的抓取成本,并对于未授权的抓取行为做到较为精确的获悉。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python通过pil模块获得图片exif信息的方法
Mar 16 Python
python返回昨天日期的方法
May 13 Python
python基于右递归解决八皇后问题的方法
May 25 Python
基于Python实现的微信好友数据分析
Feb 26 Python
DataFrame 将某列数据转为数组的方法
Apr 13 Python
浅谈python中真正关闭socket的方法
Dec 18 Python
解决pycharm运行程序出现卡住scanning files to index索引的问题
Jun 27 Python
解决Python3 控制台输出InsecureRequestWarning问题
Jul 15 Python
python中pygame安装过程(超级详细)
Aug 04 Python
Python 50行爬虫抓取并处理图灵书目过程详解
Sep 20 Python
解决pip install psycopg2出错问题
Jul 09 Python
解决python3安装pandas出错的问题
May 20 Python
python 使用装饰器并记录log的示例代码
Jul 12 #Python
如何使用python爬虫爬取要登陆的网站
Jul 12 #Python
Pycharm使用之设置代码字体大小和颜色主题的教程
Jul 12 #Python
python增加图像对比度的方法
Jul 12 #Python
Python 控制终端输出文字的实例
Jul 12 #Python
在Django的View中使用asyncio的方法
Jul 12 #Python
检测python爬虫时是否代理ip伪装成功的方法
Jul 12 #Python
You might like
Classes and Objects in PHP5-面向对象编程 [1]
2006/10/09 PHP
一个高ai的分页函数和一个url函数
2006/10/09 PHP
php 分库分表hash算法
2009/11/12 PHP
PHP连接MySQL查询结果中文显示乱码解决方法
2013/10/25 PHP
php除数取整示例
2014/04/24 PHP
php使用正则过滤js脚本代码实例
2014/05/10 PHP
PHP实现绘制3D扇形统计图及图片缩放实例
2014/10/01 PHP
php+mysqli使用面向对象方式查询数据库实例
2015/01/29 PHP
Yii2框架引用bootstrap中日期插件yii2-date-picker的方法
2016/01/09 PHP
PHP获取表单数据与HTML嵌入PHP脚本的实现
2017/02/09 PHP
实例讲解PHP验证邮箱是否合格
2019/01/28 PHP
关于juqery radio写法的兼容性问题(新老版本jquery)
2010/06/14 Javascript
jquery动态加载图片数据练习代码
2011/08/04 Javascript
父页面显示遮罩层弹出半透明状态的dialog
2014/03/04 Javascript
手机端网页点击链接触发自动拨打或保存电话的示例代码
2014/08/15 Javascript
浅谈javascript实现八大排序
2015/04/27 Javascript
PHP结合jQuery实现的评论顶、踩功能
2015/07/22 Javascript
jQuery动态添加与删除tr行实例代码
2016/10/18 Javascript
jQuery简单自定义图片轮播插件及用法示例
2016/11/21 Javascript
使用BootStrap实现悬浮窗口的效果
2016/12/13 Javascript
JS实现获取毫秒值及转换成年月日时分秒的方法
2018/08/15 Javascript
简谈创建React Component的几种方式
2019/06/15 Javascript
使用PYTHON接收多播数据的代码
2012/03/01 Python
Python实现一个简单的MySQL类
2015/01/07 Python
Python二叉搜索树与双向链表转换实现方法
2016/04/29 Python
不管你的Python报什么错,用这个模块就能正常运行
2018/09/14 Python
Python 实现将大图切片成小图,将小图组合成大图的例子
2020/03/14 Python
详解利用python识别图片中的条码(pyzbar)及条码图片矫正和增强
2020/11/17 Python
HTML5 Canvas图像模糊完美解决办法
2018/02/06 HTML / CSS
使用phonegap操作数据库的实现方法
2017/03/31 HTML / CSS
学生党员的自我评价范文
2014/03/01 职场文书
餐厅总厨求职信
2014/03/04 职场文书
2014年人事工作总结范文
2014/11/19 职场文书
python控制台打印log输出重复的解决方法
2021/05/14 Python
拒绝盗图!教你怎么用python给图片加水印
2021/06/04 Python
VUE递归树形实现多级列表
2022/07/15 Vue.js