Basic Coder 寄語(yǔ)
很高興Basic coder終于和大家見面了,相信關(guān)注的同學(xué)一定會(huì)有各種疑問,這是個(gè)什么樣的公眾號(hào),它的主要內(nèi)容是什么,關(guān)注它能獲得什么。正如Basic coder字面上意義一樣,它是一個(gè)關(guān)于基礎(chǔ)編程的公眾號(hào),旨在分享任何具體編程問題的解決方案,在這里你看不到高深莫測(cè),高度抽象的大牛演講稿,也不會(huì)看到漫天飛舞,層出不窮的高大上名詞,但這里有最實(shí)際的,經(jīng)過(guò)檢驗(yàn)的解決方案,也許不是最好的,但一定會(huì)越來(lái)越好,它們最終的目的是讓每個(gè)想要編程,喜歡創(chuàng)造的人更快更好的完成自己的目標(biāo)。個(gè)人一直認(rèn)為國(guó)內(nèi)編程門檻居高不下,本身就是有問題的,應(yīng)該有人去做點(diǎn)什么讓全民編程的時(shí)代盡快到來(lái),屆時(shí)軟件設(shè)計(jì)更多的是高層次的邏輯功能組合,是一門更需要靈感,創(chuàng)意的工程設(shè)計(jì)學(xué)科,而不是一門抓亂頭發(fā),把大部分時(shí)間花在易錯(cuò),繁瑣的下層細(xì)節(jié)實(shí)現(xiàn)上的高門檻專業(yè)。舉個(gè)例子可能更加能說(shuō)明問題,我很想為自己或者以后的孩子設(shè)計(jì)一款智能語(yǔ)音機(jī)器人,運(yùn)用上自己設(shè)計(jì)的很多交互邏輯,從而可以作為小孩子從小的玩具伴侶,或者放在辦公桌上解悶,亦可以放在車上當(dāng)車載小助手。從一個(gè)傳統(tǒng)程序設(shè)計(jì)者的角度出發(fā),想要完成這個(gè)系統(tǒng),需要一個(gè)嵌入式計(jì)算機(jī)以及對(duì)應(yīng)的開發(fā)軟件(市面上也有這樣的套件),然后是學(xué)習(xí)和運(yùn)用開源語(yǔ)音識(shí)別算法等。可以想象整個(gè)過(guò)程加上踩坑,又是一個(gè)漫長(zhǎng)的過(guò)程。在互聯(lián)網(wǎng)信息時(shí)代,分享其實(shí)是件非常容易的事,相信只要有人去做,愿意分享(這也是開源的意義之一),越來(lái)越多的人就會(huì)從中受益,并且創(chuàng)造出更多好玩好用的東西。可能大家可能會(huì)聯(lián)想到開源軟件,Basic Coder和開源軟件都是通過(guò)分享來(lái)實(shí)現(xiàn)自身價(jià)值,而Basic Coder的內(nèi)容范圍更廣,小到幾行代碼,大到一個(gè)軟件,只要能幫助他人節(jié)約研究和學(xué)習(xí)成本的問題解決方案,都是Basic Coder所肯定的。BC永遠(yuǎn)歡迎并且十分渴求您的來(lái)稿。
介紹:電商圖片式價(jià)格
其實(shí)本篇為15年3月寫的文章,CSDN原文鏈接,作為開篇之作,實(shí)在有點(diǎn)寒磣,主要目的在于拋磚引玉。電商,曾一度成為了互聯(lián)網(wǎng)行業(yè)的代名詞,而眾多電商之間的競(jìng)爭(zhēng)也都一直存在,從而引發(fā)了另外一個(gè)需求:能夠快速的獲取競(jìng)爭(zhēng)對(duì)手相同商品(競(jìng)品)的價(jià)格,從而可以實(shí)時(shí)的調(diào)整自己的價(jià)格,在同行業(yè)競(jìng)爭(zhēng)中變得尤為關(guān)鍵。攜程旅行網(wǎng)是國(guó)內(nèi)最大的在線旅游提供商,其部分酒店的價(jià)格為了防止競(jìng)爭(zhēng)對(duì)手爬取,采用了圖片形式。 其他電商平臺(tái)也曾紛紛效仿。然而仔細(xì)分析,圖片式價(jià)格實(shí)質(zhì)也是自欺欺人罷了。我們以攜程網(wǎng)為例,討論如何高效破解其圖片式價(jià)格。
注: 目前攜程多數(shù)頁(yè)面酒店似乎已經(jīng)不再使用圖片式價(jià)格。
相關(guān)工作:攜程圖片價(jià)格識(shí)別分析
先上一張圖,看看這個(gè)價(jià)格是怎么來(lái)的。
可以看到,這個(gè)數(shù)字5,是由p_h57_7這個(gè)CSS樣式定義的。而這個(gè)樣式里定義了一個(gè)背景圖片,注意這個(gè)地方后面跟了一個(gè)數(shù)字! 也就是 -1346。 然后,再看看這個(gè)圖片是怎樣的一張圖。打開鏈接就可以獲得,如圖(2)所示。
真實(shí)的圖片比這個(gè)要長(zhǎng),我截取了一段。這時(shí)候你可能聯(lián)想到了,上面的1346這個(gè)數(shù)字可能就是代表了這張圖片橫向第1346個(gè)像素所代表的數(shù)字。確實(shí)如此。不過(guò)這個(gè)位置的像素都是白色,真正的數(shù)字從往后兩個(gè)像素開始,也就是1348這個(gè)項(xiàng)目開始。這個(gè)像素處的數(shù)字正是5。
解決方案
獲得了價(jià)格所在圖片以及知道了具體價(jià)格數(shù)字所在位置,下面只需要通過(guò)簡(jiǎn)單的圖像處理就可以獲得這些數(shù)字! 圖像處理聽起來(lái)很高大上,其實(shí)咱們這里用到的圖像處理非常普通和簡(jiǎn)單。雖然背景圖像是會(huì)經(jīng)常動(dòng)態(tài)更新的,但這些數(shù)字都是一樣的格式。比如3這個(gè)數(shù)字,同一個(gè)尺寸的背景圖中的3都是一樣的。可能大家有個(gè)擔(dān)心就是萬(wàn)一價(jià)格弄成驗(yàn)證碼那樣,怎么辦?其實(shí)不用擔(dān)心,因?yàn)檫@是價(jià)格,很少有人能接受驗(yàn)證碼那樣扭曲的價(jià)格的,所以價(jià)格有它自身的特殊性。價(jià)格爬取往往是爬蟲的一部分,對(duì)于互聯(lián)網(wǎng)海量的數(shù)據(jù),爬蟲的目標(biāo)也要求快,準(zhǔn),狠!快字當(dāng)先。所以我們要用盡可能快的處理算法來(lái)識(shí)別每一個(gè)數(shù)字。反而這里如果使用傳統(tǒng)復(fù)雜的圖像識(shí)算法來(lái)做這件事,那爬完所有數(shù)據(jù)所用的時(shí)間將非常恐怖。
像前面所說(shuō),這些字符一共就12個(gè),分別是: . , 0 1 2 3 4 5 6 7 8 9。放大數(shù)字3和4,如圖3所示:
看他們的第一豎列,是不同的。所以可以根據(jù)第一豎列來(lái)區(qū)分3和4。其他的數(shù)字也都一樣。
為了加快速度,我們要先對(duì)圖片做2值化處理,即只要這個(gè)像素不是白色,就全部設(shè)置為1,是白色就設(shè)置為0. 在代碼中,我們使用了boolean數(shù)組來(lái)存儲(chǔ)2值化結(jié)果。然后從第一豎列開始,形成一顆決策樹。比如,若某兩個(gè)字符的第一豎列一樣,那么就繼續(xù)判斷第二豎列,以此類推,只要兩個(gè)字符不一樣,肯定會(huì)有不一樣的豎列。這樣可以盡早發(fā)現(xiàn)數(shù)字。
以下是識(shí)別算法部分代碼:
/** 獲取圖片像素的二值化二維數(shù)組——縱向優(yōu)先, 為true就是該點(diǎn)為白色,為false該點(diǎn)為黑色
* @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中為黑色)和白色 都設(shè)置為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;
}
/** 識(shí)別數(shù)字
* @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';
}
}
}
}
}
}
}
}
完整代碼見文末鏈接
結(jié)論
識(shí)別速度和效果,如圖4所示:
根據(jù)統(tǒng)計(jì),處理整個(gè)一張圖片需要8ms(以后所有相同的圖片不需要重復(fù)處理(可以放在緩存中間件中))。下次讀取,只需要0ms,幾乎不花時(shí)間。
參考文獻(xiàn):
無(wú)。
稿子說(shuō)明:BC希望您的來(lái)稿內(nèi)容分為5個(gè)部分:
- 介紹: 文章所解決問題的描述,相關(guān)概念介紹,解決方案的大致思想。
- 相關(guān)工作: 所解決問題的詳細(xì)描述,如果存在已知的解決方案,不妨列舉列舉,以及相關(guān)知識(shí)的描述。
- 解決方案: 詳細(xì)解決方案。有代碼的列出部分主要代碼。
- 結(jié)果分析: 取得結(jié)果的效果分析和效率分析。有需要可以加入與其他方案的對(duì)比。
- 相關(guān)資料,參考文獻(xiàn)等。如果有完整代碼可以給出代碼所在地址。