不過有了 Groovy 這個 Java 的 Scripting 語言,只要簡單地搭配 NekoHTML Library 就能快速做出簡易源碼分析程式。前幾天曾介紹過「Groovy + XmlParser」用來擷取 Google 新聞 RSS 的內容,但是這個作法並不適用於一般網頁,因為一般的 HTML 很少能通過標準 XML 的解析,所以我們只另找對 HTML/XHTML 有更強悍解析能力的函式庫。
這個範例使用 CyberNeko 的 NekoHTML,這是個相當管用的函式庫,搭配 Groovy 服用只需要不到十行程式碼就能達成目的。
此範例的目標是擷取 Google 新聞首頁側邊欄的「發燒新聞」列表標題,雖然用 RSS 或 API 存取也能達成相同目的;但本文的範例對某些用途來說比較敏感,所以只好挑個比較沒爭議的資料來源 : )
view-source:https://news.google.com/nwshp?hl=zh-TW&tab=wn
![]() |
本範例擷取右邊側欄「發燒新聞」的文字內容 |
第一個要解決的問題,是傳統 Java 需要先加載 .jar 函式庫檔案到 CLASSPATH 的麻煩,這對 Groovy 來說,只需要一行程式碼就解決了,使用 Grape / Grab 自動從 Maven Repositories 取得相依的套件庫:
@Grab('net.sourceforge.nekohtml:nekohtml:1.9.16')
接下來開始建立 NekoHTML 的 SAXParser(),它用來解析 HTML/XHTML 文件。
def parser = new org.cyberneko.html.parsers.SAXParser()
parser.setFeature('http://xml.org/sax/features/namespaces', false)
從某個 URL 載入原始碼並完成解析只要一行:
def page = new XmlParser(parser).parse('https://news.google.com/nwshp?hl=zh-TW&tab=wn')
接著利用網頁除錯工具或打開原始碼,找到發燒新聞的 DOM 標籤。
![]() |
使用 Google Chrome 內建的網頁除錯工具 |
以這個範例來說,我們的目標就是這個 <DIV> 標籤:
<div class="small-section section-POPULAR" ...
所以利用深度優先(depthFirst)搜尋將節點找到,這裡搭配 grep 進行資料篩選,只有 class 屬性包含 small-section 與 section-POPULAR 才會被取出:
page.depthFirst().DIV.grep{ it.'@class'?.contains('small-section') }
上面的傳回結果是 List 型態,所以可以用 each 做迴圈:
page.depthFirst().DIV.grep{ it.'@class'?.contains('small-section section-POPULAR') }.each {
div->
println div
}
下一步開始修改迴圈內容,將裡面包含的標題找出來,標題清單位於這個 <DIV> 標籤內:
<div class="contents" s="t">
同樣使用 depthFirst 方式搜尋:
def contents = div.depthFirst().DIV.grep{ it.'@class'=='contents'&&it.'@s'=='t' }
這個結果也是 LIST,我們只需要取出一個:
if (contents.size() > 0) {
contents.first().depthFirst().DIV.grep{ it.'@class'=='title' }.each {
div_title->
println div_title
}
}
其中 div_title 的資料結構如下:
DIV[attributes={class=title}; value=[A[attributes={target=_blank, class=article usg-AFQjCNGshI48ZItUXdT9SuCtCCz76f-tGA did--2335265617202207122, href=http://www.espnstar.com.tw/news/basketball/2012/1227/256409.htm, url=http://www.espnstar.com.tw/news/basketball/2012/1227/256409.htm, id=-2335265617202207122}; value=[SPAN[attributes={class=titletext}; value=[險勝灰狼豪自評打得難看]]]]]]
到這邊已經很容易直接將文字內容取出:
println div_title.A.SPAN.text()
目前完成的程式碼如下,已經可以解析出標題文字:
@Grab('net.sourceforge.nekohtml:nekohtml:1.9.16')
def parser = new org.cyberneko.html.parsers.SAXParser()
parser.setFeature('http://xml.org/sax/features/namespaces', false)
def page = new XmlParser(parser).parse('https://news.google.com/nwshp?hl=zh-TW&tab=wn')
page.depthFirst().DIV.grep{ it.'@class'?.contains('small-section section-POPULAR') }.each {
div->
def contents = div.depthFirst().DIV.grep{ it.'@class'=='contents'&&it.'@s'=='t' }
if (contents.size() > 0) {
contents.first().depthFirst().DIV.grep{ it.'@class'=='title' }.each {
div_title->
println div_title.A.SPAN.text()
}
}
}
輸出結果參考如下:
印度政府把巴士強暴案女大學生送新加坡治療
險勝灰狼豪自評打得難看
金溥聰:總統堅持對的事氛圍會改善
黃淑琦不熟新家墜樓賈永婕哭喊:妳趕快回來!
創舉! 飢童可免費到4大超商吃到飽
京廣高鐵通車陸媒瘋狂連線
宏達電蝴蝶機Butterfly(2498-TW)熱銷
披肩殺人! 捲入機器勒斃女子
Kobe狂飆40分湖人兵敗丹佛
總統:希望每年新生兒不少於18萬
上面的程式碼落落長,其實只是為了說明 NekoHTML 的用法,實際上程式碼可以化簡成不到十行:
@Grab('net.sourceforge.nekohtml:nekohtml:1.9.16')
def parser = new org.cyberneko.html.parsers.SAXParser()
def page = new XmlParser(parser).parse('https://news.google.com/nwshp?hl=zh-TW&tab=wn')
page.depthFirst().DIV.grep{ it.'@class'=='title' }.each {
println it.A.SPAN.text()
}
Groovy 很值得 Java 開發者學習,即使沒機會在專案派上用場,它平時也能化簡很多工作上繁雜的電腦操作程序啊 : ) 歡迎加入 Groovy Taiwan 的學習社群~
原始碼分享於 Gist:
https://gist.github.com/4387321
到這邊已經很容易直接將文字內容取出:
println div_title.A.SPAN.text()
目前完成的程式碼如下,已經可以解析出標題文字:
@Grab('net.sourceforge.nekohtml:nekohtml:1.9.16')
def parser = new org.cyberneko.html.parsers.SAXParser()
parser.setFeature('http://xml.org/sax/features/namespaces', false)
def page = new XmlParser(parser).parse('https://news.google.com/nwshp?hl=zh-TW&tab=wn')
page.depthFirst().DIV.grep{ it.'@class'?.contains('small-section section-POPULAR') }.each {
div->
def contents = div.depthFirst().DIV.grep{ it.'@class'=='contents'&&it.'@s'=='t' }
if (contents.size() > 0) {
contents.first().depthFirst().DIV.grep{ it.'@class'=='title' }.each {
div_title->
println div_title.A.SPAN.text()
}
}
}
輸出結果參考如下:
印度政府把巴士強暴案女大學生送新加坡治療
險勝灰狼豪自評打得難看
金溥聰:總統堅持對的事氛圍會改善
黃淑琦不熟新家墜樓賈永婕哭喊:妳趕快回來!
創舉! 飢童可免費到4大超商吃到飽
京廣高鐵通車陸媒瘋狂連線
宏達電蝴蝶機Butterfly(2498-TW)熱銷
披肩殺人! 捲入機器勒斃女子
Kobe狂飆40分湖人兵敗丹佛
總統:希望每年新生兒不少於18萬
上面的程式碼落落長,其實只是為了說明 NekoHTML 的用法,實際上程式碼可以化簡成不到十行:
@Grab('net.sourceforge.nekohtml:nekohtml:1.9.16')
def parser = new org.cyberneko.html.parsers.SAXParser()
def page = new XmlParser(parser).parse('https://news.google.com/nwshp?hl=zh-TW&tab=wn')
page.depthFirst().DIV.grep{ it.'@class'=='title' }.each {
println it.A.SPAN.text()
}
Groovy 很值得 Java 開發者學習,即使沒機會在專案派上用場,它平時也能化簡很多工作上繁雜的電腦操作程序啊 : ) 歡迎加入 Groovy Taiwan 的學習社群~
原始碼分享於 Gist:
https://gist.github.com/4387321
謝謝分享,我現在正在想一個開發的程式。
回覆刪除這一篇文章對我有很大的幫助,謝謝