爬虫中遇到过哪些厉害的反爬,以及骚操作反反爬?

本人python菜鸟一枚,专业相关。平日里喜欢爬一些电影电视剧还有其他,突然想请教一下各位小哥哥大神,平时你们遇到过哪些比较厉害的反爬技术,自己又是如…
关注者
59
被浏览
59,012

19 个回答

学习了前边的爬虫知识,大家一定爬取过很多的网站了,也一定被很多网站的各式各样的反爬机制劝退过。那么这些反爬机制如何来破解,大家也一定想破了头。本节课,我们来搞点不同寻常的有深度的事情——破解字体反爬!

大家看目录,发现我把字体反爬分了多个章节,可想而知字体反爬的“困难程度”,但是不要紧,我们会把目前的字体反爬技术一一给大家讲解!

一、扫盲

字体反爬,一种常见的反爬技术,一些网站采用了自定义的字体文件,在字体文件中给需要加密的符号指定一个十六进制编码,这些符号对应的编码被传输到前端,由前端根据字体文件内容解析并显示回正常的字符。

二、字体文件

常见的字体文件包括: ttf eot woff woff2 等, 我们一般在前端页面中通过 @font-face 定义字体样式(字体样式属于CSS样式)来构建与字体文件的映射,然后再将其设置到元素控件中去,并且我们也能够在前端页面的源代码中找到这些字体文件,但是也并不是所有的字体文件都会出现在前端页面的源代码中,部分网站还会将其放入到 API 接口中等等,那么接下来,我们先来讲解下比较简单的字体反爬——静态字体反爬!

三、实战

第一步:接口数据抓取

我们针对“某某二手车”进行爬虫操作,这是链接: guazi.com/ ,大家可以在“我要买车”这里随便选择一个品牌,我选择了“大众”。

这个二手车网站的数据都是通过 API 接口的形式传输的,所以大家要先找到这个网站的 API 接口,请看下图:

第二步:发现问题

找到这个接口以后呢,我们需要关注一些数据,仔细看,你是能够发现一些问题的:

看我在图中圈中的信息,我们发现有些关键性数据信息居然是加密过后的,这就意味着这个网站采用了字体反爬,那么按照我们的思路,首先就要想到去网页源代码或者 API 接口中找字体文件进行解密。

第三步:找字体文件

回到开发者工具的 Elements 选项卡中,先用快捷键 Ctrl + F 调出搜索框( Tips:很多工具都可以使用这个快捷键调出搜索框进行关键字检索),输入woff 查看反馈结果(注意,不一定只搜 woff,上面提到过的 ttf、eot 等关键词也可以搜索,不同网站用的字体文件可能不同),我们发现搜到了和 woff 关键字相匹配的一些信息,这个链接可能就是我们需要的,大家可以复制此处的链接(要先双击,再选中这部分内容),用浏览器打开,会下载一个字体文件。

这个字体文件我们先用这个网站打开: kekee000.github.io/font

看到上图的内容,基本就宣告这个字体文件找错了,但是别怕,多找找,网页源代码找不到就从 API 接口中再找找。发现从 API 接口中找到一个 woff2 的字体文件,我们下载下来看看,发现是数字,和前面就能联想到了一起了,基本正确无疑了!

第四步:代码编写

先把接口中需要的数据抓出来。

import requests
# 接口有点长,做个换行处理
API_URL = 'https://mapi.guazi.com/car-source/carList/pcList?' \
          'versionId=0.0.0.0&sourceFrom=wap&deviceId=5215c1f1-5b57-4568-9631-d36477f616ad' \
          '&osv=IOS&minor=dazhong&sourceType=&ec_buy_car_list_ab=&location_city=&district_id=' \
          '&tag=-1&license_date=&auto_type=&driving_type=&gearbox=&road_haul=&air_displacement=' \
          '&emission=&car_color=&guobie=&bright_spot_config=&seat=&fuel_type=&order=' \
          '&priceRange=0,-1&tag_types=&diff_city=&intention_options=&initialPriceRange=' \
          '&monthlyPriceRange=&transfer_num=&car_year=&carid_qigangshu=&carid_jinqixingshi=' \
          '&cheliangjibie=&page=1&pageSize=20&city_filter=12&city=12&guazi_city=12&qpres=&platfromSource=wap'
# 发送请求得到响应结果
resp = requests.get(url=API_URL)
# json 数据转字典
data = resp.json()
# 数据提取
car_data = data['data']['postList']
# 二手车概述、车辆时间、里程、首付、总价
car_info = [
    [car['title'], car['license_date'], car['road_haul'], car['first_pay'], car['price']] for car in car_data
print(car_info)

然后针对上方打印结果中加密数据,根据上方打开的字体文件构建映射。

# 字体映射
font_dict = {
    'uniE9CE': '0', 'uniE41D': '1', 'uniE630': '2', 'uniEAF2': '3',
    'uniE325': '4', 'uniE891': '5', 'uniEC4C': '6', 'uniE1D0': '7',
    'uniF88A': '7', 'uniE76E': '8', 'uniE52E': '9'
}

接下来就是知识积累与经验之谈了,大家在这里记好。看到 unixxxx 这种, uni 代表的就是 Unicode 编码,一般 Unicode 编码是十六进制,所以 unixxxx 还可以写作 0xxxxx,那么我们借助 Python 中的 hex 方法,将十进制转为十六进制检验下。

# 随便找一个加密信息,取其中的数值进行转换:
print(hex(58928))

这里打印的结果为:0xe630,发现正好能和映射中的 uniE630 对应,那么只需要使用代码稍加处理,便能够得到二手车的正确信息。

# 将上方字体映射字典中 unicode 编码改为二手车网站的 ‘&#十进制数值;’ 的形式
new_font_dict = {f'&#{int(key[3:], 16)};': value for key, value in font_dict.items()}
# print(new_font_dict)
# 使用字符串的替换操作来一次全局替换
for key, value in new_font_dict.items():
    car_info = str(car_info).replace(key, value)
# print(car_info)

经过验证,信息是没有问题的,所以再将 car_info 从字符串转回列表即可。

import ast
print(ast.literal_eval(car_info))

有小伙伴可能在 ast 模块这里有疑问,为什么不是用 eval,而是用 ast.literal_eval 呢,这是因为 eval 方法会自动执行恶意指令,如果你的字符串中存在恶意指令,那么恭喜你即将中招。所以我们用更加安全的 ast 模块中的 literal_eval 方法。

这就是本二手车网站的字体反爬的破解方式,你学会了吗!

四、完整代码

在这里提供了最标准的代码。

import requests
import ast
# 接口有点长,做个换行处理
API_URL = 'https://mapi.guazi.com/car-source/carList/pcList?' \
          'versionId=0.0.0.0&sourceFrom=wap&deviceId=5215c1f1-5b57-4568-9631-d36477f616ad' \
          '&osv=IOS&minor=dazhong&sourceType=&ec_buy_car_list_ab=&location_city=&district_id=' \
          '&tag=-1&license_date=&auto_type=&driving_type=&gearbox=&road_haul=&air_displacement=' \
          '&emission=&car_color=&guobie=&bright_spot_config=&seat=&fuel_type=&order=' \
          '&priceRange=0,-1&tag_types=&diff_city=&intention_options=&initialPriceRange=' \
          '&monthlyPriceRange=&transfer_num=&car_year=&carid_qigangshu=&carid_jinqixingshi=' \
          '&cheliangjibie=&page=1&pageSize=20&city_filter=12&city=12&guazi_city=12&qpres=&platfromSource=wap'
# 发送请求得到响应结果
resp = requests.get(url=API_URL)
# json 数据转字典
data = resp.json()
# 数据提取
car_data = data['data']['postList']
# 二手车概述、车辆时间、里程、首付、总价
car_info = [
    [car['title'], car['license_date'], car['road_haul'], car['first_pay'], car['price']] for car in car_data
print(car_info)
# 字体映射
font_dict = {
    'uniE9CE': '0', 'uniE41D': '1', 'uniE630': '2', 'uniEAF2': '3',
    'uniE325': '4', 'uniE891': '5', 'uniEC4C': '6', 'uniE1D0': '7',
    'uniF88A': '7', 'uniE76E': '8', 'uniE52E': '9'
# 正确信息解析
# 将上方字体映射字典中 unicode 编码改为二手车网站的 ‘&#十进制数值;’ 的形式
new_font_dict = {f'&#{int(key[3:], 16)};': value for key, value in font_dict.items()}
# print(new_font_dict)
# 使用字符串的替换操作来一次全局替换
for key, value in new_font_dict.items():
    car_info = str(car_info).replace(key, value)
# 再将 car_info 转回列表
print(ast.literal_eval(car_info))

以上的内容你学会了吗?下一篇继续来聊聊字体反爬的内容,欢迎持续关注哟!

爬虫和反爬之间,永远都是道高一尺魔高一丈的存在!

一、一句话核心

应对反爬策略多种多样,但万变不离其宗,核心一句话就是:

"爬虫越像人为操作,越不会被检测到反爬。"

二、我经常用的反反爬技术:

2.1 模拟请求头

request header,其中最关键的一项,User-Agent,可以写个agent_list,每次请求,随机选择一个agent,像这样:

agent_list = [
	"Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; Nexus S Build/GRK39F) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
	"Avant Browser/1.2.789rel1 (http://www.avantbrowser.com)",
	"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5",
	"Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.310.0 Safari/532.9",
	"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7",
	"Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14",
	"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14",
	"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20",
	"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.27 (KHTML, like Gecko) Chrome/12.0.712.0 Safari/534.27",
	"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1",
	"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.120 Safari/535.2",
	"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7",
	"Mozilla/5.0 (Windows; U; Windows NT 6.0 x64; en-US; rv:1.9pre) Gecko/2008072421 Minefield/3.0.2pre",
	"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10"
	]

在调用的时候,随机选取一个就可以了:

'User-Agent': random.choice(agent_list)

当然,你也可以使用 fake_useragent(一个前人集成好的随机UA库) 这个库,但有时候不好使,通常会报一个这种Error:

fake_useragent.errors.FakeUserAgentError: Maximum amount of retries reached

遇到这种报错不要慌,简单粗暴一点,把fake_useragent的json内容down到本地,目的是把从网站服务器获取,改为从本地获取,就可以避免timeout这种Error了。

json获取地址: fake-useragent.herokuapp.com

2.2 伪造请求cookie

发送请求的时候,request header里面,加上"cookie"这一项,伪造自己登陆了的假象。

从哪里获取"cookie"值呢?同样是按F12打开浏览器开发者模式,找到目标地址对应的headers->request headers,找到cookie这一项,把值复制下来。

查看网页的cookie值

放到爬虫代码的请求头里,类似这样:

把cookie值粘到爬虫代码

2.3 随机等待间隔

每次发送请求后,sleep随机等待时间,像这样:

time.sleep(random.uniform(0.5, 1)) # 随机等待时间是0.5秒和1秒之间的一个小数

尽量不要用sleep(1)、sleep(3)这种整数时间的等待,一看就是机器。。
还是那句话,让爬虫程序表现地更像一个人!

2.4 使用代理IP

使用代理IP解决反爬。(免费代理不靠谱,最好使用付费的。有按次数收费的,有按时长收费的,根据自身情况选择)
是什么意思呢,就是每次发送请求,让你像从不同的地域发过来的一样,第一次我的ip地址是河北,第二次是广东,第三次是美国。。。像这样:

以下代码是以「讯代理」举例的demo代码,其他IP代理请按照其接口规范自行开发。

def get_ip_pool(cnt):
	"""获取代理ip的函数"""
	url_api = '获取代理IP的API地址'
	try:
		r = requests.get(url_api)
		res_text = r.text
		res_status = r.status_code
		print('获取代理ip状态码:', res_status)
		print('返回内容是:', res_text)
		res_json = json.loads(res_text)
		ip_pool = random.choice(res_json['RESULT'])
		ip = ip_pool['ip']
		port = ip_pool['port']
		ret = str(ip) + ':' + str(port)
		print('获取代理ip成功 -> ', ret)
		return ret
	except Exception as e:
		print('get_ip_pool except:', str(e))
proxies = get_ip_pool() # 调用获取代理ip的函数
requests.get(url=url, headers=headers, proxies={'HTTPS': proxies}) # 发送请求

这样,对端服务器就会认为你/你们是很多地域的访客,就算访问很频繁,可能也不会反爬你!

2.5 验证码破解

关于验证码破解,我建议大家阅读 崔庆才 写的 《Python3网络爬虫开发实战》

其中,第8章:验证码的识别,提到了四类验证码的破解:

  • 8.1 图形验证码的识别
  • 8.2 极验滑动验证码的识别
  • 8.3 点触验证码的识别
  • 8.4 微博宫格验证码的识别

在8.3章节里,作者提到用第三方打码平台超级鹰平台,我也应用到了下面这个案例。
用第三方打码平台,直接调用它的接口,省心省力。
我之前为了破解Google的recaptcha验证码,就这种:

recaptcha验证码

调用的超级鹰的图像识别打码方法。大致思路是:

  1. 把页面弹出的验证码图片元素,截图保存到本地。
  2. 按照打码平台的图片大小要求,用PIL库进行缩放、裁剪并保存。
  3. 把处理好的图片,通过调用平台api发送给打码平台服务器,平台识别成功后返回坐标值对,利用python的selenium库依次点击相应坐标,完成验证码的自动识别。(此期间需逻辑判断,如果平台返回有误,需重新触发点击操作,直至验证成功)


顺便贴一下python代码:

def f_solve_captcha(v_infile, offset_x, offset_y, multiple=0.55):
	利用超级鹰识别验证码
	:param offset_x: x轴偏移量
	:param offset_y: y轴偏移量
	:param v_infile: 验证码图片
	:param multiple: 图片缩小系数
	:return: 验证码识别结果坐标list
	outfile = 'new-' + v_infile
	# 1、图片缩小到超级鹰要求:宽不超过460px,高不超过310px
	img = Image.open(v_infile)
	w, h = img.size
	w, h = round(w * multiple), round(h * multiple)  # 去掉浮点,防报错
	img = img.resize((w, h), Image.ANTIALIAS)
	img.save(outfile, optimize=True, quality=85)  # 质量为85效果最好
	print('pic smaller done!')
	# 2、调用超级鹰识别
	chaojiying = Chaojiying_Client(cjy_username, cjy_password, cjy_soft_id)
	im = open(outfile, 'rb').read()
	ret = chaojiying.PostPic(im, 9008)
	print(ret)
	loc_list2 = []
	if ret['err_no'] == 0:  # 返回码0代表成功
		loc_list = ret['pic_str'].split('|')
		for loc in loc_list:
			loc_x = round(int(loc.split(',')[0]) / multiple)
			loc_y = round(int(loc.split(',')[1]) / multiple)
			loc_list2.append((loc_x + offset_x, loc_y + offset_y))