• web数据采集核心技术分享系列(三)如何破解验证码?图像分析?特征匹配?人工智能?第三方集成?...哪个最强大?


    先加个目录,方便大家查看

    web数据采集核心技术分享系列(一)做一个强大的web数据采集系统,你需要什么?

    web数据采集核心技术分享系列(二)如何提取信息?字符串?正则?xpath?xslt?自定义?...什么才是王道?

    web数据采集核心技术分享系列(三)如何破解验证码?图像分析?特征匹配?人工智能?第三方集成?...哪个最强大? 

    web数据采集核心技术分享系列(四)利用神经网络实现网页验证码破解 

    应各位热心看客的要求建了个QQ群:254764602,欢迎大家加群一起讨论,互相学习进步。

    加群请输入暗号“数据采集”,否则不加 

     

    速度进入主题,这次的话题有点大,也有点难度,所以可能一篇说不完,先写一篇,回头根据大家的反馈我再写第二篇。

    道高一尺魔高一丈,在验证码这个领域,道高一尺不难,魔高一丈非常难,所以我们就通常的验证码来做讨论,比较特殊的或者变态的验证码就不做深入探讨了。

     

    一个普通的验证码通常是一个图片,有几个字符,然后有一些背景色,前景色,杂点(俗称噪点),干扰线,字符可能会有倾斜,扭曲,粘连,变形,甚至手写体,破解的过程总结起来就是一句话,去除干扰,简化特征,匹配特征,得到验证码,我不是写书的,不能面面俱到,我们从简单点的开始,看图说话,从下图可以看出,最后一步猜验证码的方法有三个,分别是简单的图像分析+特征匹配,基于神经网络的人工智能特征匹配,以及采用第三方google组件继承的方式,更强大的方式依赖于集成多个第三方类库(包含C以及C++代码)的实现,更为复杂,为了方便大家理解,先从第一种看起

     第一步,获取验证码图片

    在web数据采集过程中,如果采用获取网页源文件的方式,获取到图片地址在获取图片,应该不会有问题,如果借助浏览器获取到网页,再得到验证码地址,再去获取图片,则会导致问题,因为一般验证码的地址都是随机生成的,再次访问会得到另外一个验证码,所以借助浏览器的童鞋们,请直接从浏览器中获取图片。

    第二部,变形

    对于倾斜,字体变形的验证码,不做变形还原是很难继续处理的,所以必须变形,变形的原理针对不同的变形会有不同,没有哪一个方法可以包治百病且药到病除,所以我们也针对性讨论,比如对于倾斜, 要获取到字符区域四个角,然后计算倾斜四个边的倾斜角度,然后再向想法方向拉伸(我不是学计算机的,也不是学图形算法的,这些都是我的个人经验,说的不对还请不吝赐教)

    贴一段代码给大家  

      1  Bitmap output = input;

     2             int x = input.Width;

     3             int y = input.Height;
     4             int startPointsCount = 10;
     5             int[] yBlackCount = new int[y];
     6 
     7             Point leftTop = new Point(00);
     8             Point leftBottom = new Point(00);
     9             Point rightTop = new Point(00);
    10             Point rightBottom = new Point(00);
    11 
    12             for (int j = 0; j < y; j++)
    13             {
    14                 for (int i = 0; i < x; i++)
    15                 {
    16                     if (input.GetPixel(i, j).R == 0)
    17                     {
    18                         yBlackCount[j]++;
    19                     }
    20                 }
    21             }
    22 
    23             for (int j = 1; j < y - 1; j++)
    24             {
    25                 Point letterStart = new Point(00);
    26                 Point letterEnd = new Point(00);
    27                 for (int i = 1; i < x - 1; i++)
    28                 {
    29                     if (input.GetPixel(i, j).R == 0)
    30                     {
    31                         letterStart = new Point(i, j);
    32                         break;
    33                     }
    34                 }
    35                 for (int i = x - 2; i > 0; i--)
    36                 {
    37                     if (input.GetPixel(i, j).R == 0)
    38                     {
    39                         letterEnd = new Point(i, j);
    40                         break;
    41                     }
    42                 }
    43                 if (yBlackCount[j] > startPointsCount && yBlackCount[j + 1] > yBlackCount[j] && leftTop.Y == 0)
    44                 {
    45                     //top of letters
    46                     leftTop = letterStart;
    47                     rightTop = letterEnd;
    48                 }
    49                 if (leftTop.Y > 0 && yBlackCount[j + 1] < startPointsCount && yBlackCount[j] > yBlackCount[j + 1] && leftBottom.Y == 0)
    50                 {
    51                     //botton of letters
    52                     leftBottom = letterStart;
    53                     rightBottom = letterEnd;
    54                 }
    55             }
    56             if (leftTop.Y != 0 && leftBottom.Y != 0)
    57             {
    58                 int lDistince = ((leftBottom.X - leftTop.X) * y) / (leftBottom.Y - leftTop.Y);
    59                 int rDistince = ((rightBottom.X - rightTop.X) * y) / (rightBottom.Y - rightTop.Y);
    60                 if (lDistince > 20)
    61                 {
    62                     lDistince = 20;
    63                 }
    64                 if (lDistince < -20)
    65                 {
    66                     lDistince = -20;
    67                 }
    68                 if (rDistince > 20)
    69                 {
    70                     rDistince = 20;
    71                 }
    72                 if (rDistince < -20)
    73                 {
    74                     rDistince = -20;
    75                 }
    76 
    77 
    78                 Graphics g = Graphics.FromImage(output);
    79                 Brush b = new TextureBrush(source);
    80 
    81                 //g.FillRectangle(b, this.ClientRectangle);
    82                 g.FillRectangle(b, rectangle);
    83                 
    84                 Point[] destinationPoints = {
    85                     new Point(lDistince, 0),        // destination for upper-left point of original
    86                     new Point(x+rDistince, 0),      // destination for upper-right point of original
    87                     new Point(0, y)};               // destination for lower-left point of original
    88                 g.DrawImage(source, destinationPoints);
    89             }
    90 
    91             return output;

     其他的变形暂且不在这里深入,要针对具体变形才能深入展开。

    3,继续我们简单验证码处理的流程,说实话web数据采集中任何一点都可以拿出来单独写一个系列,要想做一个强大的采集系统,不是一个人花一两个月可以完成的,这里面的艰难只有你真正去做了,真正拿给客户运行才能体会到,如果各位大牛都能无私的把牛逼的解决方案和源代码开源,那么程序员的生活就会容易很多,大家都是一条船上的同路人,互相扶持多好。不好意思废话几句,继续说灰度化,这个网上代码很多,为了方便大家,我还是贴出来,如果大家觉得简单代码没必要贴,下次我就不贴了。

     protected static Color Gray(Color c)

    {
                int rgb = Convert.ToInt32((double)(((0.3 * c.R) + (0.59 * c.G)) + (0.11 * c.B)));
                return Color.FromArgb(rgb, rgb, rgb);
    }

     4.转化为黑白图片,俗称二值话,其实3和4都是为了简化特征,为后续处理打好基础,二值化的关键步骤是取得门限值,或者叫阀值,就是说什么样的点应该看做黑点,什么样的点应该看做白点,上代码(为啥总是第一行或者最后一行就没格式呢,谁告诉我?)

                Bitmap output = new Bitmap(input.Width, input.Height);
                int tv = ComputeThresholdValue(input);
                int x = input.Width;
                int y = input.Height;
                int blackCount = 0;
                int whiteCount = 0;
                int nearDots;
                for (int i = 0; i < x; i++)
                {
                    for (int j = 0; j < y; j++)
                    {
                        //suppose the background is white,set the border to white
                        if (i == 0 || i == input.Width - 1 || j == 0 || j == input.Height - 1)
                        {
                            output.SetPixel(i, j, Color.White);
                            whiteCount++;
                            continue;
                        }
                        //white point, background
                        if (input.GetPixel(i, j).R >= tv)
                        {
                            output.SetPixel(i, j, Color.White);
                            whiteCount++;
                        }
                        //black point, char
                        else
                        {
                            output.SetPixel(i, j, Color.Black);
                            blackCount++;
                        }
                    }

                } 

     5.切分,切分的目的是把一个字符串中的单个字符找出来,单个字符的特征处理起来就要简单很多,切分的原理就是主要是定位到字符边界,然后切分图片,经过上面几个步骤之后,图片上是一个个的黑白字符,假设白色为底色,黑色为字符,那么对黑色点在XY坐标系里的分布进行统计,即可得到字符边界。

      /// <summary>

            /// Split picture, and get the codes into a list
            
    /// </summary>
            
    /// <param name="map"></param>
            
    /// <param name="count"></param>
            
    /// <returns></returns>
            public static List<Bitmap> Split(Bitmap map)
            {
                List<Bitmap> resultList = new List<Bitmap>();

                int x = map.Width;
                int y = map.Height;
                int maxNoisyWidth = 4;//code with width nor more thal 4 is treated as noisy code
                int maxNoisyCount = 4//points no more than 4 is treated as noisy points

                
    //black is char
                
    //black points count per column
                int[] xBlackCount = new int[x];
                for (int i = 0; i < x; i++)
                {
                    for (int j = 0; j < y; j++)
                    {
                        if (map.GetPixel(i, j).R == 0)
                        {
                            xBlackCount[i]++;
                        }
                    }
                }
                //white points count per column
                int[] yBlackCount = new int[y];
                for (int j = 0; j < y; j++)
                {
                    for (int i = 0; i < x; i++)
                    {
                        if (map.GetPixel(i, j).R == 0)
                        {
                            yBlackCount[j]++;
                        }
                    }
                }

                //split picture
                bool charFlag = false;
                int xStart = 0;
                int xEnd = 0;
                int yStart = 0;
                int yEnd = 0;
                for (int j = 0; j < yBlackCount.Length; j++)
                {
                    if (yBlackCount[j] >= maxNoisyCount && charFlag == false)
                    {
                        //start to scan the top of all char
                        yStart = j;
                        charFlag = true;
                    }
                    if (yBlackCount[j] < maxNoisyCount && charFlag == true)
                    {
                        //end of scan the bottom of all char
                        yEnd = j;
                        charFlag = false;
                    }
                    if (yStart != 0 && yEnd != 0)
                    {
                        //got the top and bottom of all char
                        break;
                    }
                }
                for (int i = 0; i < xBlackCount.Length; i++)
                {
                    if (xBlackCount[i] >= maxNoisyCount && charFlag == false)
                    {
                        //start to scan a char
                        xStart = i;
                        charFlag = true;
                    }
                    if (xBlackCount[i] < maxNoisyCount && charFlag == true)
                    {
                        //end of scan a char
                        xEnd = i;
                        charFlag = false;
                    }
                    if (xStart != 0 && xEnd != 0)
                    {
                        //got the start and end of a char,check whether it's noise
                        if (xEnd - xStart < maxNoisyWidth)
                        {
                            //reset start and end
                            xStart = 0;
                            xEnd = 0;
                            continue;
                        }
                        //create new map for a char
                        Bitmap newMap = new Bitmap(xEnd - xStart + 1, yEnd - yStart + 1);
                        for (int ni = xStart; ni <= xEnd; ni++)
                        {
                            for (int nj = yStart; nj <= yEnd; nj++)
                            {
                                newMap.SetPixel(ni - xStart, nj - yStart, map.GetPixel(ni, nj));
                            }
                        }
                        newMap = new Bitmap(newMap, 1616);
                        resultList.Add(newMap);
                        //reset start and end
                        xStart = 0;
                        xEnd = 0;
                    }
                }
                return resultList;
            }

    6.切分完成之后,我们得到一组图片,每一个代表一个字符,然后进行特征计算,这里的思路首先把图片转化为一个矩阵,矩阵是啥不知道?查一下吧,还是有必要的,然后使用幂法求一个方阵的最大特征值和它所对应的特征向量, 向量也不知道??我敢判定你肯定跟我一样,大学数据没及格过。哈哈。然后要把该向量与我们知识库(一堆向量,每个向量都对应一个字符,这个知识库需要通过人工对程序进行训练得到,也就是你告诉程序,这个向量是2,那个是3,后面会讲)里面的向量进行比较,求出向量之间的举例,与其距离最小的向量就表明其特征最相近,也就是说,这两个字符很像,我们就认为他们是同一个字符,从而得出判断结果。

     input = new Bitmap(input, 1616);

                Double[,] doublemap = new Double[input.Width, input.Height];
                for (int i = 0; i < input.Width; i++)
                {
                    for (int j = 0; j < input.Height; j++)
                    {
                        if (input.GetPixel(i, j).R == 255)
                        {
                            doublemap[i, j] = Convert.ToDouble(1);
                        }
                        else
                        {
                            doublemap[i, j] = Convert.ToDouble(0);
                        }
                    }
                }

                Double[] W = new double[input.Width]; ;
                Double max = 0;
                MatrixLab mat = new MatrixLab(input.Width, 0.001, doublemap);
                mat.returnResult(ref W, ref max);

                SampleVector vector = new SampleVector(W, "");
                Double minDistance = Double.MaxValue;
                SampleVector similarVector = null;
                foreach (SampleVector target in this._studyList)
                {
                    double distance = _metric.Compute(vector, target);
                    if (distance < minDistance)
                    {
                        similarVector = target;
                        output = target.Code;
                        minDistance = distance;
                    }

                } 

    最简单的原理先讲到这里,下一篇我们深入点讲解,开头说了,现写一篇,回头根据大家的反馈我再写第二篇,欢迎大家交流

     本系列 web数据采集核心技术分享注重分享思路,所有的代码都是为了配合思路的讲解,想要关注如何搭建一个完整的采集系统的童鞋稍安勿躁,后续会关注这个话题,不想关注思路,只想复制代码,F5运行,点鼠标进行数据抓取的童鞋请理解。

    PS: 因本人能力有限,虽在web数据采集领域奋战多年,却也不可能在web数据采集的各个方面都提供最牛逼的解决方案和思路,还请各位看官本着互相交流学习,一起进步成长的态度来批评指正,欢迎留言。   

  • 相关阅读:
    Excel 利用VBA 发邮件
    SharePoint 2010 Ribbon Locations
    Sharepoint Query List Item Using CAML(folder)
    Customize SharePoint Ribbon Using ECMA Javascript
    Sharepoint学习笔记—ECMAScript对象模型--实现编写代码时的智能提示功能
    Javascript client sharepoint object model -- ECMA
    利用VBA拆分Word每个页面并分别保存
    sharepoint 2010 页面刷新时滚动条位置保持不变 Controlling scrollbar position on postback
    javascript倒计时 页面跳转
    微信小程序入门到实战(三)
  • 原文地址:https://www.cnblogs.com/keven1006/p/2625343.html
Copyright © 2020-2023  润新知