以前在学校曾经用过request+xpath的方法作过一些爬虫脚原本玩,从ios正式转前端以后,出于兴趣,我对爬虫和反爬虫又作了一些了解,而且作了一些爬虫攻防的实践。
咱们在爬取网站的时候,都会遵照 robots 协议,在爬取数据的过程当中,尽可能不对服务器形成压力。但并非全部人都这样,网络上仍然会有大量的恶意爬虫。对于网络维护者来讲,爬虫的肆意横行不只给服务器形成极大的压力,还意味着本身的网站资料泄露,甚至是本身刻意隐藏在网站的隐私的内容也会泄露,这也就是反爬虫技术存在的意义。
下面开始个人攻防实践。css
先从最基本的requests开始。requests是一经常使用的http请求库,它使用python语言编写,能够方便地发送http请求,以及方便地处理响应结果。这是一段抓取豆瓣电影内容的代码。前端
import requests from lxml import etree url = 'https://movie.douban.com/subject/1292052/' data = requests.get(url).text s=etree.HTML(data) film=s.xpath('//*[@id="content"]/h1/span[1]/text()') print(film)
代码的运行结果,会输出node
['肖申克的救赎 The Shawshank Redemption']
这就是最简单的完整的爬虫操做,经过代码发送网络请求,而后解析返回内容,分析页面元素,获得本身须要的东西。
这样的爬虫防起来也很容易。使用抓包工具看一下刚才发送的请求,再对比一下浏览器发送的正常请求。能够看到,二者的请求头差异很是大,尤为requests请求头中的user-agent,赫然写着python-requests。这就等因而告诉服务端,这条请求不是真人发的。服务端只须要对请求头进行一下判断,就能够防护这一种的爬虫。
固然requests也不是这么没用的,它也支持伪造请求头。以user-agent为例,对刚才的代码进行修改,就能够很容易地在请求头中加入你想要加的字段,假装成真实的请求,干扰服务端的判断。python
import requests from lxml import etree user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = { 'User-Agent' : user_agent } url = 'https://movie.douban.com/subject/1292052/' data = requests.get(url,headers=headers).text s=etree.HTML(data) film=s.xpath('//*[@id="content"]/h1/span[1]/text()') print(film)
现阶段,就网络请求的内容上来讲,爬虫脚本已经和真人同样了,那么服务器就要从别的角度来进行防护。
有两个思路,第一个,分析爬虫脚本的行为模式来进行识别和防护。
爬虫脚本一般会很频繁的进行网络请求,好比要爬取豆瓣排行榜top100的电影,就会连续发送100个网络请求。针对这种行为模式,服务端就能够对访问的 IP 进行统计,若是单个 IP 短期内访问超过设定的阈值,就给予封锁。这确实能够防护一批爬虫,可是也容易误伤正经常使用户,而且爬虫脚本也能够绕过去。
这时候的爬虫脚本要作的就是ip代理,每隔几回请求就切换一下ip,防止请求次数超过服务端设的阈值。设置代理的代码也很是简单。ios
import requests proxies = { "http" : "http://111.155.124.78:8123" # 代理ip } user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = { 'User-Agent' : user_agent } url = 'https://movie.douban.com/subject/1292052/' res = requests.get(url = http_url, headers = headers, proxies = proxies)
第二个思路,经过作一些只有真人能作的操做来识别爬虫脚本。最典型的就是以12306为表明的验证码操做。
增长验证码是一个既古老又至关有效果的方法,可以让不少爬虫望风而逃。固然这也不是万无一失的。通过多年的发展,用计算机视觉进行一些图像识别已经不是什么新鲜事,训练神经网络的门槛也愈来愈低,而且有许多开源的计算机视觉库能够无偿使用。例如能够在python中引入的tesseract,只要一行命令就能进行验证码的识别。web
import pytesseract from PIL import Image ... #get identifying code img ... im=Image.open('code.png') result = pytesseract.image_to_string(im)
再专业一点的话,还能够加上一些图像预处理的操做,好比降噪和二值化,提升验证码的识别准确率。固然要是验证码本来的干扰线, 噪点都比较多,甚至还出现了人类肉眼都难以辨别的验证码(12306),计算机识别的准确度也会相应降低一些。但这种方法对于真实的人类用户来讲实在是太不友好了,属因而杀敌一千自损八百的作法。后端
验证码的方法虽然防爬效果好,可是对于真人实在是不够友好,开发人员在优化验证操做的方面也下了不少工夫。现在,不少的人机验证操做已经再也不须要输入验证码,有些只要一下点击就能够完成,有些甚至不须要任何操做,在用户不知道的状况下就能完成验证。这里其实包含了不一样的隐形验证方法。
有些隐形验证采用了基于JavaScript的验证手段。这种方法主要是在响应数据页面以前,先返回一段带有JavaScript 代码的页面,用于验证访问者有无 JavaScript 的执行环境,以肯定使用的是否是浏览器。例如淘宝、快代理这样的网站。一般状况下,这段JS代码执行后,会发送一个带参数key的请求,后台经过判断key的值来决定是响应真实的页面,仍是响应伪造或错误的页面。由于key参数是动态生成的,每次都不同,难以分析出其生成方法,使得没法构造对应的http请求。
有些则更加高级一些,经过检测出用户的浏览习惯,好比用户经常使用 IP 或者鼠标移动状况等,而后自行判断人机操做。这样就用一次点击取代了繁琐的验证码,并且实际效果还更好。
对于这类的反爬手段,就轮到selenium这个神器登场了。selenium是一个测试用的库,能够调用浏览器内核,也就是说能够打开一个真的浏览器,而且能够手动进行操做。那就完美能够完美应对上述两种隐形验证手段。
selenium的使用也很简单,能够直接对页面元素进行操做。配合根据页面元素等待页面加载完成的时延操做,基本上把人浏览页面的过程整个模拟了一遍。并且由于selenium会打开一个浏览器,因此若是有点击的验证操做,通常这种操做也就在开始的登陆页会有,人来点一下就是了。浏览器
from selenium import webdriver browser = webdriver.Chrome() browser.get("url") #得到dom节点 node = browser.find_elements_by_id("id") nodes = browser.find_elements_by_css_selector("css-selector") nodelist = browser.find_elements_by_class_name("class-name") #操做dom元素 browser.find_element_by_xpath('xpath-to-dom').send_keys('password') browser.find_element_by_xpath('xpath-to-dom').click() #等待页面加载 locator = (By.CLASS_NAME, 'page-content') try: WebDriverWait(driver, 10, 0.5).until(EC.presence_of_element_located(locator)) finally: driver.close()
这么看起来仿佛selenium就是无解的了,实际上并非。较新的智能人机验证已经把selenium列入了针对目标中,使得即便手动点击进行人机验证也会失败。这是怎么作的呢?事实上,这是对于浏览器头作了一次检测。若是打开selenium的浏览器控制台输入window.navigator.webdriver
,返回值会是“true”。而在正常打开的浏览器中输入这段命令,返回的会是“undefined”。
在这里,我找到了关于webdriver的描述:navigator.webdriver
)。能够看到,webdriver属性就是用来表示用户代理是否被自动化控制,也就是这个属性暴露了selenium的存在,人机验证就没法经过。并且,这个属性仍是只读的,那么就不能直接修改。固然硬要改也不是不行,经过修改目标属性的get方法,达到属性修改的目的。
这时的webdriver属性就是undefined了,而后再进行智能人机验证,就能够经过了。但这是治标不治本的,此时若是浏览器打开了新的窗口,或者点击连接进入新的页面,咱们会发现,webdriver又变回了true。固然,在每次打开新页面后都输入这段命令也能够,不过事实上,虽然点击验证能够被绕过去,但若是直接在页面中加入检测webdriver的JS代码,一打开页面就执行,那么在你改webdriver以前,网站已经知道你究竟是不是爬虫脚本了。服务器
道高一尺,魔高一丈。事实上即便这样的反爬手段,也仍是能够绕过去。在启动Chromedriver以前,为Chrome开启实验性功能参数excludeSwitches,它的值为['enable-automation'],像这样网络
from selenium.webdriver import Chrome from selenium.webdriver import ChromeOptions option = ChromeOptions() option.add_experimental_option("excludeSwitches", ["enable-automation"]) driver = Chrome(options=option) driver.get('url')
这时候,无论怎么打开新页面,webdriver都会是undefined。对于这个级别的爬虫脚本,还不知道要怎么防护,检测的成本过高了。
不过,事实上,换个思路,还有一些有趣的反爬方法。好比猫眼电影的实时票房和起点中文网,在浏览器里能看到内容,可是打开网页代码一看,全变成了方块。这就是一种很好地反爬方法,简单地说就是后端搭一套字体生成接口,随机生成一个字体,而后返回这个字体文件,以及各个数字的unicode对应关系,前端页面进行数据填充,就能够隐藏敏感数据。还有些充分利用了css进行的反爬,脑洞更大。搞两套数据,显示的时候用css定位将真实的覆盖假的。或者搞一些干扰字符,显示的时候将opacity设为0进行隐藏。甚至还有设置一个背景,让它和显示的内容拼接在一块儿,成为真正要展现的内容。这些都是很是有趣的反爬手段。不过对于前端来讲,毕竟全部的数据和代码,都给到了客户端,爬虫脚本老是能想出办法来爬到数据,各类反爬的手段,也就是加大爬数据的难度而已。主要仍是要自觉,拒绝恶意爬虫。