又到歲末,大家都忙着撈年底最后一桶金,我也不例外,忙着采集數據,不過有時候需要付出一點點時間而已。
在本案例中,我遇到了一個純數字的電話號碼變成了圖片需要采集過來,在原網頁上以<img src="一個JSP文件地址加一串密碼" />的形式展現給我們,在采集的時候,有人建議我繞過去,直接采圖片算了,不過本着對品質的追求,還是覺得應該做到采集的同時轉化為文本。
我的思路是這樣的,先處理保存0-9及“-”的黑白圖片到本地磁盤,並分別取名為0.gif,1.gif....9.gif,-.gif,之后采集圖片流到內存中,處理成黑白圖片后,按長度等分切割,並與本地圖片循環比對。這種情況也僅適合於“純數字的簡單”圖片。請注意,在本案例中,沒有紋路識別,只有像素比對。
於是,試驗品開始了:
首先,得到遠端圖片的地址,並根據這個地址得到Response出來的圖片(注,這是一個流,並非一個真正的圖片文件,就像網站上的圖片驗證碼一樣。)。
在這里我用到了HttpWebRequest,但是發現直接代入圖片地址后GET到的是空白,於是加參數.Referer = "http://該網站的域名",好了,現在遠端給了我圖片的流。
本段偽代碼如下:

2 objRequest.Timeout = 10000; // 設置超時10秒
3 objRequest.Referer = " http://被采網站的域名/ ";
4 HttpWebResponse objResponse = (HttpWebResponse)objRequest.GetResponse();
5 System.IO.Stream resStream = objResponse.GetResponseStream();
6 從以上代碼中,我得到這樣一副圖片:
7 // 保存圖片的代碼也供上。
8 // System.Drawing.Image i = System.Drawing.Image.FromStream(resStream);
9 // i.Save(@"c:\x.gif ", System.Drawing.Imaging.ImageFormat.Gif);
10 // resStream.Close();
11 // i.Dispose();
第二步,圖片流得到了,接下來是處理這副圖片了。不過在這之前,我還有件重要的事情要做,因為是純數字的圖片,並且可能區號和電話之間有“-”符號,所以我必須在本地保存了這些圖片的黑白樣品,這個過程很簡單,就是用PHOTOSHOP去色,然后到調整亮度/對比度,各增加100,即可得到一張黑白的圖片,之后將其切分為10*20的小圖,分別取名為0.gif,1.gif...9.gif,-.gif一共11個圖片。
,之后,我的處理流程是:圖片流到內存====》變灰處理=====》加亮度,對比度=====》變黑白處理=====》切分並和本地這11張圖片比對

2 // 第一步 變灰度圖
3 iGetPhoto = ToGray(iGetPhoto);
4 // 第二步 增加亮度100
5 iGetPhoto = KiLighten(iGetPhoto, 100);
6 // 第三步增加對比度100
7 iGetPhoto = KiContrast(iGetPhoto, 100);
8 // 第四步 變黑白
9 iGetPhoto = ToBlackWhite(iGetPhoto);
四個函數體:

2 /// 圖片變成灰度
3 /// </summary>
4 /// <param name="b"></param>
5 /// <returns></returns>
6 public Bitmap ToGray(Bitmap b) {
7 for ( int x = 0; x < b.Width; x++)
8 {
9 for ( int y = 0; y < b.Height; y++)
10 {
11 Color c = b.GetPixel(x, y);
12 int luma = ( int)(c.R * 0.3 + c.G * 0.59 + c.B * 0.11); // 轉換灰度的算法
13 b.SetPixel(x, y, Color.FromArgb(luma, luma, luma));
14 }
15 }return b;
16 }
17 /// <summary>
18 /// 圖像變成黑白
19 /// </summary>
20 /// <param name="b"></param>
21 /// <returns></returns>
22 public Bitmap ToBlackWhite(Bitmap b) {
23 for ( int x = 0; x < b.Width; x++)
24 {
25 for ( int y = 0; y < b.Height; y++)
26 {
27 Color c = b.GetPixel(x, y);
28 if (c.R < ( byte) 255)
29 {
30 b.SetPixel(x, y, Color.FromArgb( 0, 0, 0));
31 }
32 }
33 }return b;
34 }
35 /// <summary>
36 /// 圖像亮度調整
37 /// </summary>
38 /// <param name="b"></param>
39 /// <param name="degree"></param>
40 /// <returns></returns>
41 public Bitmap KiLighten(Bitmap b, int degree)
42 {
43
44 if (b == null)
45 {
46
47 return null;
48
49 }
50
51
52
53 if (degree < - 255) degree = - 255;
54
55 if (degree > 255) degree = 255;
56
57
58
59 try
60 {
61
62
63
64 int width = b.Width;
65
66 int height = b.Height;
67
68
69
70 int pix = 0;
71
72
73
74 BitmapData data = b.LockBits( new Rectangle( 0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
75
76
77
78 unsafe
79 {
80
81 byte* p = ( byte*)data.Scan0;
82
83 int offset = data.Stride - width * 3;
84
85 for ( int y = 0; y < height; y++)
86 {
87
88 for ( int x = 0; x < width; x++)
89 {
90
91 // 處理指定位置像素的亮度
92
93 for ( int i = 0; i < 3; i++)
94 {
95
96 pix = p[i] + degree;
97
98
99
100 if (degree < 0) p[i] = ( byte)Math.Max( 0, pix);
101
102 if (degree > 0) p[i] = ( byte)Math.Min( 255, pix);
103
104
105
106 } // i
107
108 p += 3;
109
110 } // x
111
112 p += offset;
113
114 } // y
115
116 }
117
118
119
120 b.UnlockBits(data);
121
122
123
124 return b;
125
126 }
127
128 catch
129 {
130
131 return null;
132
133 }
134
135
136
137 }
138
139
140 /// <summary>
141
142 /// 圖像對比度調整
143
144 /// </summary>
145
146 /// <param name="b"> 原始圖 </param>
147
148 /// <param name="degree"> 對比度[-100, 100] </param>
149
150 /// <returns></returns>
151
152 public Bitmap KiContrast(Bitmap b, int degree)
153 {
154
155 if (b == null)
156 {
157
158 return null;
159
160 }
161
162
163
164 if (degree < - 100) degree = - 100;
165
166 if (degree > 100) degree = 100;
167
168
169
170 try
171 {
172
173
174
175 double pixel = 0;
176
177 double contrast = ( 100.0 + degree) / 100.0;
178
179 contrast *= contrast;
180
181 int width = b.Width;
182
183 int height = b.Height;
184
185 BitmapData data = b.LockBits( new Rectangle( 0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
186
187 unsafe
188 {
189
190 byte* p = ( byte*)data.Scan0;
191
192 int offset = data.Stride - width * 3;
193
194 for ( int y = 0; y < height; y++)
195 {
196
197 for ( int x = 0; x < width; x++)
198 {
199
200 // 處理指定位置像素的對比度
201
202 for ( int i = 0; i < 3; i++)
203 {
204
205 pixel = ((p[i] / 255.0 - 0.5) * contrast + 0.5) * 255;
206
207 if (pixel < 0) pixel = 0;
208
209 if (pixel > 255) pixel = 255;
210
211 p[i] = ( byte)pixel;
212
213 } // i
214
215 p += 3;
216
217 } // x
218
219 p += offset;
220
221 } // y
222 }
223 b.UnlockBits(data);
224 return b;
225 } catch
226 {
227 return null;
228 }
229 }
第三步,所有的准備工作已經完成了,開始比對!內存中有了一副黑白的數字圖,它的尺寸是140*20,並且左邊空出來7像素,右側待定,具體看電話號碼有幾位,可能是3像素,也可能是13像素;本地磁盤中有了0-9.gif,它們的尺寸是10*20,現在要做的就是比對:比對代碼如下:

2 Bitmap[] numColl = {
3 new Bitmap(( " /Temp/0.gif ").ToServerPath()),
4 new Bitmap(( " /Temp/1.gif ").ToServerPath()),
5 new Bitmap(( " /Temp/2.gif ").ToServerPath()),
6 new Bitmap(( " /Temp/3.gif ").ToServerPath()),
7 new Bitmap(( " /Temp/4.gif ").ToServerPath()),
8 new Bitmap(( " /Temp/5.gif ").ToServerPath()),
9 new Bitmap(( " /Temp/6.gif ").ToServerPath()),
10 new Bitmap(( " /Temp/7.gif ").ToServerPath()),
11 new Bitmap(( " /Temp/8.gif ").ToServerPath()),
12 new Bitmap(( " /Temp/9.gif ").ToServerPath()),
13 new Bitmap(( " /Temp/-.gif ").ToServerPath())
14 };

2 /// 比較原圖和每個小樣圖,並給出數字結果
3 /// </summary>
4 /// <param name="iGetPhoto"></param>
5 /// <param name="numColl"></param>
6 /// <returns></returns>
7 public string ComparePic(Bitmap iGetPhoto /* 原圖 */, Bitmap[] numColl /* 小圖樣圖集 */) {
8 int numCount = 13;
9 string result = string.Empty;
10 for ( int i = 0; i < numCount; i++)
11 {
12 int x = i * 10 + 7; // 原始圖的開始取像素位置
13 Bitmap perBmp = new Bitmap( 10, 20);
14 Graphics gPhoto = Graphics.FromImage(perBmp);
15 gPhoto.Clear(Color.White);
16 gPhoto.DrawImage(iGetPhoto /* 原圖 */, 0, 0, /* 目標位置 */ new Rectangle( new Point(x, 0), new Size( 10, 20)) /* 源位置 */, GraphicsUnit.Pixel);
17 for ( int j = 0; j < 11; j++) // 這是數字樣圖的集合循環
18 {
19 bool isTrue = true; // 接下來循環小圖的每一個像素,與大圖中裁出的小圖作比較,只要一個像素不對,就OVER
20 for ( int n = 0; n < 20; n++)
21 {
22 for ( int m = 0; m < 10; m++)
23 {
24 Color point1 = perBmp.GetPixel(m, n);
25 Color point2 = numColl[j].GetPixel(m, n);
26 if (point1.ToArgb() != point2.ToArgb())
27 {
28 isTrue = false;
29 }
30 }
31 }
32 if (isTrue)
33 {
34 result += j == 10 ? " - " : j.ToString();
35 break;
36 }
37 }
38 perBmp.Dispose();
39 gPhoto.Dispose();
40 }
41 return result;
42 }
最后,把零散的調用封裝成調用的入口函數:

2 public string GetPicTel( string url)
3 {
4 HttpWebRequest objRequest = (HttpWebRequest)WebRequest.Create(url);
5 objRequest.Timeout = 10000; // 設置尾5秒
6 objRequest.Referer = " http://你要偷圖片的網站域名 ";
7 try
8 {
9 HttpWebResponse objResponse = (HttpWebResponse)objRequest.GetResponse();
10 // System.IO.Stream resStream = objResponse.GetResponseStream();
11 // System.Drawing.Image i = System.Drawing.Image.FromStream(resStream);
12 // i.Save(@"c:\x.gif ", System.Drawing.Imaging.ImageFormat.Gif);
13 // resStream.Close();
14 // i.Dispose();
15
16 System.IO.Stream resStream = objResponse.GetResponseStream();
17 Bitmap iGetPhoto = new Bitmap(resStream);
18 // 第一步 變灰度圖
19 iGetPhoto = ToGray(iGetPhoto);
20 // 第二步 增加亮度100
21 iGetPhoto = KiLighten(iGetPhoto, 100);
22 // 第三步增加對比度100
23 iGetPhoto = KiContrast(iGetPhoto, 100);
24 // 第四步 變黑白
25 iGetPhoto = ToBlackWhite(iGetPhoto);
26
27
28
29 // 測試圖片的結果
30 // iGetPhoto.Save(@"c:\x.gif ", System.Drawing.Imaging.ImageFormat.Gif);
31 // resStream.Close();
32 // return string.Empty;
33 Bitmap[] numColl = {
34 new Bitmap(( " /Temp/0.gif ").ToServerPath()),
35 new Bitmap(( " /Temp/1.gif ").ToServerPath()),
36 new Bitmap(( " /Temp/2.gif ").ToServerPath()),
37 new Bitmap(( " /Temp/3.gif ").ToServerPath()),
38 new Bitmap(( " /Temp/4.gif ").ToServerPath()),
39 new Bitmap(( " /Temp/5.gif ").ToServerPath()),
40 new Bitmap(( " /Temp/6.gif ").ToServerPath()),
41 new Bitmap(( " /Temp/7.gif ").ToServerPath()),
42 new Bitmap(( " /Temp/8.gif ").ToServerPath()),
43 new Bitmap(( " /Temp/9.gif ").ToServerPath()),
44 new Bitmap(( " /Temp/-.gif ").ToServerPath())
45 };
46 return ComparePic(iGetPhoto, numColl);
47
48
49 }
50 catch (Exception ex)
51 {
52 return string.Empty;
53 }
54 }
另外,要說明的是,有些驗證碼(不帶扭曲),有傾斜或上下波動的也可以用這種方法搞定,只是需要再多動一點點腦筋.但是帶扭曲效果的驗證碼就是非常專業的事情了,不過我們只用來做采集,不是爆破專家,這種方式應該基本滿足應用了.
希望本文可以拋磚引玉,幫你采集到你需要的數據,當然,盡可能地支持別人的版權!也正是因為版權,恕我無法給出原始圖片地址.
本文來自http://corecainiao.cnblogs.com/如需轉載請標明出處。