反爬(一)

网站一反爬破解

这个网站的反爬比较初级,页面上有一个滑动验证码,但是不存在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
    47
    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)。(灰度图只返回一个值,表示灰度值)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      @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
  1. 将图片转换成二维数组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @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%以上满足就可以。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @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高度的范围内,这样可以减少循环次数。*

  • 第二步:验证是否识别缺口距离成功

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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
  • 第三步:查询数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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是无法请求成功的哦。