海关进出口验证码学习

这个网站的反爬比较初级,页面上有一个滑动验证码,但是不存在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()的方法有两种。

    1. 使用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
      
    2. 将图片转换成二维数组:

      @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是无法请求成功的哦。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/api/USB 接口地址 常见错误结果和解决方法: 下面凑字数 {“code”:“20004”,“message”:“企业实时数据获取验签证书未在服务系统注册”,“total”:0,“serviceTime”:1500000000000} 得到这个结果,说明证书没有注册,测试环境联系管理员,生产环境需要到单一窗口去注册上传。如果已经上传了的,去看看你填的ebpCode是否是你自己的。 {“code”:“20000”,“message”:“上传失败 java.lang.IllegalStateException: xxxx这里是错误信息内容”,“total”:0,“serviceTime”:1500000000000} 这个错误说明上传的数据格式不对,海关系统无法解析,注意看看是否有字段类型或者格式错误,也注意看具体看错误内容,一般都有提示。 {“code”:“20005”,“message”:“验签失败”,“total”:0,“serviceTime”:1500000000000} 得到这个信息,说明上传的内容格式没问题了,验签失败,可能加签过程不对,一般都是字符串或者加签格式不对导致的,相见加签部分的细节,注意比对加签内容的格式和样例是否一致。另外也要看看用得证书什么的有没有问题。 {“code”:“20006”,“message”:“上传失败,入库失败 java.sql.SQLException: ORA-00001: 违反唯一约束条件 (sessionID重复)”,“total”:0,“serviceTime”:1500000000000} 得到这个消息,恭喜你,测试基本成功了:加签成功、数据格式没有问题了,接下来就可以自己生成模拟数据去测试了。 {“code”:“10000”,“message”:“上传成功”,“total”:0,“serviceTime”:1500000000000} 恭喜,测试成功。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值