爬虫与反爬、加密算法
网络爬虫
网络爬虫,是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成。
但是当网络爬虫被滥用后,互联网上就出现太多同质的东西,原创得不到保护。
于是,很多网站开始反网络爬虫,想方设法保护自己的内容。
他们根据ip访问频率,浏览网页速度,账户登录,输入验证码,flash封装,ajax混淆,js加密,图片等技术,来应对网络爬虫。
防的一方不惜成本,迫使抓的一方在考虑成本效益后放弃。
抓的一方不惜成本,防的一方在考虑用户流失后放弃。
常见的反爬策略
知识点一: User-Agent + Referer检测
User-Agent 是HTTP协议的中的一个字段, 其作用是描述发出HTTP请求的终端的一些信息。使得服务器能够识别客户使用的操作系统及版本、CPU类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。服务器通过这个字段就可以知道访问网站的是什么人。对于不是正常浏览器的用户进行屏蔽。
解决方案: 伪装浏览器的User-Agent,因为每个浏览器的User-Agent不一样,并且所有的用户都能使用浏览器。所有每次请求的时候条件浏览器的User-Agent,就能解决UA检测。
Referer 是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的。例如有一些图片网站在你请求图片的时候,就会检测你的Referer值,如果Referer不符合,不会返回正常的图片。
解决方案:在检测referer的请求中,携带符合的referer值。
知识点二:js混淆和渲染
所谓 JavaScript 混淆,基本就是:
1.去掉一些实际没有调用的函数。
2.将零散的变量声明合并。
3.逻辑函数的精简。
4.变量名的简化。具体要看不同的压缩工具的考虑优劣。常见的有UglifyJS、JScrambler等工具。
js渲染其实就是对HTML页面的修改。比如有一些网页本身没有返回数据,数据是经过js加载之后添加到HTML当中的。当遇到这种情况的时候,我们要知道爬虫是不会执行JavaScript操作。所以需要用其他的方法处理。
解决方案:
1.通过阅读网站js源码,找到关键的代码,并用python实现。
2.通过阅读网站js源码,找到关键的代码,用PyV8等库直接执行js代码。
3.通过selenium库直接模拟浏览器环境。
知识点三: IP限制频次
WEB系统都是走http协议跟WEB容器连通的,每次请求至少会产生一次客户端与服务器的tcp连接。对于服务端来说可以很清楚的查看到,一个ip地址在单位时间内发起的请求。当请求数超过一定的值之后,就可判断为非正常的用户请求。
解决方案:
1.自行设计ip代理池,通过轮换的方式,每次请求携带不同的代理地址。
2.ADSL动态拨号他有个独有的特点,每拨一次号,就获取一个新的IP。也就是它的IP是不固定的。
知识点四:验证码
验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。
这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答CAPTCHA的问题,所以回答出问题的用户就可以被认为是人类。
解决方案:
1.手动识别验证码
2.pytesseract识别简单的验证码
3.对接打码平台
加密算法
加密算法的简介
据记载,公元前400年,古希腊人发明了置换密码。1881年世界上的第一个电话保密专利出现。在第二次世界大战期间,德国军方启用“恩尼格玛”密码机,密码学在战争中起着非常重要的作用。
随着信息化和数字化社会的发展,人们对信息安全和保密的重要性认识不断提高,于是在1997年,美国国家标准局公布实施了“美国数据加密标准(DES)”,民间力量开始全面介入密码学的研究和应用中,采用的加密算法有DES、RSA、SHA等。随着对加密强度需求的不断提高,近期又出现了AES、ECC等。
使用密码学可以达到以下目的:
保密性:防止用户的标识或数据被读取。
数据完整性:防止数据被更改。
身份验证:确保数据发自特定的一方。
加密算法介绍
加密算法分类
根据密钥类型不同将现代密码技术分为两类:对称加密算法(秘密钥匙加密)和非对称加密算法(公开密钥加密)。
对称钥匙加密系统是加密和解密均采用同一把秘密钥匙,而且通信双方都必须获得这把钥匙,并保持钥匙的秘密。
非对称密钥加密系统采用的加密钥匙(公钥)和解密钥匙(私钥)是不同的。
对称加密算法主要包含DES、3DES、AES等算法。
非对称加密算法主要包含RSA、DSA、ECC等算法
常用加密算法
DES:
全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法
入口参数有三个:Key、Data、Mode
Key为7个字节共56位,是DES算法的工作密钥;
Data为8个字节64位,是要被加密或被解密的数据;
Mode为DES的工作方式,有两种:加密或解密
3DES(即Triple DES)是DES向AES过渡的加密算法,
使用两个密钥,执行三次DES算法,
加密的过程是加密-解密-加密
解密的过程是解密-加密-解密
AES:
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),这个标准用来替代原先的DES
AES的区块长度固定为128 比特,密钥长度则可以是128,192或256比特 (16、24和32字节)
大致步骤如下:
1、密钥扩展(KeyExpansion),
2、初始轮(Initial Round),
3、重复轮(Rounds),每一轮又包括:SubBytes、ShiftRows、MixColumns、AddRoundKey,
4、最终轮(Final Round),最终轮没有MixColumns。
RSA:
公钥加密算法,一种非对称密码算法
公钥加密,私钥解密
3个参数:
rsa_n, rsa_e,message
rsa_n, rsa_e 用于生成公钥
message: 需要加密的消息
python加密库
PyCrypto是Python中密码学方面最有名的第三方软件包。可惜的是,它的开发工作于2012年就已停止。其他人还在继续发布最新版本的PyCrypto,如果你不介意使用第三方的二进制包,仍可以取得 Python 3.5 的相应版本。比如,可以在 Github 上找到了对应 Python 3.5 的 PyCrypto 二进制包。
幸运的是,有一个该项目的分支 PyCrytodome 取代了 PyCrypto 。为了在 Linux 上安装它,你可以使用以下 pip 命令:
pip install pycryptodome
在 Windows 系统上安装则稍有不同:
pip install pycryptodomex
位:比特,就是计算机里面的0或1
计算机最小的存储单位:字节(byte)
一个字节有8位(比特)
binascii库
a2b_hex :16进制转bytes
b2a_hex :把bytes字符串转16进制
a2b_base64 :把ascii字符转成二进制
b2a_base64 :把二进制转成ascii字符
DES加密实例
from Cryptodome.Cipher import DES import binascii def pad(text): while len(text)%8 != 0: #不是8的倍数,用空格补全成8的倍数 text += ' ' return text # 或者 # def pad(text): # text = text.encode() # result = len(text) %8 # if ( result != 0): #当字节数不是8的倍数时,用\0字节补全 # text = text + (b'\0'*(16-result)) # return text key = b'abcdefgh' # 密钥,一般是8位或16位 des = DES.new(key,DES.MODE_ECB) #参数 key:密钥 mode:模式一般是DES.MODE_ECB text = 'hello world' #要加密的文本 encrypto_text = des.encrypt(pad(text).encode()) # 加密 print('密文',binascii.b2a_hex(encrypto_text)) #把bytes转成16进制 # 如果不经过pad处理,会报错 :加密的数据不对,必须是密钥的位数的整数倍(此处是8的整数倍) data = des.decrypt(encrypto_text).rstrip() # 会多出来一些字符串,所以要rstrip一下 print('明文',data)
>>
密文 b'c720e6acbf9f18b072b2abe014f5a6ce' 明文 b'hello world'
AES加密实例
from Cryptodome.Cipher import AES from binascii import a2b_hex,b2a_hex class aescrypt(): def __init__(self,key): self.key = key.encode() #传入公钥 self.mode = AES.MODE_ECB #选定模式 self.aes = AES.new(self.key,self.mode) #创建一个aes对象 #这里的密钥长度必须是16、24或32,目前16位的就够用了 def aesencrypt(self,text): # 用公钥加密数据,得到密文,并转成16进制 # text = text.encode() # result = len(text) %16 # if ( result != 0): #当字节数不是16的倍数时,用\0字节补全 # text = text + (b'\0'*(16-result)) # self.encrypt_text = self.aes.encrypt(text) while len(text) % 16 !=0: #当需要加密的字符串长度不是16的倍数,在后面添加空格直到是16的倍数为止 text = text + ' ' self.encrypt_text = self.aes.encrypt(text.encode()) return b2a_hex(self.encrypt_text) def aesdecrypt(self,text): # 把密文转成字符串,然后用公钥解密,去掉多余的空格 self.decrypt_text = self.aes.decrypt(a2b_hex(text)) return self.decrypt_text.decode().rstrip(' ') if __name__ == '__main__': pr = aescrypt('abcdefghabcdefgh') en_text = pr.aesencrypt('hello world') print('密文:',en_text) de_text = pr.aesdecrypt(en_text) print('明文:',de_text) >> 密文: b'10ccb47c783a235845b23d23ce68c4f2' 明文: hello world
RSA加密实例
import rsa from binascii import a2b_hex,b2a_hex class rsacrypt(): def __init__(self): self.pub_key,self.priv_key = rsa.newkeys(1024)# 生成公钥和私钥 def rsa_encrypt(self,text): self.encrypt_text = rsa.encrypt(text.encode(),self.pub_key)# 此时是bytes return b2a_hex(self.encrypt_text) # 转成16进制密文 def rsa_decrypt(self,text): self.decrypt_text = rsa.decrypt(a2b_hex(text),self.priv_key) # 把16进制密文转成bytes,然后解密 return self.decrypt_text.decode() if __name__ == '__main__': rs = rsacrypt() text = "hello world" en_text = rs.rsa_encrypt(text) print('密文:',en_text) de_text = rs.rsa_decrypt(en_text) print('明文:',de_text) >> 密文: b'0ecaa6c11e95d1f720ae22d4d80280aa3f28052072773f608bba2c80ee93dfc9cbd5d47c76134c6fec0c26398807b0836e221fdb28eb20bee2d676ebb2c37671c926e11109aa19e1f1ee9e3f2e37e5218b0452390878a4d2aa557aa278462691ff08eababbd02707d7be8e25f6e6b814a88b107f56f8afbb6b7c4d14e9352808' 明文: hello world
补充:
MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致
import hashlib s = 'hello world' m = hashlib.md5() # 实例 m.update(s.encode()) # 加密的数据都是bytes类型 print(m.hexdigest()) # 加密后以16进制展示 >> 5eb63bbbe01eeed093cb22bb8f5acdc3
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。
原理
用64个字符来表示二进制数据的方法
A-Z a-z 0-9 + /
把3个字节(24位)的二进制数据进行拼接,按照6位进行分割,变成4个字节,如果二进制数据不能被3整除,余1个字节则在末尾有1个=,余2个字节则在末尾有2个=
转码过程例子:
3*8=4*6
内存1个字节占8位
转前: s 1 3
先转成ascii:对应 115 49 51
2进制: 01110011 00110001 00110011
6个一组(4组) 011100110011000100110011
然后才有后面的 011100 110011 000100 110011
然后计算机是8位8位的存数 6不够,自动就补两个高位0了
所有有了 高位补0
科学计算器输入 00011100 00110011 00000100 00110011
得到 28 51 4 51
查对下照表 c z E z
以“迅雷下载”为例: 很多下载类网站都提供“迅雷下载”的链接,其地址通常是加密的迅雷专用下载地址。
其实迅雷的“专用地址”也是用Base64"加密"的,其过程如下:
一、在地址的前后分别添加AA和ZZ
二、对新的字符串进行Base64编码
另: Flashget的与迅雷类似,只不过在第一步时加的“料”不同罢了,Flashget在地址前后加的“料”是[FLASHGET]
而QQ旋风的干脆不加料,直接就对地址进行Base64编码了
实例
import base64 s = 'hello world' r1 = base64.encodebytes(s.encode()) #编码 print(r1) #每57个字节有一个\n r2 = base64.b64encode(s.encode()) # 若不想有\n分隔,用b64encode print(r2) data = base64.b64decode(r1) # 解码 print(data) >> b'aGVsbG8gd29ybGQ=\n' b'aGVsbG8gd29ybGQ=' b'hello world'