VBA / 抓取網路 JSON資料

VBA無法像 JavaScript等程式有現成的編譯函數

所以處理的方式會有點瑣碎,資料必須進行多次的切割

我參考了之前東吳VBA課程吳老師的教學範例

修改成使用XMLHTTP 物件來抓取網頁資料,而不是先將資料存在儲存格中

這是因為一個儲存格中僅能顯示32,767 個字元,如果資料量太多就無法先存在儲存格

只能將資料存在變數之中,再透過字串分割處理來留下需要的資料

使用XMLHTTP 物件必須額外引入Microsoft HTML Object Library

引用物件的方式分為2種

Early Binding 和 Late Binding

Early Binding 就是先在工具->設定引用項目->勾選

之後用 New 物件名稱來建立物件

如果要控制 IE 或者 Windows系統和檔案,也同樣需要引入對應的物件

 

 

Late Binding 則是透過CreateObject(物件名稱)

 

用來練習的檔案都來自政府資料開放平臺

但是這個平臺只是匯集政府各單位的資料

所以不同的單位可能會有不一樣的Json內容

有些是很單純的資料,

有些還會有很多說明資訊,而資料是其中一個屬性內容,例如以下的資料


 

所以必須針對不同資料型態適當修改程式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
'設定網址
Const URl As String = "https://data.epa.gov.tw/api/v1/aqx_p_432?limit=1000&api_key=9be7b239-557b-4c10-9775-78cadfc555e9&format=json"

'分割單筆json資料 {"key1":"value1",....}
Sub JSON_Split_Range(S, k)
     '處理資料
    'Debug.Print "S1-" & S
    S = Replace(S, """", "")
    'Debug.Print "S2-" & S
    S = Replace(S, " ", "")
    'Debug.Print "S3-" & S
    '分割單筆json資料為 key1:value1......
    arr = Split(Mid(S, 2, Len(S) - 2), ",")   '第一筆跳過{ 開始取字串 第二筆之後跳過 , 得到 key:value格式的字串陣列
    'Debug.Print "arr(0)-" & arr(0)
    c = 1
    For i = 0 To UBound(arr)
        'Debug.Print "arr(i)" & arr(i)
        '分割 key1:value1
        arr2 = Split(Trim(arr(i)), ":")
        'Debug.Print "arr2(0)" & arr2(0)
        'Debug.Print "arr2(1)" & arr2(1)
        
        If k = 1 Then
            'Debug.Print " arr2(0)-"& arr2(0)
            'Debug.Print " arr2(1)-"& arr2(1)
            Cells(k, c) = arr2(0)
            Cells(k + 1, c) = arr2(1)
        Else: Cells(k + 1, c) = arr2(1)
        End If
        c = c + 1
    Next
End Sub

'透過XMLHTTP 抓取網頁資料
Sub xmlHttp()
    
    Application.ScreenUpdating = False
    '刪除舊資料
    Sheets(1).Select
    Cells.Select
    Selection.ClearContents
    Range("A1").Select
    
     '設定 XMLHTTP 物件
    Dim xmlHttp As Object
    Set xmlHttp = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    xmlHttp.Open "GET", URl, False
    xmlHttp.setRequestHeader "Content-Type", "application/json ; charset=utf-8"
    xmlHttp.setRequestHeader "Cache-Control", "no-cache"
    xmlHttp.setRequestHeader "Pragma", "no-cache"
    xmlHttp.setRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"
    xmlHttp.send
    
    '儲存格內容的長度 (文字)
    '32,767 個字元。在一個儲存格中僅能顯示1,024 個字元;在資料編輯列顯示全部 32,767 個字元。

    'XMLHttp Response
    'T = xmlHttp.getResponseHeader("content-length") '資料字元數
    'P = Len(xmlHttp.ResponseText)                   '資料內容長度
    
    S = xmlHttp.ResponseText                         '回傳資料內容
    
    '連線沒問題才處理
    If xmlHttp.readyState = 4 Then
        If xmlHttp.Status = 200 Then
            
            '處理抓回來的資料  要依據實際資料進行調整
            
            startVal = InStr(1, S, """" & "records" & """", vbTextCompare) ' "records"
            startVal = InStr(startVal, S, "[", vbTextCompare)  '[
            endVal = InStr(startVal, S, "]", vbTextCompare)    ']
            strDiv = Mid(S, startVal, (endVal - startVal) + 1) '從"records" 後取出[{...}]
            'Debug.Print "strDiv-" & strDiv
            
            'Debug.Print "Mid(strDiv, 2, Len(strDiv) - 2" & Mid(strDiv, 2, Len(strDiv) - 2) '從[...]取出 {..).{..}  -
            arr = Split(Mid(strDiv, 2, Len(strDiv) - 2), "}")       '>split }  最後一個分割 會沒有資料

            k = 1
            'Debug.Print "UBound(arr)-" &UBound(arr)
            For i = 0 To UBound(arr) '回傳陣列的index數
                'Debug.Print "arr(i)-" & arr(i)
                If arr(i) <> "" Then                 '最後一筆分割沒資料
                    Call JSON_Split_Range(arr(i), k) '再分割
                End If
                k = k + 1
            Next
            
        End If
    End If
    MsgBox ("輸出完成")
    Application.ScreenUpdating = True
   
 End Sub

 

完成的樣子


 

後來在網路上發現可以從excel直接讀取Json檔

Excel 2016從資料→新查詢→其他來源→從Web

網路的教學在從檔案裡就有Json的選項,應該是 2016之後的版本

 

→貼入Json來源,就可以將資料讀進 Excel Power Query進行處理

 

因為抓取的Json檔,將資料放在名稱為records屬性裡

所以點擊 records的List

會出現所有records的資料

 

在清單案右鍵→到表格

或者左上的按鈕也一樣

 

會出現資料欄位,因為是要抓所有資料,就直接按確定

如果要選擇部分欄位的話,可以按載入更多顯示所有欄位

 

這邊用預設的”無”就可以了

選其他的分隔符號都無法成功建立表格

 

成功建立表格

 

按關閉並載入就會回到Excel

 

載入的資料會套用表格,並開啟篩選的功能

 

後記

在過程發生了2個情況

第1個情況是前面有提到的有的Json檔可以直接在網頁開啟,有的卻會直接下載

後者無法在VBA XMLHTTP抓取資料,經過Google大神的開示

這是網頁 HTTP response header之中 Content-Disposition的關係

如果Content-Disposition屬性值為:attachment,就會下載成附件

屬性值如果是inline(默认值 )就會以網頁形式顯示

例如使用Chrome外掛Header Editor修改之後,就能夠直接顯示在瀏覽器頁面

(用xmlHttp.getResponseHeader(“Content-Disposition”)得到的值仍然是attachment)

所以在XMLHTTP增加了xmlHttp.setRequestHeader “Content-Disposition”, “inline”

這樣就能夠抓取資料

備註:後來更新Excel之後,Power Query的”從Web”也可以在”進階”設定Header的參數

 

備註2:這個方式跟POWER BI一樣,使用了 Power Query M語言建立查詢

 

第2個情況是在其他電腦利用Excel Power Query建立連線時

會出現”要求已經中止: 無法建立SSL/TLS 的安全通道”

Google大神表示可能是.Net的問題

後來我後來發現兩臺電腦Excel執行”從Web”設定連結的介面不太一樣,有問題的沒有”基礎”、”進階”的選項

所以猜想可能是 Excel Power Query的問題

 

執行Windows跟Offiec更新(在進階選項要開啟”收到其他Microsoft產品的更新”)

確實.Net 跟 Excel2016 都有更新檔

更新之後就能正常連線了