Excel 股市資料抓取服務

提供Excel 股市資料抓取服務
可透過下列方式聯絡我
Email: iamaraymond@yahoo.com.tw
(FB請先加我好友再私訊,不然會跑到陌生訊息)

課程:
Excel VBA 金融資料抓取 | 打造股票研究系統 (學生數: 602,學員評價5顆星)
無痛起步-Excel VBA超入門實戰(學生數: 413,學員評價5顆星)


2018年9月6日 星期四

Excel VBA 抓取樂透網站

最近一位學生傳了一個連結給我
問如何抓取表中的那些開獎數字

我對此網站內容沒甚麼興趣,畢竟我也沒玩過樂透
但我對如何抓此網站的資料倒是十分有興趣

網站架構不算複雜,只能算是一個小型網站
然而我認為這對於初學者是難度頗高的網站

從開發人員工具中可以看到
裡面大概9成以上都是Div的Tag
(代表不好使用getElementsByTagName)
而且屬性也只有class可以使用
(HTMLFile無法抓到class屬性)

這時候怎麼辦?

解決方法有二

1.如果不是很care執行速度,可以使用IE
多了getElementsByClassName這個工具
可以大幅簡化爬蟲流程,難度也會大幅下降

2.若對速度有要求,或是想玩玩HTMLFile另一個抓法
可以試試看節點的用法


上圖為一般網頁的架構,圖中的HTML、Head、Body、Div稱為網頁元素(Element)
甚麼是網頁元素呢?
我們可以將他視為一個「人」,以我們熟悉的輩分來解釋就是
HTML為Head和Body的父親
Head和Body互為兄弟
Body底下還有一個兒子叫做Div

其實,專業術語就是這麼稱呼的
我們說
HTML是Head和Body的父節點(ParentNode)
Head和Body互為兄弟節點(SiblingNode)
Div為Body的子節點(ChildNode)

了解這個觀念後,我們接下來就正式抓取網頁

首先我們要找出存有資料的元素是哪一個
經過觀察後,可以發現資料放在一個Div的Tag
其Class屬性為"container-fluid bgwhite bordergray pb50"
然而我們怎麼讓電腦知道我們要抓的就是這個元素呢?

在以前,只和大家講過兩種方式
1.直接用數的
但是此網站滿滿的Div Tag,不容易數

2.觀察元素的內容,透過迴圈+IF判斷式來鎖定
但是這是建立在「有固定內容」的基礎上,這個元素的內容時時刻刻在變化
今天用了某個關鍵字來鎖定,下一分鐘就失效了

因此,今天要說的是第三種方式
觀察此元素的父節點、兄弟節點、子節點,如果在這些節點中
有固定內容,那我們就可以鎖定這個節點,再找到我們想要的元素

聽起來很抽象對吧?

我們來實際操作看看

透過觀察,我可以知道網站裡的「ISSUE NO」是不會改變的
而包含這個關鍵字的元素是Class為"container-fluid bgwhite bordergray"的Div
而這個元素恰好是我們要的元素的兄弟節點
因此到這邊的Code為

Dim myXML As Object
Set myXML = CreateObject("Microsoft.XMLHTTP")

Dim myHTML As Object
Set myHTML = CreateObject("HTMLFile")

With myXML
    .Open "GET", "http://www.speedy-ball.com/speedy10-result.aspx?t=" & t, False
    .send

    myHTML.body.innerHTML = .responseText
    
    Set myDivs = myHTML.getElementsByTagName("div")
    i = 5
    For Each myDiv In myDivs
        If myDiv.innerText Like "*ISSUE NO*" Then
            Set mySec = myDiv.NextSibling
        End If
    Next
    
    
End With

Set myXML = Nothing

Debug.Print Format(Timer - t, "0.00秒")

其中的mySec就是我們要的元素了
抓到我們要的元素之後,我們看到資料是放在此元素的「孫子」裡


但是沒有所謂的「孫節點」,所以我們就用兒子的兒子來替代
這邊要注意的是,childnodes傳回來的是一個集合,因此要指定索引值

因此要修改為
Set mySecs = myDiv.NextSibling.ChildNodes(0).ChildNodes

接下來用ForEach迴圈把集合跑一遍
並且設定,如果元素的內容為"Load More Results"就離開迴圈

完整的Code:
Sub test()

Dim myXML As Object
Set myXML = CreateObject("Microsoft.XMLHTTP")

Dim myHTML As Object
Set myHTML = CreateObject("HTMLFile")

With myXML
    .Open "GET", "http://www.speedy-ball.com/speedy10-result.aspx?t=" & t, False
    .send

    myHTML.body.innerHTML = .responseText
    
    Set myDivs = myHTML.getElementsByTagName("div")
    i = 5
    For Each myDiv In myDivs
        If myDiv.innerText Like "*ISSUE NO*" Then
            Set mySecs = myDiv.NextSibling.ChildNodes(0).ChildNodes
            For Each mySec In mySecs
                If mySec.ChildNodes(0).innerText = "Load More Results" Then Exit For
                Cells(i, 1) = mySec.ChildNodes(0).innerText
                
                Cells(i, 2) = mySec.ChildNodes(1).innerText
                Set myNumbers = mySec.ChildNodes(2).ChildNodes
                j = 3
                For Each myNumber In myNumbers
                    Cells(i, j) = myNumber.innerText
                    j = j + 1
                Next
            i = i + 1
            Next
            Exit For
        End If
    Next
    
    
End With

Set myXML = Nothing

Debug.Print Format(Timer - t, "0.00秒")

End Sub

這對於初學者算是難度蠻高的網站
但難的不是在找資料源,或是破解防護機制
而是深入理解網頁的架構
建議可以使用逐行執行+區域變數視窗
慢慢的理解目前是在哪一個元素,而元素中有哪些可以提取的屬性
多練習幾次才能抓住其中的精隨

10 則留言:

  1. 因為網站一直在更新,建議習慣上可以多加上這3行
    可以避免一直抓到cache,無法即時更新資料
    您可以用這個樂透網站試看看,新號碼開出後,有加上沒加上的差別
    .setRequestHeader "Cache-Control", "no-cache"
    .setRequestHeader "Pragma", "no-cache"
    .setRequestHeader "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"

    回覆刪除
  2. 感謝Snare大的分享,我之前處理抓即時資料的方式都是直接在URL後面加個"?t=timer",我會再試試看Snare大的方式!

    回覆刪除
  3. 請問"Next Lottery Draw"下次出球時間,應有個伺服端函數在做處理,該如何直接讀取該值,而非抓取HTML中 Tag "div class="countdown" innerText,這該如何做? 煩請指導,謝謝!

    回覆刪除
    回覆
    1. 請問一下這麼做的目的是?

      刪除
    2. 想要同步即時更新出球時間,以便抓取最新的出球結果;不必一直下載整個HTML,來偵測是否時間已到!

      刪除
  4. 作者已經移除這則留言。

    回覆刪除
  5. 作者已經移除這則留言。

    回覆刪除
  6. (上面2篇,打錯字,不知道怎麼編輯,只好刪除重發 sorry)
    Shock168
    是問我嗎??
    從出球結果可知,出球時間是固定75秒
    而且伺服器端的資料也不是我們能控制的,看到的都是整理好送出來的資料
    所以沒必要去煩惱這個問題,因為您也改不了


    從原始碼分析,每次出球資料是從這個網址送出來的
    http://www.speedy-ball.com/tools/service_ajax.ashx?action=speed10lastone
    可用這個網址,使用Application.OnTime,設定75秒更新一次
    程式碼還可以縮短很多

    回覆刪除
    回覆
    1. 謝謝! 我就是想知道這個,想要做即時同步擷取出球資訊,不必每次都要下載整個HTML來偵測,只要知道個端口,時間到,就去下載HTML讀取出球資訊! 感恩! :)

      刪除
  7. 老师:利用VBA,請問如何抓取网页里的直播电视源呢?这些直播源都是有加密和时间性的,谢谢。
    例如:
    https://news.now.com/home/live331a
    http://news.tvb.com/live/inews

    回覆刪除