看到帖子有網友問如何使用C#程序訪問百度指數(網站的站長對這個指標一定不陌生)。如下圖,顯示了CSDN的用戶關注度和媒體關注度。但很可惜的是,用戶只能通過瀏覽的方式得到某天的指數,如果想批量檢查多個關鍵詞的百度指數,就很麻煩了。而且如果想得到具體每天的數值也不是那么容易。
正好自己也有需要,所以研究了下,寫了個簡單的Demo實現此功能,歡迎各位朋友指正。
由于百度指數是以Flash的形式在客戶端展現出來的,不方便直接取到,開始我以為要用C#程序和Flash客戶端交互。但按常理,我們需要分析發(fā)送客戶度請求是發(fā)送到服務器的數據和返回的響應結果(其實基本所有的外掛都是這樣做的)。
我使用的抓包工具是HTTP Analyzer,通過查看Post Data,可以看到客戶端是發(fā)送了一個Post請求到http://index.baidu.com/gateway.php
發(fā)送的數據是一個AMF Message
那么我們需要研究下我們如何能讓我們的程序發(fā)送出相同的請求。
再此之前我們需要了解下AMF的概念,AMF是Adobe公司開發(fā)的數據交互和遠程過程調用的協議,全稱為Action Message Format,很類似于WebService。但WebService傳遞的是XML文本數據,而AMF使用Http傳輸的是二進制數據。
AMF中主要使用的數據類型如下:
- public enum DataType
- {
- Number = 0,
- Boolean = 1,
- String = 2,
- UntypedObject = 3,
- MovieClip = 4,
- Null = 5,
- Undefined = 6,
- ReferencedObject = 7,
- MixedArray = 8,
- End = 9,
- Array = 10,//0x0A
- Date = 11,//0x0B
- LongString = 12,//0x0C
- TypeAsObject = 13,//0x0D
- Recordset = 14,//0x0E
- Xml = 15,//0x0F
- TypedObject = 16,//0x10
- AMF3data = 17//0x11
- }
我們看下向服務器發(fā)送的AMF Message信息,HEX顯示如下
這些二進制數據的分析如下
0003 說明使用的AMF版本,目前AMF有兩個版本,0000 表示 AMF0,0003表示AMF3
0000 表示AMF Header的數量0
0001 表示AMF Body的數量1
0017 表示調用的方法的長度占23個字節(jié)(17的16進制就是23)
接下來
4461 7461 4163 6365 7373 6f72 2e67 6574 496e 6465 7865 73
這23個字節(jié)表示調用服務器端的方法是DataAccessor.getIndexes(44的16進制就是68,也就是D,后面不再做類似解釋)
接下來的 00 022f 31 表示target作為標識實現請求和響應的聯系,一般是自增整數。00表示數據類型為Number,02表示長度為兩個字節(jié),2f 31其實就是/1
00 0000 2d Body的長度為Number類型,長度為45
0a 0000 0003 0a表示數組類型(類似于C#中的Dictionary,鍵值對,鍵總是string類型),長度為3
數組的第一部分
0200 0ce5 a881 e8bf 85e6 9599 e882 b2 02表示字符串類型,0c表示此字符串長度為12,后面的e5 a881 e8bf 85e6 9599 e882 b2
表示傳遞的數據,可以用下段代碼查看
- byte[] buffer = new byte[] { 0xe5, 0xa8, 0x81, 0xe8, 0xbf, 0x85, 0xe6, 0x95, 0x99, 0xe8, 0x82, 0xb2 };
- string str = System.Text.Encoding.UTF8.GetString(buffer);
- Console.Write(str); //廣告
數組的第二部分
02 0001 30 字符串0,不知道有什么意義
數組的第三部分
02 0012 3230 3130 2d36 2d31 7c32 3031 302d 362d 3131
長度為18的字符串,內容為2010-6-1|2010-6-11
至此,請求的數據我們已經分析完畢,再簡單說下返回的數據(Response Content)
返回的AMF Message當然也包括Header和Body(數組類型)
Body的Target為/1/onResult
Body的Content包括
key String類型 就是我們傳進去的關鍵詞
area Number類型 0
areaName String類型 可以用上面的代碼看到內容,就是全國
userIndexes String類型 這里都是百度指數(用戶關注度),用逗號分隔開
mediaIndexes String類型 這里都是媒體指數(媒體關注度),用逗號分隔開(由于圖中userIndexes 占位較多,所以未顯示出來,在userIndexes數據下方)
如果想得到Z后一天的百度指數,只要找到mediaIndexes前面Z后一個逗號后跟的數字即可(當然要排除mediaIndexes本身數據類型及長度的占位字節(jié))
前面我們已經分析了發(fā)送給服務器的數據,下面就是我們怎樣把這些數據post到服務器了,我們看到對AMF的二進制數據處理(序列化及反序列化)很麻煩,有興趣的朋友可以使
用FluroineFx
FluroineFx官方網站:http://www.fluorinefx.com/
FluroineFx下載地址:http://www.fluorinefx.com/download.html
FluroineFx在線文檔:http://www.fluorinefx.com/docs/fluorine/index.html
但由于我們的應用非常簡單,所以使用網上流傳的一個模擬AMF Post的類,做了些修改,主要是我們現在應用的編碼為UTF8
- class AMFPostData
- {
- public List<byte> Message;
- /// <summary>
- /// 初始化Message
- /// </summary>
- /// <param name="amftype"></param>
- /// <param name="headers"></param>
- /// <param name="bodies"></param>
- public AMFPostData(AMFType amftype, int headers, int bodies)
- {
- //AMF版本
- if (amftype == AMFType.AMF0)
- {
- Message = new List<byte>(new byte[] { 0x00, 0x00 });
- }
- else if (amftype == AMFType.AMF3)
- {
- Message = new List<byte>(new byte[] { 0x00, 0x03 });
- }
- //header數量
- Message.Add(BitConverter.GetBytes(headers)[1]);
- Message.Add(BitConverter.GetBytes(headers)[0]);
- //body數量
- Message.Add(BitConverter.GetBytes(bodies)[1]);
- Message.Add(BitConverter.GetBytes(bodies)[0]);
- }
- /// <summary>
- /// 添加Target
- /// </summary>
- /// <param name="target"></param>
- /// <param name="response"></param>
- public void AddTargetAndResponse(string target, string response)
- {
- //添加Target長度
- Message.Add(BitConverter.GetBytes(target.Length)[1]);
- Message.Add(BitConverter.GetBytes(target.Length)[0]);
- //添加Target內容
- Message.AddRange(Encoding.UTF8.GetBytes(target));
- //添加Response長度
- Message.Add(BitConverter.GetBytes(response.Length)[1]);
- Message.Add(BitConverter.GetBytes(response.Length)[0]);
- //添加Response內容
- Message.AddRange(Encoding.UTF8.GetBytes(response));
- }
- /// <summary>
- /// 添加Body
- /// </summary>
- /// <param name="length"></param>
- /// <param name="Content"></param>
- public void AddBody(AMFPostDataBody amfpostdatabody)
- {
- Message.AddRange(amfpostdatabody.GetLength());
- Message.AddRange(amfpostdatabody.Content.ToArray());
- }
- }
- class AMFPostDataBody
- {
- private byte[] length = new byte[4];
- public List<byte> Content = new List<byte>();
- /// <summary>
- /// 初始化Body
- /// </summary>
- /// <param name="datatype"></param>
- /// <param name="arraylength"></param>
- public AMFPostDataBody(DataType datatype, int arraylength)
- {
- //添加類型標識
- Content.Add((byte)datatype);
- //數組的話添加長度
- if (datatype == DataType.Array)
- {
- Content.Add(BitConverter.GetBytes(arraylength)[3]);
- Content.Add(BitConverter.GetBytes(arraylength)[2]);
- Content.Add(BitConverter.GetBytes(arraylength)[1]);
- Content.Add(BitConverter.GetBytes(arraylength)[0]);
- }
- }
- public void AddData(DataType datatype, string value)
- {
- //添加類型標識
- Content.Add((byte)datatype);
- switch (datatype)
- {
- case DataType.Number:
- AddData(double.Parse(value));
- break;
- case DataType.String:
- AddData(value);
- break;
- case DataType.Boolean:
- AddData(Boolean.Parse(value));
- break;
- }
- }
- #region 各種類型處理方法
- /// <summary>
- /// Boolean
- /// </summary>
- /// <param name="flag"></param>
- private void AddData(bool flag)
- {
- if (flag)
- Content.Add(0x01);
- else
- Content.Add(0x00);
- }
- /// <summary>
- /// String
- /// </summary>
- /// <param name="value"></param>
- private void AddData(string value)
- {
- //添加長度
- Content.Add(BitConverter.GetBytes(Encoding.UTF8.GetBytes(value).Length)[1]);
- Content.Add(BitConverter.GetBytes(Encoding.UTF8.GetBytes(value).Length)[0]);
- //添加內容
- Content.AddRange(Encoding.UTF8.GetBytes(value));
- }
- /// <summary>
- /// Number
- /// </summary>
- /// <param name="number"></param>
- private void AddData(double number)
- {
- byte[] b = new byte[8];
- b = BitConverter.GetBytes(number);
- for (int i = 7; i > -1; i--)
- {
- Content.Add(b[i]);
- }
- }
- #endregion
- public byte[] GetLength()
- {
- length[0] = BitConverter.GetBytes(Content.Count)[3];
- length[1] = BitConverter.GetBytes(Content.Count)[2];
- length[2] = BitConverter.GetBytes(Content.Count)[1];
- length[3] = BitConverter.GetBytes(Content.Count)[0];
- return length;
- }
- }
- public enum AMFType
- {
- AMF0,
- AMF3
- }
- public enum DataType
- {
- Number = 0,
- Boolean = 1,
- String = 2,
- UntypedObject = 3,
- MovieClip = 4,
- Null = 5,
- Undefined = 6,
- ReferencedObject = 7,
- MixedArray = 8,
- End = 9,
- Array = 10,//0x0A
- Date = 11,//0x0B
- LongString = 12,//0x0C
- TypeAsObject = 13,//0x0D
- Recordset = 14,//0x0E
- Xml = 15,//0x0F
- TypedObject = 16,//0x10
- AMF3data = 17//0x11
- }
根據前文分析結果,我們生成數據的方法如下
- private static byte [] GetData(string key, string startdate, string enddate)
- {
- AMFPostData amfpostdata = new AMFPostData(AMFType.AMF3, 0, 1);
- amfpostdata.AddTargetAndResponse("DataAccessor.getIndexes", "/1");
- AMFPostDataBody amfpostdatabody = new AMFPostDataBody(DataType.Array, 3);
- amfpostdatabody.AddData(DataType.String, key);
- amfpostdatabody.AddData(DataType.String, "0");
- amfpostdatabody.AddData(DataType.String, startdate + "|" + enddate);
- amfpostdata.AddBody(amfpostdatabody);
- byte[] data = amfpostdata.Message.ToArray();
- return data;
- }
可以使用以下方法發(fā)送數據及得到返回響應的二進制數據
- public static byte[] GetFlashData(string gateway, byte[] data)
- {
- HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(gateway);
- request.Method = "Post";
- request.ContentType = "application/x-amf";
- CookieContainer cookie = new CookieContainer();
- request.CookieContainer = cookie;
- byte[] requestData = data;
- request.ContentLength = requestData.Length;
- byte[] responseData = null;
- try
- {
- using (Stream requestStream = request.GetRequestStream())
- {
- requestStream.Write(requestData, 0, requestData.Length);
- }
- HttpWebResponse response = (HttpWebResponse)request.GetResponse();
- using (Stream responseStream = response.GetResponseStream())
- {
- //StreamToBytes是將流轉換為二進制字節(jié)數組的方法,下文補上
- responseData = StreamToBytes(responseStream);
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine(DateTime.Now.ToString() + "," + ex.Message);
- //Log.WriteLine(DateTime.Now.ToString() + "," + ex.Message);
- }
- return responseData;
- }
由于返回的Stream只能Read,所以轉換為二進制流麻煩一些,并且考慮到AMF中流結束的標記為00 00 09,所以寫了如下方法得到返回的二進制流
- /// <summary>
- /// 將 Stream 轉成 byte[]
- /// </summary>
- public static byte[] StreamToBytes(Stream stream)
- {
- List<byte> bytes = new List<byte>();
- byte b = (byte)stream.ReadByte();
- byte b1 = 0, b2 = 0, b3 = 0;
- while (true)
- {
- if (b1 == 0 && b2 == 0 && b3 == 9)
- {
- break;
- }
- b1 = b2;
- b2 = b3;
- b3 = b;
- bytes.Add(b3);
- b = (byte)stream.ReadByte();
- }
- return bytes.ToArray();
- }
對返回數據的分析第一篇已經說過了,考慮到分析二進制數據比較麻煩,而實際上我們需要的數據就在userIndexes和mediaIndexes之間,所以我用了如下的方法返回Z后7天的百度指數
- private static List<int> GetIndexs(byte[] data)
- {
- if (data == null)
- {
- return null;
- }
- List<int> result = new List<int>();
- string str = Encoding.UTF8.GetString(data);
- string start = "userIndexes";
- string end = "mediaIndexes";
- int startIndex = str.IndexOf(start) + start.Length;
- int endIndex = str.IndexOf(end);
- string[] temp = str.Substring(startIndex, endIndex - startIndex).Split(',');
- result.Add(StringToInt(temp[temp.Length - 1]));
- int number = 0;
- for (int index = temp.Length - 2; index > temp.Length - 8; index--)
- {
- int.TryParse(temp[index], out number);
- result.Add(number);
- }
- return result;
- //以下為測試代碼
- //int count = 0;
- //foreach (byte item in data)
- //{
- // Log.Write(item.ToString("X2"));
- // Log.Write(" ");
- // count++;
- // if (count == 16)
- // {
- // count = 0;
- // Log.Write(System.Environment.NewLine);
- // }
- //}
- }
- //Z后一天的數據由于和mediaIndexes中有包含數據類型長度的字節(jié),特殊處理下
- public static int StringToInt(string str)
- {
- int result = 0;
- int number = 0;
- for (int i = 0; i < str.Length; i++)
- {
- number = str[i] - '0';
- if (number >= 0 && number <= 9)
- {
- result = result * 10 + number;
- }
- else
- {
- break;
- }
- }
- return result;
- }
下面是主函數的調用
- static void Main(string[] args)
- {
- string keyword = "威迅教育";
- string start = "2010-6-1";
- string end = "2010-6-11";
- List<int> result = Run(keyword, start, end);
- foreach (int item in result)
- {
- Console.Write(item + " ");
- }
- }
- public static List<int> Run(string keyword, string start, string end)
- {
- byte[] data = GetData(keyword, start, end);
- string gateway = "http://index.baidu.com/gateway.php";
- byte[] responseData = WebFunc.GetFlashData(gateway, data);
- if (responseData == null)
- {
- return null;
- }
- List<int> result = GetIndexs(responseData);
- return result;
- }
如果還是無法得到的,可以留言我會提供代碼樣例
Z后,實際上,以上的算法效率并不高,百度指數可以同時查詢三個關鍵詞,有興趣的朋友可以研究下處理的方式。