这个网站的反爬比较初级,页面上有一个滑动验证码,但是不存在js加密反爬之类的东西,所以只需要识别出来验证码图片的缺口位置,以Post参数的形式返回给服务端就可以请求到数据了。
本次流程有两种方式来实现,一种方式是使用requests.get()/post()的形式,另一种方式是使用session;前一种方式需要先请求获取cookie,然后每次携带cookie进行请求,后一种方式相对步骤简单些。
这里选择使用session的方式。
如果用前一种方式尝试,直接请求该网站主页返回的信息中没有cookie,直接请求获取验证码的url时既可以获取图片信息,也可以获取cookie。
-
第一步:请求验证码图片
请求到验证码图片后,可以根据缺口位置的RGB颜色值范围,来找出缺口的位置,也可以先灰度化处理,再来判断缺口的位置,这里先灰度化。
class 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)。(灰度图只返回一个值,表示灰度值)@staticmethod 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
-
将图片转换成二维数组:
@staticmethod 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%以上满足就可以。
@staticmethod 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高度的范围内,这样可以减少循环次数。
-
-
第二步:验证是否识别缺口距离成功
def 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
-
第三步:查询数据
def 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是无法请求成功的哦。