BC开篇-抛砖引玉,爬取图片式价格

Basic Coder 寄语

很高兴Basic coder终于和大家见面了,相信关注的同学一定会有各种疑问,这是个什么样的公众号,它的主要内容是什么,关注它能获得什么。正如Basic coder字面上意义一样,它是一个关于基础编程的公众号,旨在分享任何具体编程问题的解决方案,在这里你看不到高深莫测,高度抽象的大牛演讲稿,也不会看到漫天飞舞,层出不穷的高大上名词,但这里有最实际的,经过检验的解决方案,也许不是最好的,但一定会越来越好,它们最终的目的是让每个想要编程,喜欢创造的人更快更好的完成自己的目标。个人一直认为国内编程门槛居高不下,本身就是有问题的,应该有人去做点什么让全民编程的时代尽快到来,届时软件设计更多的是高层次的逻辑功能组合,是一门更需要灵感,创意的工程设计学科,而不是一门抓乱头发,把大部分时间花在易错,繁琐的下层细节实现上的高门槛专业。举个例子可能更加能说明问题,我很想为自己或者以后的孩子设计一款智能语音机器人,运用上自己设计的很多交互逻辑,从而可以作为小孩子从小的玩具伴侣,或者放在办公桌上解闷,亦可以放在车上当车载小助手。从一个传统程序设计者的角度出发,想要完成这个系统,需要一个嵌入式计算机以及对应的开发软件(市面上也有这样的套件),然后是学习和运用开源语音识别算法等。可以想象整个过程加上踩坑,又是一个漫长的过程。在互联网信息时代,分享其实是件非常容易的事,相信只要有人去做,愿意分享(这也是开源的意义之一),越来越多的人就会从中受益,并且创造出更多好玩好用的东西。可能大家可能会联想到开源软件,Basic Coder和开源软件都是通过分享来实现自身价值,而Basic Coder的内容范围更广,小到几行代码,大到一个软件,只要能帮助他人节约研究和学习成本的问题解决方案,都是Basic Coder所肯定的。BC永远欢迎并且十分渴求您的来稿。

介绍:电商图片式价格

其实本篇为15年3月写的文章,CSDN原文链接,作为开篇之作,实在有点寒碜,主要目的在于抛砖引玉。电商,曾一度成为了互联网行业的代名词,而众多电商之间的竞争也都一直存在,从而引发了另外一个需求:能够快速的获取竞争对手相同商品(竞品)的价格,从而可以实时的调整自己的价格,在同行业竞争中变得尤为关键。携程旅行网是国内最大的在线旅游提供商,其部分酒店的价格为了防止竞争对手爬取,采用了图片形式。 其他电商平台也曾纷纷效仿。然而仔细分析,图片式价格实质也是自欺欺人罢了。我们以携程网为例,讨论如何高效破解其图片式价格。

注: 目前携程多数页面酒店似乎已经不再使用图片式价格。

相关工作:携程图片价格识别分析

先上一张图,看看这个价格是怎么来的。


图(1)

可以看到,这个数字5,是由p_h57_7这个CSS样式定义的。而这个样式里定义了一个背景图片,注意这个地方后面跟了一个数字! 也就是 -1346。 然后,再看看这个图片是怎样的一张图。打开链接就可以获得,如图(2)所示。


图(2)

真实的图片比这个要长,我截取了一段。这时候你可能联想到了,上面的1346这个数字可能就是代表了这张图片横向第1346个像素所代表的数字。确实如此。不过这个位置的像素都是白色,真正的数字从往后两个像素开始,也就是1348这个项目开始。这个像素处的数字正是5。

解决方案

获得了价格所在图片以及知道了具体价格数字所在位置,下面只需要通过简单的图像处理就可以获得这些数字! 图像处理听起来很高大上,其实咱们这里用到的图像处理非常普通和简单。虽然背景图像是会经常动态更新的,但这些数字都是一样的格式。比如3这个数字,同一个尺寸的背景图中的3都是一样的。可能大家有个担心就是万一价格弄成验证码那样,怎么办?其实不用担心,因为这是价格,很少有人能接受验证码那样扭曲的价格的,所以价格有它自身的特殊性。价格爬取往往是爬虫的一部分,对于互联网海量的数据,爬虫的目标也要求快,准,狠!快字当先。所以我们要用尽可能快的处理算法来识别每一个数字。反而这里如果使用传统复杂的图像识算法来做这件事,那爬完所有数据所用的时间将非常恐怖。
像前面所说,这些字符一共就12个,分别是: . , 0 1 2 3 4 5 6 7 8 9。放大数字3和4,如图3所示:



图(3)

看他们的第一竖列,是不同的。所以可以根据第一竖列来区分3和4。其他的数字也都一样。
为了加快速度,我们要先对图片做2值化处理,即只要这个像素不是白色,就全部设置为1,是白色就设置为0. 在代码中,我们使用了boolean数组来存储2值化结果。然后从第一竖列开始,形成一颗决策树。比如,若某两个字符的第一竖列一样,那么就继续判断第二竖列,以此类推,只要两个字符不一样,肯定会有不一样的竖列。这样可以尽早发现数字。
以下是识别算法部分代码:

/** 获取图片像素的二值化二维数组——纵向优先, 为true就是该点为白色,为false该点为黑色 
     * @param img 
     * @return 
     */  
    public static boolean [][] get2ValuePixesHeightFirst(BufferedImage img)  
    {  
        int width= img.getWidth();  
        int height= img.getHeight();  
        boolean [][] result = new boolean[width][height];  
        for(int i=0;i<width;i++)  
            for(int j=0;j<height;j++)  
            {  
                //透明(在RGB中为黑色)和白色 都设置为false;  
                result[i][j]=img.getRGB(i, j)==16777215 || img.getRGB(i, j)== 0?false:true;  
            }  
        return result;  
    }  
      
    // (-1) 空白   
    //(-2)stopSymbol   16 17 18  
    //(-3)comma                 19 20 21 22  
    // 1          8 9  17 18  
    // 2      6 7 8 9       16 17 18  
    // 3      6 7 8 9    15 16 17 18  
    // 4          12 13 14 15        
    // 5                 15 16 17 18  
    // 6            9 10 11 12 13 14 15 16  
    // 7      6 7      17 18  
    // 8        7 8 9 10 11  13 14 15 16 17  
    // 9          8 9 10 11 12 13  
    // 0          8 9 10 11 12 13 14 15 16  
      
      
    /** 判断是否为空白竖列 
     * @param verticalLineArray 
     * @return 
     * @throws Exception 
     */  
    public static boolean isBlankLine(boolean [] verticalLineArray) throws Exception  
    {  
        if(verticalLineArray.length!=22)  
        {  
            throw new Exception("This is a new rule image. Can not recognize it!");  
        }  
        for(int i=0;i<verticalLineArray.length;i++)  
        {  
            if(verticalLineArray[i])  
            {  
                return false;  
            }  
        }  
        return true;  
    }  
      
    /** 识别数字 
     * @param verticalLineArray 
     * @return 
     * @throws Exception 
     */  
    public static char recognizeNumber( boolean [] verticalLineArray) throws Exception  
    {  
        if(verticalLineArray.length!=22)  
        {  
            throw new Exception("This is a new rule image. Can not recognize it!");  
        }  
        if(verticalLineArray[6-1])  
        {// 2 , 3,  7  
            if(verticalLineArray[8-1])  
            {// 2, 3  
                if(verticalLineArray[15-1])  
                {//3  
                    return '3';  
                }  
                else  
                {  
                    return '2';  
                }  
            }  
            else  
            {// 7  
                return '7';  
            }  
        }  
        else  
        {  
            if(verticalLineArray[7-1])  
            {//8  
                return '8';  
            }  
            else  
            {  
                if(verticalLineArray[8-1])  
                {//1 , 9 , 0  
                    if(verticalLineArray[10-1])  
                    {//9 , 0  
                        if(verticalLineArray[14-1])  
                        {  
                            return '0';  
                        }  
                        else  
                        {  
                            return '9';  
                        }  
                    }  
                    else  
                    {  
                        return '1';  
                    }  
                }  
                else  
                {  
                    if(verticalLineArray[9-1])  
                    {// 6  
                        return '6';  
                    }  
                    else  
                    {// 4 ,5 , '.' , ','  
                        if(verticalLineArray[12-1])  
                        {// 4  
                            return '4';  
                        }  
                        else  
                        {  
                            if(verticalLineArray[15-1])  
                            {  
                                return '5';  
                            }  
                            else  
                            {  
                                if(verticalLineArray[16-1])  
                                {  
                                    return '.';  
                                }  
                                else if(verticalLineArray[19-1])  
                                {  
                                    return ',';  
                                }  
                                else  
                                {  
                                    return '\\\\0';  
                                }  
                            }  
                        }  
                    }  
                }  
            }  
        }  
              
    }  

完整代码见文末链接

结论

识别速度和效果,如图4所示:



图4

根据统计,处理整个一张图片需要8ms(以后所有相同的图片不需要重复处理(可以放在缓存中间件中))。下次读取,只需要0ms,几乎不花时间。

完成代码Github地址

参考文献:
无。

稿子说明:BC希望您的来稿内容分为5个部分:

  1. 介绍: 文章所解决问题的描述,相关概念介绍,解决方案的大致思想。
  2. 相关工作: 所解决问题的详细描述,如果存在已知的解决方案,不妨列举列举,以及相关知识的描述。
  3. 解决方案: 详细解决方案。有代码的列出部分主要代码。
  4. 结果分析: 取得结果的效果分析和效率分析。有需要可以加入与其他方案的对比。
  5. 相关资料,参考文献等。如果有完整代码可以给出代码所在地址。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,943评论 4 60
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • 上一次哈哈跟大家整理了两句快速学五十音图的口诀。不知道大家有没有看哈。其实五十音的话是一个非常有规律的一个图表,跟...
    dc3c12c58779阅读 895评论 0 0
  • 人员的控制源于对企业绩效的需求,如果一个企业人员爆满绩效很低,人员控制上不是好的现象。 就像我们公司根据工资占比的...
    杨平的阅读 455评论 0 0
  • 第一眼看到这幅图,我感觉作画者是在描述一幅时间场景,过去和现在以及对逝去时光的留恋。所以姑且我就给这幅画起个名字,...
    Nina张阅读 865评论 0 0