爬虫攻防战:反爬与反反爬全解析
反爬与反反爬
常见的反爬手段和解决思路
1 服务器反爬的原因
-
爬虫占总PV(PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv)比例较高,这样浪费钱(尤其是三月份爬虫)。 三月份爬虫是个什么概念呢?每年的三月份我们会迎接一次爬虫高峰期,有大量的硕士在写论文的时候会选择爬虫一些任务时,并进行舆情分析。因为五月份交论文,所以嘛,大家都是读过书的,你们懂的,前期各种botA、LOL,到了三月份了,来不及了,赶紧抓数据,四月份分析一下,五月份交论文,就是这么个节奏。
-
公司可免费查询的资源被批量抓走,丧失竞争力,这样少赚钱。 数据可以在非登录状态下直接被查询。如果强制登陆,那么可以通过封杀账号的方式让对方付出代价,这也是很多网站的做法。但是不强制对方登录。那么如果没有反爬虫,对方就可以批量复制的信息,公司竞争力就会大大减少。竞争对手可以抓到数据,时间长了用户就会知道,只需要去竞争对手那里就可以了,没必要来我们网站,这对我们是不利的。
-
快传爬虫成功的几率小 爬虫在国内还是个擦边球,就是有可能可以起玩成功,也可能完全无效。所以还是需要用技术手段来做最后的保障。
2 服务器常反什么样的爬虫
-
十分低级的应届毕业生 应届毕业生的爬虫通常简单粗暴,根本不管服务器压力,加上人数不可预测,很容易把站点弄挂。
-
十分低级的创业小公司 现在的创业公司越来越多,发现自己手头没有数据。怎么办?写爬虫爬啊。于是就有了不计其数的小爬虫,出于公司生死存亡的考虑,不断爬取数据。
-
不小心写错了没人去停止的失控小爬虫 有些网站已经做了相应的反爬,但是爬虫依然孜孜不倦地爬取。什么意思呢?就是说,他们根本爬不到任何数据,除了httpcode是200以外,一切都是不对的,可是爬虫依然不停止这个很可能就是一些托管在某些服务器上的小爬虫,已经无人认领了,依然在辛勤地工作着。
-
成型的商业对手 这个是最大的对手,他们有技术、有钱,要什么有什么,如果和你死磕,你就只能硬着头皮和他死磕。
-
抽风的搜索引擎 大家不要以为搜索引擎都是好人,他们也有抽风的时候,而且一抽风就会导致服务器性能下降,请求量跟网络攻击没什么区别。
3 反爬虫领域常见的一些概念
因为反爬虫暂时是个较新的领域,因此有些定义要自己下:
-
爬虫:使用任何技术手段,批量获取网站信息的一种方式。关键在于批量。
-
反爬虫:使用任何技术手段,阻止别人批量获取自己网站信息的一种方式。关键也在于批量。
-
误伤:在反爬虫的过程中,错误的将普通用户识别为爬虫。误伤率高的反爬虫策略,效果再好也不能用。
-
拦截:成功地阻止爬虫访问。这里会有拦截率的概念。通常来说,拦截率越高的反爬虫策略,误伤的可能性就越高。因此需要做个权衡。
-
资源:机器成本与人力成本的总和。 这里要切记,人力成本也是资源,而且比机器更重要。因为,根据摩尔定律,机器越来越便宜。而根据IT行业的发展趋势,程序员工资越来越贵。因此,通常服务器反爬就是让爬虫工程师加班才是王道,机器成本并不是特别值钱。
4 反反爬的三个方向
-
基于身份识别进行反反爬
-
基于爬虫行为进行反反爬
-
基于数据加密进行反反爬
5 常见基于身份识别进行反反爬
5.1 通过headers字段来反爬
headers中有很多字段,这些字段都有可能会被对方服务器拿过来进行判断是否为爬虫
5.1.1 通过headers中的User-Agent字段来反爬
-
反爬原理:爬虫默认情况下没有User-Agent,而是使用模块默认设置
-
解决方法:请求之前添加User-Agent即可;更好的方式是使用User-Agent池来解决(收集一堆User-Agent的方式,或者是随机生成User-Agent)
5.1.2 通过referer字段或者是其他字段来反爬
-
反爬原理:爬虫默认情况下不会带上referer字段,服务器端通过判断请求发起的源头,以此判断请求是否合法
-
解决方法:添加referer字段
5.1.3 通过cookie来反爬
-
反爬原因:通过检查cookies来查看发起请求的用户是否具备相应权限,以此来进行反爬
-
解决方案:进行模拟登陆,成功获取cookies之后在进行数据爬取
5.2 通过请求参数来反爬
请求参数的获取方法有很多,向服务器发送请求,很多时候需要携带请求参数,通常服务器端可以通过检查请求参数是否正确来判断是否为爬虫
5.2.1 通过从html静态文件中获取请求数据
-
反爬原因:通过增加获取请求参数的难度进行反爬
-
解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系
5.2.2 通过发送请求获取请求数据
-
反爬原因:通过增加获取请求参数的难度进行反爬
-
解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系,搞清楚请求参数的来源
5.2.3 通过生成请求参数
-
反爬原理:jst生成了请求参数
-
解决方案:分析js,观察加密的实现过程,通过js2py获取js的执行结果,或者使用selenium/DrissionPage来实现
5.2.4 通过验证码来反爬
-
反爬原理:对方服务器通过弹出验证码强制验证用户浏览行为
-
解决方案:打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐
6 常见基于爬虫行为进行反反爬
6.1 基于请求频率或总请求数量
爬虫的行为与普通用户有着明显的区别,爬虫的请求频率与请求次数要远高于普通用户
6.1.1 通过请求p/账号单位时间内总请求数量进行反爬
-
反爬原理:正常浏览器请求网站,速度不会太快,同一个ip/账号大量请求了对方服务器,有更大的可能性会被识别为爬虫
-
解决方案:对应的通过购买高质量的ip的方式能够解决问题/购买多个多账号
6.1.2 通过同一ip/账号请求之间的间隔进行反爬
-
反爬原理:正常人操作浏览器浏览网站,请求之间的时间间隔是随机的,而爬虫前后两个请求之间时间间隔通常比较固定的时间间隔较短,因此可以用来做反爬
-
解决方案:请求之间进行随机等待,模拟真实用户操作,在添加时间间隔后,为了能够高速获取数据,尽量使用代理池,如果是账号,则将账号请求之间设置随机休眠
6.1.3 通过对请求p/账号每天请求次数设置阈值进行反爬
-
反爬原理:正常的浏览行为,其一天的请求次数是有限的,通常超过某一个值,服务器就会拒绝响应
-
解决方案:对应的通过购买高质量的ip的方法多账号,同时设置请求间随机休眠
6.2 根据爬虫行为进行反爬,通常在爬虫步骤上做分析
6.2.1 通过jst实现跳转来反爬
-
反爬原理:jst实现页面跳转,无法在源码中获取下一页url
-
解决方案:多次抓包获取条状url,分析规律
6.2.2 通过蜜蜂陷阱获取爬虫p(或者代理)p,进行反爬
反爬原理:在爬虫获取链接进行请求的过程中,爬虫会根据正则,xpath,css等方式进行后续链接的提取,此时服务器端可以设置一个陷阱url,会被提取规则获取,但是正常用户无法获取,这样就能有效的区分爬虫和正常用户
比如我们的百度贴吧里面,将下一页注释起来
解决方法: 完成爬虫的编写之后,使用代理批量爬取测试/仔细分析响应内容结构,找出页面中存在的陷阱
6.2.3 通过假数据反爬
反爬原理:向返回的响应中添加假数据污染数据库,通常不会被正常用户看到 解决方法: 长期运行,核对数据库中数据同实际页面中数据对应情况,如果存在问题/仔细分析响应内容
6.2.4 阻塞任务队列
反爬原理:通过生成大量垃圾url,从而阻塞任务队列,降低爬虫的实际工作效率 解决方法: 观察运行过程中请求响应状态/仔细分析源码获取垃圾url生成规则,对URL进行过滤
6.2.5 阻塞网络IO
反爬原理:发送请求获取响应的过程实际上就是下载的过程,在任务队列中混入一个大文件的url,当爬虫在进行该请求时将会占用网络io,如果是有多线程则会占用线程 解决方法: 观察爬虫运行状态/多线程对请求线程计时/发送请求钱
6.2.6 运维平台综合审计
反爬原理:通过运维平台进行综合管理,通常采用复合型反爬虫策略,多种手段同时使用 解决方法: 仔细观察分析,长期运行测试目标网站,检查数据采集速度,多方面处理
7 常见基于数据加密进行反反爬
7.1 对响应中含有的数据进行特殊化处理
通常的特殊化处理主要指的就是css数据偏移/自定义字体/数据加密/数据图片/特殊编码格式等
7.1.1 通过自定义字体来反爬
下图来自猫眼电影电脑版
反爬思路: 使用自有字体文件 解决思路:切换到手机版/解析字体文件进行翻译
7.1.2 通过css来反爬
下图来自猫眼去哪儿电脑版
解决思路:计算css的偏移
7.1.3 通过js动态生成数据进行反爬
-
反爬原理:通过js动态生成
-
解决思路:解析关键词,获得数据生成流程,模拟生成数据
7.1.4 通过数据图片化反爬
-
反爬原理:把数据用图片的形式展示
-
解决思路:通过使用图片解析引擎从图片中解析数据
7.1.5 通过编码格式进行反爬
-
反爬原理:不适用默认编码格式,在获取响应之后通常爬虫使用utf-8格式进行解码,此时解码结果将会是乱码或者报错
-
解决思路:根据源码进行多格式解码,或者真正的解码格式
小结
-
掌握 常见的反爬手段、原理以及应对思路
8.如何应对反爬
基础反爬应对策略
8.1 示例:伪装请求头
import requests headers = {'User-Agent': 'Mozilla/5.0 (windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36','Referer': 'https://www.example.com/','Accept-Language': 'zh-CN,zh:q=0.9' } response = requests.get('https://www.example.com/api/data', headers=headers)
8.2 IP限制应对策略
示例:使用代理IP池
import random import requests proxy_pool = [{'http': 'http://123.123.123.123:8080'},{'http': 'http://124.124.124.124:8888'},{'http': 'http://125.125.125.125:3128'} ] proxy = random.choice(proxy_pool) response = requests.get('https://www.example.com', proxies=proxy)
8.3 动态内容加载应对
示例:使用Selenium模拟浏览器
from selenium import webdriver from selenium.webdriver.chrome.options import Options import time options = Options() # 无头模式 options.add_argument('--headless') driver = webdriver.Chrome(options=options) driver.get('https://www.baidu.com') # 等待JavaScript加载 time.sleep(2) content = driver.page_source print(content) driver.quit()
8.4 高级反爬:字体反爬
• 什么是字体反爬 网页开发者自己创造一种字体,在字体中每个文字都有其代号,以后在网页中不会直接显示这个文字的最终效果,而是显示他的代号,因此即使获取到了网页中的文本内容,也只是获取到文字的代号,而不是文字本身。 简单的说,字体反爬就是浏览器页面上的字符和调试窗口或者源码中的内容,显示的不一样,这就是字体反爬。
字体反爬原理 在之前,网站开发者设计网页时,只能使用公用的字体来展示网页中的数据。但随着CSS样式或者json的深入开发,网站开发者可以将自己的字体放到服务器中。当用户在访问Web界面时,对应的字体就会被浏览器自动下载到用户的计算机中,然后通过CSS样式或者json进行调用。之后,通过一种映射关系,使得网页中的源数据变为真正的数据进行展示。
这样就使得网站开发者进行网页设计时,只需要使用特殊字符进行占位即可,不需要将真正的数据放到页面中去。如果爬虫程序如果不知道这种映射关系的话,就无法从字体中获取正确的数据,从而实现反爬虫。
8.4.1使用fontTools进行加密字体的解析
1.下载字体:首先我们要打开网站进入抓包工具,然后找到对于的字体,右键在新标签页中打开,会进行下载对应的字体资源
2.加载文件:使用footTools库加载字体资源,并且保存为xml的格式
# 导入fontTools库
from fontTools.ttLib import TTFont
# 加载字体
font = TTFont('font.woff')
# 保存为xml文件
font.saveXML('font.xml')
3.分析xml文件:点击保持的xml文件后,先使用shift+ctrl+'-'号,进行把文本缩起来,然后我们只需要看GlyphOrder和cmap即可,要注意的是GlyphOrder的name码点要往前进一位,也就是name为.notdef的不需要,id为0的对于*
,id为1的对应*.1
,以此类推
我们观察发现,我们的cmap也有name,code也就是我们的编码,我们可以显示有chr()转换成utf-8的编码格式,我们观察发现,里面的0x31表示1,0x36表示6
4.结论:我们需要通过map里面的code找到对应的name,然后再通过name也就码点去找到对应的id,比如这里我们知道了中文字的code=0x37,那么码点就是seven,再通过码点去找id,并且前面说了码点在GlyphOrder里面要往前进一位
9. 综合案例
目标1:选哪几网-土地-解析字体
目标网址: 土地市场招拍挂信息|土地交易信息|土地拍卖信息-选哪儿 要求:
-
获取目标数据:发送请求获取页面或接口数据。
-
提取字体文件URL:从页面或接口数据中提取字体文件的URL。
-
下载字体文件:下载字体文件到本地。
-
解析字体文件:使用字体解析工具解析字体文件,获取字符映射关系。
-
构建映射规则:根据解析结果,手动构建字符到实际数字或字母的映射规则。
-
解码数据:使用映射规则将加密字符解码为实际数据。
from fontTools.ttLib import TTFont # 导入字体处理库
import re # 导入正则表达式库
def decode_font_string(html_str):""" 解答字体反爬字符串,将特殊字符转换为对应数字:param html_str: 需要解答的含特殊字符的字符串:return: 解答后的数字字符串"""# 1. 加载并解析字体文件font = TTFont('font.woff') # 读取字体文件(WOFF格式)font.saveXML('font.xml') # 将字体转换为XML格式方便解析# 2. 读取生成的XML文件内容with open('font.xml', 'r') as f: # 打开XML文件document = f.read() # 读取文件内容# 3. 提取字符编码映射表(code到字符名的映射)# 使用正则表达式匹配所有<map>标签,获取编码和名称cmap = re.findall(r'<map code="(.*?)" name="(.*?)"/>', document)# 4. 构建字符名到实际字符的映射字典char_name_map = {} # 初始化映射字典for code, name in cmap: # 遍历每个映射关系# 将十六进制code转换为实际字符# 如0x30 -> chr(48) -> '0'char_name_map[name] = chr(eval(code))# 5. 提取字形ID映射表(GlyphID到字符名的映射)# 使用正则表达式匹配所有<GlyphID>标签# [1:]跳过第一个默认字形(通常是.notdef)glyph_ids = re.findall(r'<GlyphID id="(.*?)" name="(.*?)"/>', document)[1:]# 6. 构建最终字符到数字的映射字典char_to_num = {} # 初始化字符到数字的映射字典for glyph_id, glyph_name in glyph_ids: # 遍历每个字形ID映射# 字形ID就得到实际数字(因为ID从0开始)num = int(glyph_id) - 1# 通过字符名映射获取真实字符real_char = char_name_map[glyph_name]# 添加到映射字典:字符 -> 数字char_to_num[real_char] = num# 7. 逐个字符转换原始字符串result = [] # 初始化结果列表for char in html_str: # 遍历输入字符串的每个字符if char == '.': # 如果是小数点result.append('.') # 直接添加到结果else:# 将字符转换为映射表中对应的数字result.append(str(char_to_num[char]))# 将结果列表拼接成字符串并返回return ''.join(result)
if __name__ == '__main__':html_str = '构.相们' # 输入字符串print(decode_font_string(html_str)) # 输出解密后的数字字符串