glidedsky-爬虫-雪碧图-1

  这一次分享的是雪碧图,先看看题目。

  

 

   

 

   这个题目关键的地方其实就是识别出图片中的数字,先看一下源码。

  

 

   在<style></style>里面找到,这个sprite存的就是图片的信息,将base64后面的字符串解码后,保存成图片格式就可以得到图片了。

  下载下来的图片是这样子的,从0到9。

  那下面来看网页上的数字是怎么对应起来的。

  

 

   打开开发者模式,定位到某一个数字上,可以看到这些样式,注意到backgrond-position-x这个属性,初步怀疑是图片上的某个点的位置,打开下载下来的图片,使用画图来定位一下。

  

 

   大概是数字3的位置,但是因为每次刷新图片都是不一样的,所以这个每个数字的backgrond-position-x是会变的。这里有一个做法就是刷新多一点,找到位置的大概范围,然后直接对应上数字,这个方法很暴力,但是我觉得容易出错,所以换了另一种思路。就是先将图片上的数字抠出来,按0-9排列,9个数字就可以通过下标对应起来了,将这一个排列好的数字称为模板,然后再提取每个div中的属性,从图片中提出网页上显示的那个数字,再和模板中的数字进行匹配,匹配上了,那就可以对应上数字了。

   首先是提取网页上显示的数字;第一步将<style></style>里面的属性用一个字典的形式保存起来,这里主要用到的是每个class里面的宽度width和偏移的位置position;第二步将sprite里面图片保存下来,然后以一个<div class="col-md-1">为单位遍历它下面的div获得每一个显示数字的class名,再从第一步保存好的字典提取里面的width和position;最后根据width和position在保存下来的图片截取对应的数字。

  

 

  这里第一页的每个数字都提取出来了,为了方便展示,就将每个数字都合并起来了。

  下面就是要创建数字的模板,主要方法是找到每个数字的外轮廓,然后通过轮廓计算出它的外界矩形,然后按照从左到右的顺序排列,那么0-9就对应着数字0-9。首先是将图片放大两倍,图片太小对轮廓的计算会有影响,然后将图片从bgr转为灰度的,再通过阈值处理将图片处理成二值图像,因为cv2的findContours函数只能处理二值图像。

  

 

 

   这里将轮廓都画了出来,但是有很多是不需要的轮廓,比如最外面的长方形框,还有0,4,6,8,9中间的轮廓,因为每个轮廓保存的是点的信息,这些不需要的轮廓的点都比较少,所以这里只使用长度大于15的轮廓。

  

 

 

   那么就剩下0-9,10个数字的轮廓了,但是这里的轮廓不是按照从左到右排序的,所以要先对这10个轮廓进行排序。

def sort_contours(cnts, method="left-to-right"):
    reverse = False
    i = 0

    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True

    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]  # 用一个最小的矩形,把找到的形状包起来x,y,h,w
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))

    return cnts, boundingBoxes

  cnts是findContours函数返回的轮廓列表。

refCnts = sort_contours([c for c in contours if len(c) > 15], method="left-to-right")[0]
    digits = {}
    # 遍历每一个轮廓
    for (i, c) in enumerate(refCnts):
        # 计算外接矩形并且resize成合适大小
        (x, y, w, h) = cv2.boundingRect(c)
        roi = ref[y:y + h, x:x + w]
        # 每一个数字对应每一个模板
        digits[i] = roi
    return digits

  这样就可以得到一个模板了,然后将上面提取到的每个数字,按同样的方式处理,首先是放大两倍,然后转为灰度图片,再进行二值化处理,最后使用cv2.mathTemplate函数就可以匹配模板了。

 

 

 

scores = []
for i, roi in digits.items():
    result = cv2.matchTemplate(num_ref, roi, cv2.TM_CCOEFF)
    min_val, max_val, min_indx, max_indx = cv2.minMaxLoc(result)
    scores.append(max_val)
out.append(str(np.argmax(scores)))

  最后的np.argmax就是提出最匹配的那个模板的index,就是对应着相应的数字。out用来存储每一组的数字,最后把数字合并起来,就是网页上显示的数字了。

  

 

   遍历每一页的数字,都加起来,就得到答案了。这里注意的是有时候进行模板匹配的时候会报错,所以我使用了while循环去遍历每一页,加上异常处理,当出错的时候就重新请求一次,没有出错了就把数字加起来,然后页数再加一。

 

posted @ 2021-03-31 23:04  一个小哥哥  阅读(134)  评论(0编辑  收藏  举报