网站一反爬破解
这个网站的反爬比较初级,页面上有一个滑动验证码,但是不存在js
加密反爬之类的东西,所以只需要识别出来验证码图片的缺口位置,以Post参数的形式返回给服务端就可以请求到数据了。
本次流程有两种方式来实现,一种方式是使用requests.get()/post()
的形式,另一种方式是使用session;前一种方式需要先请求获取cookie,然后每次携带cookie进行请求,后一种方式相对步骤简单些。
这里选择使用session的方式。
如果用前一种方式尝试,直接请求该网站主页返回的信息中没有cookie,直接请求获取验证码的url
时既可以获取图片信息,也可以获取cookie。
第一步:请求验证码图片
请求到验证码图片后,可以根据缺口位置的
RGB
颜色值范围,来找出缺口的位置,也可以先灰度化处理,再来判断缺口的位置,这里先灰度化。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47class Spider(object):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0",
'Host': 'credit.customs.gov.cn',
'Referer': 'http://credit.customs.gov.cn/',
'Accept-Encoding': 'gzip, deflate',
"Accept-Language": "zh-CN,zh;q=0.9",
'Connection': 'keep-alive',
"Origin": "http://credit.customs.gov.cn",
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
index_url = 'http://credit.customs.gov.cn'
image_post_url = 'http://credit.customs.gov.cn/ccppserver/ccpp/initFirstImage'
session = requests.session()
distance = ''
def request_image(self):
response1 = self.session.post(url=self.image_post_url, headers=self.headers)
if response1.status_code == 200:
# print(response1.text)
result = json.loads(response1.text)
data = result.get('data', '')
if data:
# 返回的这个是图片的二进制数据的base64编码,所以进行decode
big_image = base64.b64decode(data.get('comp', ''))
# 其实这个小的图片并不需要(缺口的图片)
small_image = base64.b64decode(data.get('compMin', ''))
with open('big_image.jpg', 'wb') as f:
f.write(big_image)
with open('small_image.jpg', 'wb') as f:
f.write(small_image)
# 以文件形式读取图片
# image = Image.open('big_image.jpg')
# 以二进制流形式读取图片
image = Image.open(BytesIO(big_image))
# 会打开图片工具显示图片
# image.show()
# 转换成灰度化图片()
gray = image.convert('L')
# 转换成二值化图片,(默认阈值127)
# binary = image.convert('I')
# 保存图片
gray.save('gray.jpg')
self.distance = self.get_distance3(gray)
print('缺口距最左侧的距离为{}'.format(self.distance))这里
get_distance()
的方法有两种。使用
getpixel()
。getpixel((width, height))
,前面的参数是横坐标(宽度),后面的纵坐标(高度),返回width和height像素点的RGB
颜色值,即返回(r,g,b)。(灰度图只返回一个值,表示灰度值)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def get_distance2(gray):
width, height = gray.size
for col in range(width):
for row in range(height-250):
# 这里的的值需要反复测试,选出一个合适的值,可以利用windows电脑的画图工具,查看具体的像素点,利用getpixel来获取值进行对比。
if gray.getpixel((col, row)) < 25:
flag_i = True
flag_j = True
# 这里当寻找到一个点的灰度值满足条件时,对其后面一百列和下面一百行都进行检测是否满足,若满足则返回当前列的值,否则继续寻找
for i in range(row+1, row+100):
if gray.getpixel((col, i)) >= 25:
flag_i = False
break
for j in range(col+1, col+100):
if gray.getpixel((j, row)) >= 25:
flag_j = False
break
if flag_i and flag_j:
return col
将图片转换成二维数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_distance1(gray_image):
# 若image为彩色图,则转换为三维数组(三通道)
# image_array = np.array(image)
# 灰度图转换gray为二维数组(双通道)
gray_array = np.array(gray_image)
# print(len(gray_array)) 图片的高度
# print(len(gray_array[0])) 图片的宽度
for row in range(len(gray_array)):
for col in range(len(gray_array[row])):
if gray_array[row][col] < 25:
flag_i = True
flag_j = True
for i in range(row + 1, row + 101):
if gray_array[i][col] >= 25:
flag_i = False
break
for j in range(col + 1, col + 100):
if gray_array[row][j] >= 25:
flag_j = False
break
if flag_i and flag_j:
return col以上两种方式太过硬性,即要求接下来的100行,100列全部满足要求,无一例外,万一有例外呢?所以又有了第三种方式,只要90%以上满足就可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def get_distance3(gray):
width, height = gray.size
for col in range(width):
for row in range(height-250):
if gray.getpixel((col, row)) < 25:
count_i = 0
count_j = 0
for i in range(row + 1, row + 100):
if gray.getpixel((col, i)) < 25:
count_i += 1
for j in range(col + 1, col + 100):
if gray.getpixel((j, row)) < 25:
count_j += 1
if count_i > 90 and count_j > 90:
return col这里有一个小技巧,行的循环范围最大不超过height-250,通过查看small_image图片的属性可以看到为250\250像素的图形,所以我们寻找缺口位置的最上方的高度,不可能在倒数250高度的范围内,这样可以减少循环次数。*
第二步:验证是否识别缺口距离成功
1
2
3
4
5
6
7
8
9
10def check_image(self):
post_data = {'rx': self.distance + random.random()}
response = self.session.post(
url='http://credit.customs.gov.cn/ccppserver/ccpp/checkImage',
data=post_data,
headers=self.headers)
print(response.text)
result = json.loads(response.text)
if response.status_code == 200 and result.get('ok', False):
return True第三步:查询数据
1
2
3
4
5
6
7
8
9
10
11
12
13def search_data(self):
post_data = {"nameSaic": "小米", "rx": self.distance + random.random()}
url = 'http://credit.customs.gov.cn/ccppserver/ccpp/queryList'
self.headers.update({
'Content-Type': 'application/json; charset=utf-8',
'Referer': 'http://credit.customs.gov.cn/ccppwebserver/pages/ccpp/html/ccppindex.html'
})
response = self.session.post(
url=url,
json=post_data,
headers=self.headers)
if response.status_code == 200:
print(response.text)这里我们通过在Chrome中,查看该Post请求的Form Data,选择view source为
{"rx":932.6538461538462,"nameSaic":"小米"}
的形式,所以这里post的参数使用json
而不是data
,使用data
是无法请求成功的哦。