2011年12月31日 星期六

HTML Parser ( 剖析器 ) - HtmlAgilityPack ( HAP )

網頁探勘 (Web Mining) 是使用資料探勘技術由網際網路的文件及服務中發現並擷取出隱含的資訊。

我在網頁探勘中下了許多功夫,例如之前發表一篇 Regular Expression 與 C# Regex 教學 的文章,頗受歡迎。

由於 Regular Expression 並不好學,需要熟悉 Regular Expression 語法邏輯與組合去對應網頁語法與結構,才能將有用資料擷取出來分析,甚至放到資料庫儲存,已供往後做成報表研究。但是如果網頁結構改變,整個 Regular Expression 語法都要改變,而每一條語法並不是這麼好下,可能要花一些時間去瞭解網頁結構的狀況而定。

HtmlAgilityPack 套件,類似視窗程式的 WebBrowser 一樣,先載入它的網站狀況,它可以讓剖析鬆散格式 HTML 的工作就像剖析 XML 一樣簡單,只要依照他網頁結構找出它的 xpth,便可輕鬆擷取出資料。

首先,先到Html Agility Pack下載 dll,目前版本為 1.4.0。

在程式碼中,先加入這個 dll,接著再 *.cs 引用:
using HtmlAgilityPack;

目前我拿奇摩首頁 ( http://tw.yahoo.com/ ),做擷取範例,抓出所有連結的 html 語法。以下為範例程式碼:

string url = "http://tw.yahoo.com/";

HtmlWeb web = new HtmlWeb();
HtmlDocument doc = web.Load(url);

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a");

foreach (HtmlNode node in nodes)
{
    Console.WriteLine(node.OuterHtml);
}

xpath 語法參考:XPath 語法

這樣變輕輕鬆鬆地擷取出所有連結。

歡迎大家來討論,目前我也正在學習中。

回目錄
回首頁



2011年12月12日 星期一

Alias (別名) 不能在 WHERE 裡使用? 因為 SQL 子句執行順序

情境

執行T-SQL:
SELECT id, (price * 0.79) AS SpecialPrice 
FROM Books 
WHERE SpecialPrice > 300

會得到這樣的的錯誤訊息:無效的資料行名稱 'SpecialPrice'。

WHY

以下是MSDN上的解釋:

SELECT 陳述式的邏輯處理順序

下列步驟顯示 SELECT 陳述式的邏輯處理順序或繫結順序。這個順序決定何時將某一個步驟中定義的物件提供給後續步驟的子句使用。例如,如果查詢處理器可以繫結至 (存取) FROM 子句中定義的資料表或檢視表,則這些物件及其資料行就可供所有後續步驟使用。 反之,由於 SELECT 子句是步驟 8,因此之前的子句無法參考該子句中定義的任何資料行別名或衍生資料行。不過,後續子句 (例如 ORDER BY 子句) 可以參考這些資料行別名或衍生資料行。請注意,實際執行的陳述式是由查詢處理序所決定,因此順序可能與此清單有所不同。 
  1. FROM
  2. ON
  3. JOIN
  4. WHERE
  5. GROUP BY
  6. WITH CUBE 或 WITH ROLLUP
  7. HAVING
  8. SELECT
  9. DISTINCT
  10. ORDER BY
  11. TOP

簡單一句話

WHERE的執行順序比SELECT優先,自然也就不認得SELECT裡的Alias了。
參考資料:
[MSDN Forum]Order of Execution 可以參考回答提供的連結
至於MySQL可以參考:Can you use an alias in the WHERE clause in mysql?


回目錄
回首頁

2011年12月7日 星期三

使用 WebClient 把目標 URL 另存為 HTML


想要把某個網址存為HTML,我們可以使用WebClient。
第一個找到的方法:
string url = "http://www.google.com";
string fileName = "Download.html";

WebClient client = new WebClient();
Byte[] htmlData = client.DownloadData(url);
string html = Encoding.UTF8.GetString(htmlData);
StreamWriter sw = new StreamWriter(fileName);
sw.Write(html);
sw.Close();
編碼設定透過Encoding.UTF8.GetString方法來把byte型態的陣列轉換為UTF8編碼的String。 

另一種方法:

client.Encoding = Encoding.UTF8;
string html = client.DownloadString(url);
client.Encoding設定的是WebClient上傳和下載的String編碼設定。

後來發現還有最短的,不用再另外用StreamWriter存成html:

client.DownloadFile(url, fileName);
使用WebReguest、HttpWebResponse:
WebRequest request = WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string html = reader.ReadToEnd();
reader.Close();
dataStream.Close();
response.Close();
雖然比較繁複,不過相對的使用WebReguest、HttpWebResponse可以設定比較多參數(Ex:Timeout),也可以看到完整的流程:對一個URL送出request,把response存為HTML。
參考資料:
[MSDN]WebClient Constructor
[MSDN]WebRequest Class
The Will Will Web | 利用 WebClient 類別模擬 HTTP POST 表單送出的注意事項

回目錄
回首頁

2011年12月1日 星期四

Javascript UrlEncode、UrlDecode、SetCookie、GetCookie、DelCookie 的函數

最近碰到 ASP.NET 要載入 ajax 頁面再帶回 ASP.NET 設定,先在 ASP.NET 頁面將必要資料用 Session 紀錄,等到讀取完成,ajax 讀取頁面再做一些計算,最後將計算結果,利用 Cookie 帶回原本頁面做顯示,其中,只讀取一次,也就是因為只讀取一次,所以 ajax 頁面帶回原本頁面就不能利用 session,因為它不會更新!

在實作過程中,碰到寫入 Cookie 字元的錯誤,因為我傳入的是一段 HTML 程式碼,所以很多符號都不能使用,在 ASP.NET 實作很簡單,使用 server.urlencode(string) 和 server.urldecode(string) 兩函式便可輕鬆解決;另一方面,ASP.NET 也很容易的讀取設定 cookie 內的值。

而在 Javascript 中就都要自己來了,所以我找了一下網路上的資源,程式碼和引用網址都貼在下面了,歡迎取用!


<script language="javascript"> 
    /*這裡開始 UrlEncode、UrlDecode 函數*/  
    function UrlEncode(str){  
        var ret="";  
        var strSpecial="!\"#$%&'()*+,/:;<=>?[]^`{|}~%";  
        var tt= ""; 
 
        for(var i=0;i<str.length;i++)
        {  
            var chr = str.charAt(i);  
            var c=str2asc(chr);  
            tt += chr+":"+c+"n";  
            if(parseInt("0x"+c) > 0x7f)
            {  
                ret+="%"+c.slice(0,2)+"%"+c.slice(-2);  
            }
            else
            {  
                if(chr==" ")  
                    ret+="+";  
                else if(strSpecial.indexOf(chr)!=-1)  
                    ret+="%"+c.toString(16);  
                else  
                    ret+=chr;  
            }  
        }  
        return ret;  
    }  
 
    function UrlDecode(str)
    {  
        var ret="";  
        for(var i=0;i<str.length;i++)
        {  
            var chr = str.charAt(i);  
            if(chr == "+")
            {  
                ret+=" ";  
            }
            else if(chr=="%")
            {  
                var asc = str.substring(i+1,i+3);  
                if(parseInt("0x"+asc)>0x7f)
                {  
                    ret+=asc2str(parseInt("0x"+asc+str.substring(i+4,i+6)));  
                    i+=5;  
                }
                else
                {  
                    ret+=asc2str(parseInt("0x"+asc));  
                    i+=2;  
                }  
            }
            else
            {  
                ret+= chr;  
            }  
        }  
        return ret;  
    } 
    function set_cookie(key, value, exp, path, domain, secure )
    {
        var cookie_string = key + "=" + escape ( value );
        if (exp)
        {
            cookie_string += "; expires=" + exp.toGMTString();
        }
        if (path)
            cookie_string += "; path=" + escape(path);
        if (domain)
            cookie_string += "; domain=" + escape(domain);
        if (secure)
            cookie_string += "; secure";
        document.cookie = cookie_string;
    }

    function get_cookie(cookie_name)
    {
        var results = 
            document.cookie.match('(^|;) ?' + cookie_name + '=([^;]*)(;|$)');

        if (results)
            return (unescape(results[2]));
        else
            return null;
    }

    function delete_cookie(cookie_name)
    {
        var cookie_date = new Date(); //current date & time
        cookie_date.setTime(cookie_date.getTime() - 1);
        document.cookie = cookie_name += "=; expires=" + cookie_date.toGMTString();
    }
    </script>

引用:用JavaScript实现UrlEncode和UrlDecode
Javascript cookie操作函数set, get & delete


回目錄
回首頁


DataAdapter.Update 不成功? 因為 RowState 沒變更

 

情境

現在有個簡單的資料表,有id, flag兩個欄位,在抓取所有flag為0的id之後,我想把剛剛抓取的那些id的flag設為1,使用DataAdapter.Update來更新,程式碼如下:

wrong_code

好傻好天真的以為DataAdapter.Update可以對每個id update flag=1,執行之後發現這樣是不能更新的。

Why & Solution

查了MSDN之後就明白為什麼了:

當應用程式呼叫 Update 方法時,DataAdapter 會根據 DataSet 中設定的索引順序檢查 RowState 屬性,並反覆的為每個資料列執行必要的 INSERT、UPDATE 或 DELETE 陳述式。

重點在於Row有沒有被修改過,原先的程式碼中,table在Fill之後並沒有做變更的動作,所以DataAdapter.Update也就不會更新了。

粗略修改下面兩段程式碼之後,就成功Update了。

modify_1

modify_2

RowState屬性可以參考這篇:ASP.NET DataRow的DataRowState概念

另外補充:

呼叫 DataSet.AcceptChanges 方法或 DataTable.AcceptChanges 方法即可認可 DataSetDataTable 中的所有變更。如果在呼叫 Update 方法之前呼叫了這兩個方法的其中一個,則除非在呼叫 AcceptChangesAcceptChanges 之後有更進一步地變更,否則呼叫 Update 時將不會認可任何變更。


回目錄
回首頁