如何使用正規表示法-4

到目前為止, 基本工具大致上都有了,
那麼我們就來寫一個簡單的專案,
如果你的包裹送到郵局, 通常都會拿到一組編號,
而這組編號只要上郵局網站查詢,
就可以知道你目前的包裹已經送到哪邊了?

現在我們就來示範一下, 
怎麼擷取這個包裹目前送到哪個郵局的資訊。

首先要準備幾個郵件號碼,

08038042000050
56483242000070
43904540000070
03939333000278

不過這些號碼會有期限, 因此會失效,
如果你有自己的包裹號碼就在好不過了

首先連到郵局的網站
http://postserv.post.gov.tw/webpost/CSController?cmd=POS4001_1&_MENU_ID=189&_SYS_ID=D&_ACTIVE_ID=190

輸入編號, 按下確定就可以看到包裹目前的狀態。
所以我們的最後目標就是要抓到"郵件號碼"、"狀態"、"處理日期時間"
以及"處理單位"下面的資訊,
那麼該怎麼作呢?
首先回到查詢的頁面, 在這邊要借助一些工具,
上網找尋Charles這個工具, 它可以讓你看到Http處理的一些資訊,
打開郵局網頁, 左邊就會看到一個瀏覽器的icon,
而右邊會出現網頁的一些資訊,

將右邊的項目切換到Request的選項, 就會看到下圖送出的資訊,
這些資訊是指當你開啟這個網頁, 其實瀏覽器就幫你送出Request,
因此你才能得到目前郵局這個頁面。

不信,你試著在郵局的頁面送出編號,左邊馬上多出一個瀏覽器的圖示,
點進去看Request, 就可以看到我們送出的編號, 以及一些你不知道的資訊,
瀏覽器其實平常就偷偷幫我們作一堆事情了。
有了這些資訊要幹嘛?

我們利用這個工具寫程式碼來仿造送出Http request,
讓郵局頁面以為我們按下確定的按鈕, 接著送出我們想送出的編號,
就可以得到郵件流向的資訊了。

如何跟PHP Server溝通當中, 我們有說到利用HttpClient這個api,
來抓取Http之間交換的資訊, Android就有內建HttpClient的套件了,
因此不用費心去抓取這個套件。

當我們在郵局的畫面送出包裹編號以後, 就看到下面這一大串
POST /webpost/CSController HTTP/1.1
Host: postserv.post.gov.tw
Content-Length: 109
Cache-Control: max-age=0
Origin: http://postserv.post.gov.tw
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://postserv.post.gov.tw/webpost/CSController?cmd=POS4001_1&_MENU_ID=189&_SYS_ID=D&_ACTIVE_ID=190
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: UTF-8,*;q=0.5
Cookie: JSESSIONID=292323981ADBD419BF9906D97394AA03

cmd=POS4001_2&sid=292323981ADBD419BF9906D97394AA03&MAILNO1=56483242000070&MAILNO2=&MAILNO3=&MAILNO4=&MAILNO5=

在最上面那一行, 可以清楚知道這個網頁是利用POST的方式來傳送資料,
接著就是Header的欄位, 最下面的是Body,
可以清楚看到送出三個參數分別為 cmd 、 sid以及 MAILN01,
在來分析網頁, 網頁有五個空格, 分別是
郵件號碼-1、郵件號碼-2、郵件號碼-3、郵件號碼-4以及郵件號碼-5,
跟我們的MAILN0x參數有點相似, 點選右鍵檢視原始碼,
<tr> 

       <td  width="30%" class="Context_tr" align='right'>郵件號碼-1:</td> 

       <td class="Context_td01" align='left'>

          <input type=text name="MAILNO1" maxlength=20 size=21 >

       </td>

     </tr>

     <tr> 

       <td  width="30%" class="Context_tr" align='right'>郵件號碼-2:</td> 

       <td class="Context_td01" align='left'>

          <input type=text name="MAILNO2" maxlength=20 size=21 >

       </td>

     </tr>

     <tr> 

       <td  width="30%" class="Context_tr" align='right'>郵件號碼-3:</td> 

       <td class="Context_td01" align='left'>

          <input type=text name="MAILNO3" maxlength=20 size=21 >

       </td>

     </tr>

     <tr> 

       <td  width="30%" class="Context_tr" align='right'>郵件號碼-4:</td> 

       <td class="Context_td01" align='left'>

          <input type=text name="MAILNO4" maxlength=20 size=21 >

       </td>

     </tr>

     <tr> 

       <td  width="30%" class="Context_tr" align='right'>郵件號碼-5:</td> 

       <td class="Context_td01" align='left'>

          <input type=text name="MAILNO5" maxlength=20 size=21 >

       </td>

     </tr>
這樣一來我們就可以確定MAILN0x就是我們輸入的欄位,

那cmd怎麼來的?
這時候就在輸入另外一組包裹編號, 發現cmd永遠都是POS4001_2,
因此我們就可以確定cmd傳送的值都會相同,
在來就是sid, sid到底是什麼呢?
原來他是cookie, 因為由上面的資訊可以得知, cookie的值是由瀏覽器幫我們配置,
但是這裡是由server幫我們配置, 應該說是session,
所以這個值可以不用處理。

HttpPost httpResquest = new HttpPost( "http://postserv.post.gov.tw/webpost/CSController?cmd=POS4001_1&_MENU_ID=189&_SYS_ID=D&_ACTIVE_ID=190");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("cmd","POS4001_2"));
params.add(new BasicNameValuePair("MAILNO1",number));
httpResquest.setHeader("Content-Type","application/x-www-form-urlencoded");
 
try{
    httpResquest.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8));
    HttpResponse httpResponse = 
        new DefaultHttpClient().execute(httpResquest);
    if(httpResponse.getStatusLine().getStatusCode()==200){
        htmlString = EntityUtils.toString(httpResponse.getEntity());
    }
}
catch(ClientProtocolException e){
    e.printStackTrace();
}
catch(IOException e){
    e.printStackTrace();
}
catch(Exception e){
    e.printStackTrace();
}
這段程式碼主要是模擬我們的request送出去, 將cmd跟MAILN01這兩個參數的值送到server,
最後如果成功, 就會傳回網頁原始碼, 我們就用htmlString這個變數存起來,
在來進行我們的正規表示式分析。



從前面的畫面得知, 如果要將網頁分析, 就會產生四個部分的資訊,

"郵件號碼"、"狀態"、"處理日期時間"以及"處理單位"
而得到的網頁原始碼會長這樣子,
<tr  > 
       <td class="Context_tr" align="center" > 郵件號碼 </td>

       <td class="Context_tr" align="center" > 狀態 </td> 

       <td class="Context_tr" align="center" > 處理日期時間 </td>  

       <td class="Context_tr" align="center" > 處理單位 </td> 

       <td class="Context_tr" align="center" id='PrintHidden' > 查明細 </td> 

     </tr>

     <tr  ><td class='Context_td01'>&nbsp;56483242000070</td><td class='Context_td01'>&nbsp;投遞成功</td><td class='Context_td01'>&nbsp;<script>dispFormatDateTimeEng('20120114135226')</script></td><td class='Context_td01'>&nbsp;<a  href="javascript:showDetail1('70458656483242000070')">台南郵局安南投遞股 </a></td><td class='Context_td01' id='PrintHidden' >&nbsp;<input type='button' class='Context_inout' value='查明細' onclick="showDetail('56483242000070')"></td></tr>

因此我們就分成四大區塊來分析,
首先是郵件號碼, 郵件號碼就是我們輸入的參數,
所以只要一開始將使用者輸入的號碼存起來, 就可以得到這個欄位的資訊,
這樣我們就可以省下分析號碼這個流程。

一開始先取得狀態, 由於要讓辨識能夠比較獨一無二,
因此就會包含稍微前一面一點的字串,
你可以先檢視原始碼, 接著利用搜尋功能,
例如你搜尋以下字串,
56483242000070</td><td class='Context_td01'>&nbsp;投遞成功</td>
如果只找到一個, 那麼代表這樣的表示式找出來的結果,
極可能會是我們想要的,
在來就用以下的方法擷取出來,
public String getStatus(){
    Pattern pattern 
        = Pattern.compile(
        "[0-9]+<[//]td> .+?<[//]td>");
    Matcher matcher = pattern.matcher(htmlString);
    while(matcher.find()) {
        status = getChinese(matcher.group());
    }
    return status;
}
這樣就可以取得' 投遞成功 ' 的字串。


其中getChinese是我們自己寫的方法, 因為這個字串只需要節取出中文,
因此直接使用上一篇教的擷取中文的方式,
public String getChinese(String in) {
    if (in == null || ("".equals(in))) {
        return ""; 
    }
    Matcher matcher = 
        Pattern.compile("\\p{InCJKUnifiedIdeographs}").matcher(in);
    StringBuffer out = new StringBuffer();
    while (matcher.find()) {
        out.append(matcher.group());
    }
    return out.toString();
}
這樣一來我們就可以把擷取的字串拿出來。


再來就是要把日期時間擷取出來, 所以就把這一段拿出來,
dispFormatDateTimeEng('20120114135226')

利用以下的寫法, 把數字部分擷取出來,
public String getDate(){
    Pattern pattern = Pattern.compile("dispFormatDateTimeEng[(]'[0-9]+'[)]");
    Matcher matcher = pattern.matcher(htmlString);
    while(matcher.find()) {     
        dateString = matcher.group();
        Matcher dateMatcher = Pattern.compile("[0-9]+").matcher(dateString);
    while(dateMatcher.find()){
        dateString = dateMatcher.group();
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date;
        try {
            date = format.parse(dateString);
            dateString = 
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
    
    }
    return dateString;

}

剩下最後一個

接著我們要擷取目前包裹的狀態, 內容會是包裹放置在某一個郵局,
需要擷取這一段字串,
<td class='Context_td01'>&nbsp;<a  href="javascript:showDetail1('70458656483242000070')">台南郵局安南投遞股 </a></td>


因此寫了以下的片段來擷取這一段字串,
public String getStatus(){
    Pattern pattern = 
       Pattern.compile(
       "[0-9]+<[//]td> .+?<[//]td>");
    Matcher matcher = pattern.matcher(htmlString);
    while(matcher.find()) {
        status = getChinese(matcher.group());
    }
    return status;
}

這樣就完成一個簡單的查詢包裹系統,
後面還可以進階查詢細節, 這部分原理相同, 因此就不解說了。