網頁標題: 用 AutoIt 來自動翻譯

Warning: fopen(/home/crazy/www/cmsb/bcj/has_read.php): failed to open stream: Permission denied in /home/crazy/www/compose/reading.php on line 2077

Warning: fputs() expects parameter 1 to be resource, bool given in /home/crazy/www/compose/reading.php on line 2079

Warning: fclose() expects parameter 1 to be resource, bool given in /home/crazy/www/compose/reading.php on line 2080
 
﹗﹗﹗觀看留言:此文章已經有7則留言 ﹗﹗﹗


 前言:這個專案起自孝宇老師的學生謝福恩的主意與實踐,我認識他也有幾年了,他將 AutoIt 介紹給我,然後我跟他一起研究 AutoIt, 技術上互相交流。他因為想要即時翻譯日文的遊戲內容而使用 AutoIt 寫了自動翻譯的程式,然而該程式在我的 XP 上執行有些不如預期,於是我重新實作,留下這篇文章記錄。

 本文介紹的是使用 AutoIt 自動抓取 Google百度翻譯結果的腳本。腳本啟動後會註冊三組熱鍵,用 Ctrl+G 跟 Ctrl+B 分別可以執行 Google 及百度翻譯,執行時會從剪貼簿(剪下或複製時的資料暫存區)取得文字來進行翻譯,當不想使用時以 Ctrl+` 結束。

PS. [`] 指的是標準鍵盤 [Esc] 下面那個按鈕。

 以下介紹的重點,腳本中使用的核心技術是透過 COM 物件來操縱 Internet Explorer, 讓它自動抓取網頁 HTML, 並且執行頁面上的 Javascript 代碼以完成整個網頁。使用該功能之前建議先引入 IE.au3, 裡面友直接包裝好的函式可以使用,不必將 COM 物件的操作細節了解到一清二楚。


 首先必須以 _IECreate 來創立 IE 物件並初始化它的設定,本文只使用前三個參數,然而重點只在第一與第三個參數。第一個參數是要開啟的網址,但是剛開啟時不知道要用 Google 還是百度翻譯,所以設為 about:blank, 意思是「空白頁」。第三個參數預設為 1 代表 IE 視窗會顯示,可是通常我們希望它在背後工作就好不要因為跳出視窗而改變焦點跟影響語音報讀,所以這裡設為 0 使 IE 視窗隱藏。它的回傳值被紀錄為 $oIE, 往後將透過此物件操作 IE.
 用完的 IE 物件別忘了以 _IEQuit 清掉,以示負責,否則在背景執行的 IE 可能會無法終止,導致計算資源浪費。

 再來介紹 _IENavigate. 它的功能是「把現有 IE 視窗導引到指定網頁」,所謂現有 IE 視窗指的是 IE 的物件。它有三個參數,第一個參數就是 IE 的物件,第二個參數是目標網址,第三個參數在此沒有使用,預設為 1 表示它將等待 IE 的動作完成才繼續執行腳本。腳本中以 Translate 安排翻譯流程,共使用兩次 _IENavigate, 第一次導引至空白頁,第二次才是實際的翻譯系統頁面,導引至空白頁的作用是清除上次翻譯後殘留的網頁內容,以免腳本抓取頁面資訊時拿到上次的結果而不自知。

 用剛才提到的函式即可讓 IE 取回包含翻譯結果的網頁。腳本中的 $oIE = _IECreate("about:blank", 0, 0) 建立 IE 物件後,以「傳參考」方式傳入 Translate, 因為任何一項對物件的操作都可能影響其內容。當使用者按下 Ctrl+G 或 Ctrl+B, 即觸發 Tr_GTr_B 函式,會指定翻譯系統的提供者 (Google 或 BaiDu), While 迴圈將檢查出使用者有指定翻譯提供者而啟動翻譯程序。
 此時,腳本會檢查剪貼簿內有沒有「文字」類資料,若有,則它被取出,根 IE 物件與剛才指定的翻譯提供者一起送往 Translate. 此段過程用到的函式無詳述,可參閱 _ClipBoard_IsFormatAvailableClipGet.
 在 Translate 中,被翻譯的內容將先經過 UTF-8 編碼後轉為 %-encoding. 兩家翻譯都支援從網址提供資料,新的瀏覽器也都允許網址內包含非英數字元如中文、日文等,然而比較舊的瀏覽器網址中只將非英數內容以 % 編碼呈現,它記下了輸入資料每個位元組的十六進位表示結果,比如半形空白的十六進位表示是 20, 則它將被變成 %20. 實際過程可查閱 StringToASCIIArray, StringIsAlNum, Hex_ArrayToString.


 取得翻譯結果的網頁之後,下一個難題是如何找到真正翻譯的答案。每個網站在不同時刻,網頁的結構都可能被改變,因此一旦網頁結構改變,腳本內容也必須隨之而改。以下分別說明解析目前 Google 及百度翻譯結果網頁的方法。

 HTML 在瀏覽器裡會被剖析為樹狀結構,考慮此例:
 <p><label for="edit1">姓名:</label><input id="edit1" name="fullname"/></p>
 這段 HTML 會產生一個單行編輯區,前面提示為輸入姓名。整個 <p>...</p> 是一個樹上的節點,有二個子節點 label 與 input, label 裡沒有包含 HTML 標籤了,但有文字內容,而 input 則連文字內容都沒有。然後 label 跟 input 裡有些 key=value 格式的寫法,這些是「屬性」,HTML 中有些屬性可以被指定給多種標籤類型,如 id 與 name, 也有些像 for 一樣只有 label 標籤才有這種屬性。當我們以物件的角度解析網頁,每個節點都是一個物件,有自己的標籤名、屬性、內容,其他詳情請見文件物件模型
 以下就簡介如何從 IE 物件裡取出文件物件資訊,以及如何由文件物件中獲得剛才提到的子節點、屬性與文字內容。

 從網頁上定位物件有三種方式:根據 id, name 屬性及標籤名。前面提過的 id 屬性,其內的值必須在整個網頁裡是唯一,就像替該節點取了身分證號一樣,而我們可用這個訊息來查找它。透過 _IEGetObjById 來達成根據 id 檢索物件,兩個參數就是 IE 物件跟 id 值。就剛才 HTML 的範例而言,用 "edit1" 查出來的結果就會得到一個物件,代表 input 標籤本身,可以求取該物件的其他屬性如 name.
 第二種方式是根據 name 屬性,以 _IEGetObjByName 實現,不過腳本中並未使用這個函式。與 id 不同的是,name 並沒有被要求在網頁中唯一,它與網頁表單提交給伺服器的資料有關。也就是說,這項操作所得到的答案理論上不只一個,於是函式傳回的是一個可用 For...In...Next 迴圈拜訪每個元素的物件,拜訪過程中所取出的單一物件才是可以求取子節點、屬性、文字內容的節點物件。該函式共有三個參數,前二個參數依然是 IE 物件跟查找用的 name, 第三個參數是「索引」,可直接取出答案中的某個節點,免除迴圈的麻煩。
 最後的方法是根據標籤名來查找,透過 _IETagNameGetCollection. 它也有三個參數,前二個是目標物件與用來查找的標籤名,注意第一個參數在此並沒有限制為 IE 這種大範圍的物件,因為搜索標籤的動作在網頁任何地方進行皆合理,所以經由前兩個函式或 _IETagNameGetCollection 本身取得的節點物件都可以。第三個參數也是「索引」,由於相同標籤名的節點也是很多個,搜尋後必須 For...In...Next 迴圈逐一訪問或者直接給索引找出每筆結果。

PS. 寫過 Javascript 應該就知道,這三種方法分別是 document.getElementById, document.getElementsByName, 和 document.getElementsByTagName.

 抓出想要的節點物件之後就要取出子節點、屬性與文字內容。子節點可以用 _IETagNameGetCollection 來查找,屬性與文字內容要靠 _IEPropertyGet. 它有二個參數,即目標物件與所求屬性的名字,參考網頁裡有列舉所有它支援的屬性名稱,腳本裡只有用到 "innertext", 正如其名就是節點李包含的文字內容。
 參考網頁裡列舉的屬性中有另一個值得介紹的是 "innerhtml", 也就是找出該節點包含的 HTML 內容,以之前舉例而言對 <p> 求取 "innerhtml" 屬性即為 <label for="edit1">姓名:</label><input id="edit1" name="fullname"/> 這整段字。注意它跟 "innertext" 呈現特殊字元時的差異,比如半形小於、大於,因為這兩個字元在 HTML 裡負責標出標籤,所以如果網頁內容硬要用它們,必須寫成 & 開頭分號結尾的「亂碼」,如小於寫成 &lt;, 取 "innerhtml" 時這些「亂碼」會以亂碼的原文型態傳回腳本中,但 "innertext" 收到的結果中這些「亂碼」都被轉譯成人在看的文字了。對「亂碼」有興趣者可查閱字符實體引用
 不過,其實面對物件變數是可以用 . 操作來取出屬性的。參考網頁中未列出一個屬性稱為 className, 這是指定該節點 CSS 樣式用的,在標籤上寫成 <p class="xxx"> 的形式,可是 _IEPropertyGet 不支援,就以 $o.className 表達式來取得結果,其中 $o 變數所存放的是某個節點物件。之前的 "innertext""innerhtml" 也都能用 $o.innertext$o.innerhtml 這樣來存取,和透過 _IEPropertyGet 間接取得屬性的方式相比差在 _IEPropertyGet 會事先檢查某些可能的例外錯誤情形。

 Google 翻譯把回應結果放在一個標有 id="result_box" 的標籤裡,內部又以 span 標籤區別輸入每句對應的輸出譯文,因此解讀其網頁的方式就是:
 

      
  1. _IEGetObjById 定位 "result_box" 所在的節點。
  2.   
  3. _IETagNameGetCollection 求出它底下所有標籤名為 span 的子節點。
  4.   
  5. _IEPropertyGet 取出 "innertext" 即是翻譯內容片段。
  6.  

 百度的翻譯結果中並沒有如此有用的 id, 它的翻譯結果放在一個 <p> 標籤內,這個節點的特徵是擁有一個 class 屬性,它的值含有 "target-output" 這個字串。另外,百度似乎不會在傳回網頁當下立即附上翻譯結果,會稍微延遲。因此必須嘗試好幾次,直到發現翻譯結果已經傳回為止。解讀其網頁的方式如下:
 

      
  1. _IETagNameGetCollection 取出所有標籤名 p 的節點。
  2.   
  3. 逐一訪問所有結果,對每個結果都取 className 屬性。
  4.   
  5. 要是發現 "target-output" 字串包含其中,即找到翻譯結果,否則這次沒有找到。
  6.   
  7. 重複之前三個步驟直到真的找到翻譯結果為止。
  8.   
  9. 不過,如果太久沒有找到結果,很可能網路有問題或者網頁結構改變所致,應設一個時間限制以免無限次失敗嘗試。
  10.  

 腳本中的 Translate 函式是一個框架,定義了一般化的取得翻譯結果流程。將輸入轉為 % 編碼並取回翻譯網頁後,就是一直嘗試有沒有拿到翻譯內容,如果有就會正常結束,否則將超時而結束。由於它是一般化的流程,所以雖然 Google 會把翻譯結果夾帶在網頁中一起傳回,這裡仍將解晰 Google 翻譯網頁的流程也放在嘗試中。超時的單位是「毫秒」,腳本中預設限時 500 毫秒。
 Google_GetResultBaiDu_GetResult 則被寫成 callback 函式的型態,在 Translate 裡呼叫它們只有兩種狀況,一為給定字串的輸入資料欲求取翻譯頁面網址,二為給定 IE 物件來解析翻譯結果頁面,二個 callback 函式對這兩種狀況分別依實際狀況處理。這種設計方式好處在於,若想存取第三個翻譯服務,只要多寫一個 callback 函式並填入實作即可,不用重複抄寫 Translate 裡的共同程式區段。
 Translate 內呼叫它們的地方位於執行 Execute 函式,Execute 作用為執行一個敘述,然而該「敘述」是腳本運作途中可以臨時決定的,不像其他地方的程式碼已經被固定而按照預定的順序執行。因此,可以在執行 Translate 期間,決定要動用哪個 callback 函式。比如 Execute($sProvider & "($sText)") 敘述就是遇到指定 Google 翻譯就會被解釋成 Execute("Google_GetResult($sText)") 而使用 Google_GetResult, 要是指定百度翻譯就變成 Execute("BaiDu_GetResult($sText)") 而執行 BaiDu_GetResult.
 想更了解其中原理可查閱 Execute 的使用。

 最後,我自己的 Internet Explorer 8 疑似某些根憑證已過期,因此開 Google 翻譯會有安全性警告,也就是過問我是否拒絕顯示非安全性(無法認證來源或者資訊完整)的內容,我都惠選「是」,因為那些內容沒有接收並不影響我取得翻譯結果。
 可是這個詢問每次啟動 _IENavigate 取回 Google 翻譯結果時都會跳出來,我在腳本裡無法被動通知有這個視窗存在,於是我註冊了一個 SecurityWarningAnswer 程序,在 _IENavigate 期間將定期執行,每次執行即主動偵測有沒有「安全性警告」視窗存在,有就自動對其中的「是」按鈕發出點擊指令。
 其他細節可參見 AdlibRegister, AdlibUnRegister, WinGetHandleControlClick

 以下是完整的腳本內容,不過在這邊可能有些半形空白會變成全形,用 AutoIt 編譯或執行前請注意將之清除。



本文張貼者:Bo-Cheng Jhan〔張貼時間:民國105年11月10日(星期四)17點54分 | 更新次數 #11 | 最後更新:民國105年11月13日(星期天)20點50分〕

部落格首頁


學習的故鄉首頁
本站公告:〔您越需要我們,我們就越有創意〕 本站說明書:〔發現故鄉還有改進的地方,請來信告訴原丁們〕
觀察應用學習點數 :〔咱的故鄉有您的參與,會使我們有更大的發揮空間,展現更豐富精彩的學習畫面〕 〔期待藉由無障礙網頁設計,能讓視障小朋友更愛看書、更愛寫作且更愛學習〕:盲用電腦「心得分享」
〔為了讓我們有乾淨的學習環境,請勿任意在本站散播商業廣告與不合法文件或聯結〕:本站宣示