Lấy dữ liệu cổ phiếu từ Website Cafef.vn về Excel (1 người xem)

Liên hệ QC

Người dùng đang xem chủ đề này

Status
Không mở trả lời sau này.

Kiều Mạnh

I don't program, I beat code into submission!!!
Tham gia
9/6/12
Bài viết
5,538
Được thích
4,133
Giới tính
Nam
1/ ý tưởng xuất phát từ chủ đề sau


2/ Tôi đang viết thư viện GoogleSheets trên Delphi có sử dụng dữ liệu JSON nên nhân tiện thử dữ liệu Website của mục trên

3/ Viết riêng ra vài hàm chuyên cho xử lý dữ liệu của Website cafef.vn
xuất ra ba hàm API cơ bản sử dụng các thư viện thuần trên Delphi 12
Mã:
Declare PtrSafe Function GetDataJSONFromCafefVN Lib "GoogleSheets64.dll" _
            (ByVal Symbol As Variant, ByVal StartDate As Variant, _
            ByVal EndDate As Variant, ByVal PageIndex As LongPtr, _
            ByVal PageSize As LongPtr) As Variant
    Rem ##########
    Declare PtrSafe Function JSONToArray Lib "GoogleSheets64.dll" _
            (ByVal JsonString As Variant, _
            Optional ByRef IncludeHeaders As Boolean = True) As Variant
          
    Rem ########## ThreadCount = so Luong tuy chinh 2,4,6 hay 20 luong la so chan
    Declare PtrSafe Function JSONToArrayMultiThreaded Lib "GoogleSheets64.dll" _
            (ByVal JsonString As Variant, _
            Optional ByRef IncludeHeaders As Boolean = True, _
            Optional ByVal ThreadCount As LongPtr = 0) As Variant

1/ Hàm GetDataJSONFromCafefVN lấy tên theo trang Web để không nhầm lẫn sử dụng sai mục đích
lấy Web khác sẻ lỗi hàm trả về chuỗi JSON khi ta thử code sẻ thấy là

1725935153143.png

2/ Hàm JSONToArray chuyển mảng JSON của mục số 1 thành mảng gán lên Sheet của Excel ( Không sử dụng đa luồng )

3/ Hàm JSONToArrayMultiThreaded chuyển mảng JSON của mục số 1 thành mảng gán lên Sheet của Excel ( sử dụng đa luồng chạy song song )

hàm này tuỳ chỉnh tham số luồng chạy song song là 2,4,6,8 hay 20 luồng tuỳ theo cấu hình máy và là số chẳn

Tôi thử dữ liệu ít nó gần như nhau ... có lẻ sẻ có sự khác biệt khi chạy đa luồng cho dữ liệu lớn

Từ 3 hàm trên tuỳ chỉnh viết hàm phụ sử dụng cho sự kiện trên Sheet của VBA
Mã:
Sub ImportDataFrom_CafefVN(Symbol As Variant, _
                           StartDate As Variant, EndDate As Variant, _
                           PageIndex As Long, PageSize As Long, _
                           Sh As Worksheet)
    Rem ##########
    Dim JSONData As Variant
    Dim JSONToArr As Variant
    Dim startTime As Single
    Dim endTime As Single
    Dim elapsedTime As Single

    On Error GoTo ErrorHandler
    startTime = Timer

    Rem Goi hàm GetDataJSONFromCafefVN
    JSONData = GetDataJSONFromCafefVN(Symbol, StartDate, EndDate, PageIndex, PageSize)
  
    Rem 8 hay 10 là so luong tuy chinh khi su dung da luong song song
    JSONToArr = JSONToArrayMultiThreaded(JSONData, True, 8)         ' True = lay tieu de ; False = ko lay tieu de
  
    Rem ### Neu may cu yeu thi thay the da luong sang ham sau
    Rem JSONToArr = JSONToArray(JSONData, True)                     ' True = lay tieu de ; False = ko lay tieu de
  
    Debug.Print "Row " & UBound(JSONToArr, 1)
    Debug.Print "Cols " & UBound(JSONToArr, 2)
  
    Rem Kiem tra du lieu tra ve
    If IsEmpty(JSONToArr) Then
        MsgBox "Du lieu tra ve rong"
        Exit Sub
    End If
  
    Rem Gán ket qua lên Sheet (ví du tai ô A11)
    Sh.Range("A11").Resize(UBound(JSONToArr, 1), UBound(JSONToArr, 2)).Value = JSONToArr
  
    endTime = Timer
  
    Rem Tính thoi gian dã chay
    elapsedTime = endTime - startTime
  
    Rem Hien thi thoi gian dã chay trên StatusBar
    Application.StatusBar = "Elapsed Time: " & Format(elapsedTime, "0.000") & " seconds."
  
    Exit Sub
ErrorHandler:
    MsgBox "Ðã xay ra loi: " & Err.Description, vbCritical
End Sub

Hình ảnh sử dụng trên Sheet chỉ cần gõ các tham số mục khoanh màu đỏ và nhận kết quả trả về trên Sheet

1725935522304.png

Lưu ý:

1/ File sau chỉ áp dụng cho Website như mô tả trên nếu áp dụng cho Website khác là lỗi

2/ Hàm JSONToArrayMultiThreaded chuyển chuỗi JSON sử dụng cho Sheet thì tuỳ theo dữ liệu JSON của từng Web có thể sử dụng tốt hay lỗi vì dữ liệu JSON có rất nhiều loại
và hàm này chưa xử xử lý hết mọi loại JSON. nếu muốn sử dụng cho Web khác thử xem nếu chạy thì dùng còn không thì bỏ

3/ quá trình sử dụng mọi phát sinh lỗi nếu có vui lòng mô tả chi tiết nếu được tôi sẻ điều chỉnh code cho phù hợp

4/ hai DLL luôn đi kèm file Excel trong cùng thư mục sử dụng cho 32 và 64 bit ... nếu Copy vào thự mục của Windows thì viết lại code trong VBA và loại bỏ Module mod_LoadLibrary ra khỏi file Excel
 

File đính kèm

Lần chỉnh sửa cuối:
hi anh, nếu xịn vầy thì anh anh xem có thể nâng tùy biến của các hàm dưới, mỗi lần viết dll thì có thể dùng và ứng dụng.

1. Hàm ImportDataFrom_CafefVN cho phép tùy biến:

+ Url địa chỉ chứa dữ liệu;
+ Phương pháp truyền (method) GET, POST, ...
+ PostData: tham số gửi về web;
+ cookie để mở rộng sau này
Thì trở thành GetDataJSONFromCafefVN(url As String, method As String, url As String, cookie As String, datasend As String)
Tất cả đều trả về .responseText (string) còn định dạng là JSON hay HTML thì sau đó xử lý sau
Sẽ ứng dụng được rộng rãi, không chỉ web này và còn các web khác.

2. Hàm xử lý chuỗi JSON

Hàm này thì cần cho tham số nhận từ dạng text, rồi cho phép phân tích (ParseJson), chỉ nhận về chuỗi Json là xong.
 
hi anh, nếu xịn vầy thì anh anh xem có thể nâng tùy biến của các hàm dưới, mỗi lần viết dll thì có thể dùng và ứng dụng.

1. Hàm ImportDataFrom_CafefVN cho phép tùy biến:

+ Url địa chỉ chứa dữ liệu;
+ Phương pháp truyền (method) GET, POST, ...
+ PostData: tham số gửi về web;
+ cookie để mở rộng sau này
Thì trở thành GetDataJSONFromCafefVN(url As String, method As String, url As String, cookie As String, datasend As String)
Tất cả đều trả về .responseText (string) còn định dạng là JSON hay HTML thì sau đó xử lý sau
Sẽ ứng dụng được rộng rãi, không chỉ web này và còn các web khác.

2. Hàm xử lý chuỗi JSON

Hàm này thì cần cho tham số nhận từ dạng text, rồi cho phép phân tích (ParseJson), chỉ nhận về chuỗi Json là xong.
Hôm lâu tôi có viết 3 hàm sau sử dụng Indy trong Delphi 12

Mã:
Declare PtrSafe Function HttpRequest Lib "WebServerInfo64.dll" _
                (ByRef URL As Variant, ByRef Method As Variant, _
                ByRef Data As Variant) As Variant
Rem ##########
Declare PtrSafe Function ExecuteHttpRequest Lib "WebServerInfo64.dll" _
                (ByRef URL As Variant, ByRef Method As Variant, _
                ByRef Data As Variant) As Variant
Rem ########## su dung luong de an toan hon
Declare PtrSafe Function ExecuteHttpRequestAsync Lib "WebServerInfo64.dll" _
                (ByRef URL As Variant, ByRef Method As Variant, _
                ByRef Data As Variant) As Variant

Ba hàm trên như nhau nhưng khác thuật toán và cách viết .. nó thay thế hoàn toàn sử dụng MSXML2.XMLHTTP, MSXML2.ServerXMLHTTP.6.0,WinHttp.WinHttpRequest.5.1 khi truyền tham số vào hàm

chỉ là hàm có sẳn trên Delphi tôi copy từ nguồn ra khai báo truyền tham số thuần vào và không can thiệp gì hết ... nếu thích tôi úp cho nhưng sau vài ngày chi đó

Thự viện trên có các hàm cho Google Sheet khi lấy dữ liệu trả về các phương thức thuộc tính của ADODB trên Excel như
CopyFromRecordset rs , rs.GetRows() và .... ai rảnh thử copy Code sau chạy thử

Lưu ý tôi xoá bớt ( Làm sai ) SheetId và APIKey rồi vì vậy muốn sử dụng phải tự tạo APIKey

Mã:
Declare PtrSafe Function GoogleSheetAsRecordset Lib "GoogleSheets64.dll" _
            (ByRef SheetId As Variant, _
             ByRef APIKey As Variant, _
             ByRef sheetName As Variant) As Variant
       
Sub ImportGoogleSheetData()
    Dim SheetId As String
    Dim APIKey As String
    Dim sheetName As String
    Dim rs As Object
    Dim ws As Worksheet
 
    ' Set your Google Sheet details
    SheetId = "1punDQ-pRaMsi5pj4ZrhNLFkOD3qkoo" ' ID c?a Google Sheet
    APIKey = "AIzaSyC-ZEXyJH-9BwlpL-KCTZlxJw" ' API Key c?a b?n
    sheetName = "Test" ' Tên c?a sheet
 
    ' Call the Delphi function
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, sheetName)
 
    ' S? d?ng rs nhu Recordset
'    Do While Not rs.EOF
'        Debug.Print rs.Fields(1).Value ''1 là cot 2
'        rs.MoveNext
'    Loop
 
    Cells.ClearContents
    ' Paste the data into the worksheet
    Set ws = ThisWorkbook.Sheets("Sheet1")  ' Change to your target sheet
    ws.Range("A1").CopyFromRecordset rs
 
    ' Cleanup
    rs.Close
    Set rs = Nothing
End Sub
Bài đã được tự động gộp:

các hàm dựng sẳn cho Google Sheet

Mã:
Declare PtrSafe Function ExtractSheetIdFromURL Lib "GoogleSheets64.dll" _
            (ByRef url As Variant) As Variant

Declare PtrSafe Function GoogleSheetJSONToArray Lib "GoogleSheets64.dll" _
            (ByRef response As Variant) As Variant

Declare PtrSafe Function ProcessJSONResponse Lib "GoogleSheets64.dll" _
            (ByRef response As Variant) As Variant

Declare PtrSafe Function GetGoogleSheetNames Lib "GoogleSheets64.dll" _
            (ByRef SheetId As Variant, ByRef APIKey As Variant) As Variant

Declare PtrSafe Function GoogleSheetAsRecordset Lib "GoogleSheets64.dll" _
            (ByRef SheetId As Variant, _
             ByRef APIKey As Variant, _
             ByRef sheetName As Variant) As Variant

Declare PtrSafe Function FilterGoogleSheetAsRecordset Lib "GoogleSheets64.dll" _
            (ByRef SheetId As Variant, _
             ByRef APIKey As Variant, _
             ByRef APIKey As Variant, _
             ByRef ColumnIndex As Variant, ByRef FilterValue As Variant) As Variant
                       
                       
Declare PtrSafe Function GoogleSheetsToJSON Lib "GoogleSheets64.dll" ( _
            ByVal SheetId As Variant, _
            ByVal APIKey As Variant, _
            ByVal sheetName As Variant) As Variant

ai cần sử dụng hàm nào tôi chỉ dẫn .. vì dự án cho Google Sheets còn viết tiếp nữa và có liên quan máy chủ WebServer của tôi đẩy dữ liệu lên Google Sheet chưa hoàn thiện và lỗi
 
Lần chỉnh sửa cuối:
cách lấy dữ liệu Google Sheet không sử dụng ADODB trên VBA .. Lưu ý tôi xoá sai SheetId và APIKey .. ai muốn sử dụng tự khai báo APIkey của Google Sheet xong sử dụng

Mã:
Sub TestGoogleSheetsToJSON()
    Dim SheetId As String
    Dim APIKey As String
    Dim sheetName As String
    Dim response As String
    Dim result As Variant
  
    SheetId = "1punDQ-pRauHri5pj4ZrhNLFkOD3qkoo" ' ID c?a Google Sheet
    sheetName = "Test" ' Tên c?a Sheet trong Google Sheet
    APIKey = "AIzaSyC-ZpL-KCTZlxJw" ' API Key c?a Google Sheets API
  
    ' G?i hàm API và luu k?t qu?
    response = GoogleSheetsToJSON(SheetId, APIKey, sheetName)
              
    ' Hi?n th? k?t qu?
    Debug.Print response
    result = GoogleSheetJSONToArray(response)
  
    Cells.ClearContents
    Range("A1").Resize(UBound(result, 1) + 1, UBound(result, 2) + 1).Value = result
End Sub

Cách tạo APIKey link sau nếu không thích thì Google một cái nó ra cả tấn xong thong thả dò đi làm quen từ từ _)()(-
 
hi anh, nếu xịn vầy thì anh anh xem có thể nâng tùy biến của các hàm dưới, mỗi lần viết dll thì có thể dùng và ứng dụng.

1. Hàm ImportDataFrom_CafefVN cho phép tùy biến:

+ Url địa chỉ chứa dữ liệu;
+ Phương pháp truyền (method) GET, POST, ...
+ PostData: tham số gửi về web;
+ cookie để mở rộng sau này
Thì trở thành GetDataJSONFromCafefVN(url As String, method As String, url As String, cookie As String, datasend As String)
Tất cả đều trả về .responseText (string) còn định dạng là JSON hay HTML thì sau đó xử lý sau
Sẽ ứng dụng được rộng rãi, không chỉ web này và còn các web khác.

2. Hàm xử lý chuỗi JSON

Hàm này thì cần cho tham số nhận từ dạng text, rồi cho phép phân tích (ParseJson), chỉ nhận về chuỗi Json là xong.
Nếu chịu khó phối họp cùng tôi test code và dò thì sẻ biến cái không thể thành có thể vì bên ta luôn có Em ChatGPT tuyệt đỉnh

3 Hàm tôi nói trên thiếu 3 tham số mới viết hàm mới có cấu trúc như sau ... xem sao nếu ưng tôi test kỹ xong úp

Mã:
Declare PtrSafe Function SendHttpRequest Lib "GoogleSheets64.dll" ( _
    ByVal method As Variant, _
    ByVal URL As Variant, _
    ByVal contentType As Variant, _
    ByVal accept As Variant, _
    ByVal userAgent As Variant, _
    ByVal dataToSend As Variant) As Variant
   
Sub GetDataFromGoogleSheet()
    Dim response As Variant
    Dim URL As String
    Dim method As String
    Dim contentType As String
    Dim accept As String
    Dim userAgent As String
    Dim dataToSend As Variant
   
    ' ID c?a b?ng tính
    Dim spreadsheetId As String
    spreadsheetId = "1-0nB16Fefr8cTbEV7Gj5VjcegL5GcfMIc4RP5C9Sn4w"

    ' Ðu?ng d?n URL d? l?y d? li?u CSV t? Google Sheets
    URL = "https://docs.google.com/spreadsheets/d/" & spreadsheetId & "/gviz/tq?tqx=out:csv"

    method = "GET"
    contentType = "text/csv"
    accept = "text/csv"
    userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
    dataToSend = Null  ' Không g?i d? li?u cho phuong th?c GET

    ' G?i hàm SendHttpRequest
    response = SendHttpRequest(method, URL, contentType, accept, userAgent, dataToSend)

    ' Hi?n th? ph?n h?i
    Debug.Print response
End Sub

dữ liệu JSON google sheet khác biệt các kiểu JSON khác xem hình hàm mới thêm 3 tham số nếu truyền vào hàm khoanh màu xanh

xử lý các yêu cầu HTTP với nhiều URL, tiêu đề, và phương thức khác nhau (GET, POST, PUT, DELETE).

1725968131242.png
 
To @huhumalu .. viết hàm theo cách bạn gợi ý ... thử xem sao xong tính tiếp .. Lưu ý hai hàm sau chưa tương thích với hàm JSONToArrayMultiThreaded về kiểu dữ liệu mà hàm chỉ trả về dữ liệu mà bạn cần còn xửu lý sao thì ta lại tính tiếp

Copy copde sau vào File Excel bài số 1

Mã:
#If Win64 Then
    Rem ##########
    Declare PtrSafe Function SendHttpRequest Lib "GoogleSheets64.dll" ( _
            ByVal Method As Variant, _
            ByVal URL As Variant, _
            ByVal ContentType As Variant, _
            ByVal Accept As Variant, _
            ByVal UserAgent As Variant, _
            ByVal DataToSend As Variant) As Variant
    Rem ##########
    Declare PtrSafe Function SendHttpRequestEx Lib "GoogleSheets64.dll" ( _
            ByVal Method As Variant, _
            ByVal URL As Variant, _
            ByVal ContentType As Variant, _
            ByVal Accept As Variant, _
            ByVal UserAgent As Variant, _
            ByVal DataToSend As Variant, _
            ByRef ResponseData As Variant) As Boolean
    Rem ##########
    Declare PtrSafe Function ExtractSheetIdFromURL Lib "GoogleSheets64.dll" _
            (ByRef URL As Variant) As Variant
#Else
    Rem ##########
    Declare Function SendHttpRequest Lib "GoogleSheets32.dll" ( _
          ByRef Method As Variant, _
          ByRef URL As Variant, _
          ByRef ContentType As Variant, _
          ByRef Accept As Variant, _
          ByRef UserAgent As Variant, _
          ByRef DataToSend As Variant) As Variant
    Rem ##########
    Declare Function SendHttpRequestEx Lib "GoogleSheets32.dll" ( _
          ByRef Method As Variant, _
          ByRef URL As Variant, _
          ByRef ContentType As Variant, _
          ByRef Accept As Variant, _
          ByRef UserAgent As Variant, _
          ByRef DataToSend As Variant, _
          ByRef ResponseData As Variant) As Boolean
    Rem ##########
    Declare Function ExtractSheetIdFromURL Lib "GoogleSheets32.dll" _
            (ByRef URL As Variant) As Variant
#End If

Sub Test_ExtractSheetIdFromURL()
    Dim URL As String, SheetId As String
    URL = "https://docs.google.com/spreadsheets/d/1-0nB16Fefr8cTbEV7Gj5VjcegL5GcfMIc4RP5C9Sn4w/edit?gid=553666518#gid=553666518"
    SheetId = ExtractSheetIdFromURL(URL)
    Debug.Print SheetId
End Sub

Giải Thích:
1/ Hàm SendHttpRequest dữ liệu trả về hàm
2/ Hàm SendHttpRequestEx dữ liệu trả về tham số ResponseData

Hai hàm trên như nhau chỉ khác cách sử dụng và tuỳ chỉnh

Hàm SendHttpRequest SendHttpRequestEx có thể được sử dụng hiệu quả để gửi và nhận dữ liệu
từ máy chủ khi bạn có đầy đủ thông tin cần thiết như URL, phương thức HTTP (GET, POST, PUT, DELETE),
và các tiêu đề cần thiết (Content-Type, Accept, User-Agent)


Tôi mượn cái link google Sheet của thành viên GPE thử code Hàm

Mã:
Sub GetDataFromGoogleSheet_CSV2()
    Dim csvData As String
    Dim lines As Variant
    Dim line As Variant
    Dim values As Variant
    Dim cleanedValue As String
    Dim i As Long
    Dim j As Long

    ' Gi? s? csvData ch?a d? li?u CSV t? hàm SendHttpRequest
    csvData = SendHttpRequest("GET", "https://docs.google.com/spreadsheets/d/1-0nB16Fefr8cTbEV7Gj5VjcegL5GcfMIc4RP5C9Sn4w/gviz/tq?tqx=out:csv", "text/csv", "text/csv", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", Null)
    Debug.Print csvData

    ' Tách d? li?u thành t?ng dòng
    lines = Split(csvData, vbLf)
  
    Cells.ClearContents

    ' Duy?t t?ng dòng d? l?y giá tr?
    For i = LBound(lines) To UBound(lines)
        ' Tách t?ng dòng thành các giá tr?
        values = Split(lines(i), ",")

        ' Luu giá tr? vào b?ng tính (thay Sheet1 b?ng tên c?a trang tính c?a b?n)
        For j = LBound(values) To UBound(values)
            ' Lo?i b? các d?u "" th?a
            cleanedValue = Replace(values(j), """", "")
            Sheets("Sheet2").Cells(i + 1, j + 1).Value = Trim(cleanedValue)
        Next j
    Next i

    'MsgBox "D? li?u dã du?c luu vào b?ng tính!"
End Sub

Thử hàm SendHttpRequestEx

Mã:
Sub FetchVNIndexData()
    Dim ResponseData As Variant
    Dim Method As String
    Dim URL As String
    Dim ContentType As Variant
    Dim Accept As Variant
    Dim UserAgent As Variant
    Dim DataToSend As Variant
    Dim Result As Boolean
    Dim JSONToArr As Variant
  
    ' Kh?i t?o các tham s?
    URL = "https://s.cafef.vn/Ajax/PageNew/DataHistory/PriceHistory.ashx?Symbol=VNINDEX&StartDate=&EndDate=&PageIndex=1&PageSize=20"
    Method = "GET"
    ContentType = "application/json"
    Accept = "application/json"
    UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0"
    DataToSend = Null  ' Du lieu di g?i, d? tr?ng cho phuong thuc GET

    ' G?i hàm SendHttpRequest
    Result = SendHttpRequestEx(Method, URL, ContentType, Accept, UserAgent, DataToSend, ResponseData)
    Debug.Print ResponseData

    ' X? lý ph?n h?i
    If Result Then
        ' N?u yêu c?u thành công, hi?n th? d? li?u nh?n du?c
        MsgBox "Response: " & ResponseData
    Else
        ' N?u có l?i, hi?n th? thông báo l?i
        MsgBox "Request failed. Response: " & ResponseData
    End If
End Sub

Rảnh thử xem xong tính



Tải 2 DLL sau về thay thế 2 DLL bài 1 .. file sau chỉ thêm hai hàm trên
 

File đính kèm

Em không hiểu hết cái Bác viết lắm nhưng nhiều code quá cảm nhận là phức tạp, món này chắc dành cho người có chuyên môn về mấy cái này. Vì không hiểu áp dụng vô vướng đâu đó là chào thua.
Bác cho em hỏi, cái này có lấy Shopee được không:
Nếu lấy được thì đỡ phải dùng Python
 
Em không hiểu hết cái Bác viết lắm nhưng nhiều code quá cảm nhận là phức tạp, món này chắc dành cho người có chuyên môn về mấy cái này. Vì không hiểu áp dụng vô vướng đâu đó là chào thua.
Bác cho em hỏi, cái này có lấy Shopee được không:
Nếu lấy được thì đỡ phải dùng Python
Cái này căng lắm không lấy được qua API đâu.
 
Căng mới cần hỏi giải pháp chứ bạn! Chứ không khó hỏi chi mất công các Bác
Mấy năm trước người ta có chia sẻ cách lấy dữ liệu qua API mà bên Shopee làm cũng gắt, hạn chế ngặt nghèo lắm. Hiện giờ mình chưa thấy cách nào hiệu quả hơn việc dùng Selenium để cào dữ liệu, hoặc chắc là có giải pháp mà người ta không chia sẻ ra.
 
CÁI tôi biết cái không chủ yếu là dò + mò cùng Em ChatGPT xong tư duy logis lắp ráp lại thôi

có gì cứ nêu lên ai biết gì xử lý cái đó xong hệ thống lại = Chốt hạ vậy

Nhờ cái thớt bên kia lần đầu tiên tôi hiểu và biết cách xử lý đa luồng chạy song song tại cùng một thời điểm
chia nhỏ ra cho mảng JSON xong giáp lại thanh mảng gán lên Sheet ... có lẻ sẻ áp dụng nhiều nó vì thời công nghệ RAM + Chip CPU - giá bèo giáp máy cấu hình tốt chút chạy đa luồng song song bay vèo vèo

Hôm lâu tôi nhớ tới IDM tải nhiều phần đa luồng song song về máy xong giáp lại thành 1 File.. xong tôi thử hỏi Chat mất buổi sáng cuối cùng cùng ra cách tải File đa luồng song song nhiều phần về máy xong hết nó giáp lại thành một File

xong thử hỏi mảng JSON xem sao ai dè mất hơn 1h nó ra

Hình sau tải file đa luồng song song nhiều phần xong giáp lại thành 1 file test code tôi để vậy xem khi hoàn thiện chuyển vào folder Temp ai thấy
87358bcd56e5f1bba8f4.jpg
 
Lần chỉnh sửa cuối:
Em không hiểu hết cái Bác viết lắm nhưng nhiều code quá cảm nhận là phức tạp, món này chắc dành cho người có chuyên môn về mấy cái này. Vì không hiểu áp dụng vô vướng đâu đó là chào thua.
Bác cho em hỏi, cái này có lấy Shopee được không:
Nếu lấy được thì đỡ phải dùng Python
Nếu được Úp code Python lên đi chia sẻ xem sao xong Tính
 
Nếu được Úp code Python lên đi chia sẻ xem sao xong Tính
Em chưa viết được Bác, Tikia, Lazada hay Web tương tự thì dễ hơn, Shopee cơ chế chặt chẽ, em đang làm thủ công webSCraper, Các Bác thạo món này nhờ các Bác hỗ trợ
Bài đã được tự động gộp:

Mấy năm trước người ta có chia sẻ cách lấy dữ liệu qua API mà bên Shopee làm cũng gắt, hạn chế ngặt nghèo lắm. Hiện giờ mình chưa thấy cách nào hiệu quả hơn việc dùng Selenium để cào dữ liệu, hoặc chắc là có giải pháp mà người ta không chia sẻ ra.
Bạn biết lấy bằng Selenium hoặc beautifulsoup không, chia sẻ dùm mình!
 
Lần chỉnh sửa cuối:
ngày nay vắng khách ... có lẻ tình hình chung là đói

Tôi phá các kiểu từ 10h sáng để tìm cái hàm chuyển mọi loại JSON cuối cùng thất bại.. được cái này tịt cái kia

chán quay sang phá cái Hàm JSON trên VBA mã nguồn có trên Github và trên GPE này sử dụng cũng nhiều dài trên 1000 dòng chi đó
cũng chỉ xử lý vài kiểu JSON mèo cào xong cũng đơ rồi văng vào Thùng RÁC ... //..,,

mất thời gian vào cái thứ tào lao ý tưởng viết hàm xử lý mọi chủng loại JSON gần như quá khó ...

tốt nhất cần xử lý loại Web nào xem nguồn xong viết hàm cho nhanh và bớt một đống If và Optional Rác rưởi làm chậm tốc độ code :p--=0--=0--=0
 
Em chưa viết được Bác, Tikia, Lazada hay Web tương tự thì dễ hơn, Shopee cơ chế chặt chẽ, em đang làm thủ công webSCraper, Các Bác thạo món này nhờ các Bác hỗ trợ
Bài đã được tự động gộp:


Bạn biết lấy bằng Selenium hoặc beautifulsoup không, chia sẻ dùm mình!
beautifulsoup: Không chạy được mã Javascript nhúng trong HTML, nên cách này vô dụng.
Selenium: Cái này bạn cần phải viết mã thật chuẩn sao cho Shopee không nhận diện bạn đang dùng bot để truy cập vào trang, nếu không rất dễ dính kiểm tra bằng captcha.
 
beautifulsoup: Không chạy được mã Javascript nhúng trong HTML, nên cách này vô dụng.
Selenium: Cái này bạn cần phải viết mã thật chuẩn sao cho Shopee không nhận diện bạn đang dùng bot để truy cập vào trang, nếu không rất dễ dính kiểm tra bằng captcha.
Thì ý tôi hỏi là bạn viết được không để nhờ đó, chứ mấy cái lý thuyết bạn nói đương nhiên là tôi hiểu rồi mà!
 
lâu này ta hay keo sử dụng Python lấy dữ liệu Web Siêu nhanh có lẻ cũng tới lúc xem xét lại rồi

Delphi lấy dữ liệu Web OK sạch chỉ là ta chưa biết cách sử dụng nó + nguồn trang Web cần lấy thôi

có điều lấy dữ liệu Web phụ thuộc vào máy chủ của nó cho hay không cho và nó chỉ cần thay đổi tí teo xem như căng mắt ra dò lại từ đầu

nên tôi chỉ dừng ở bức biết chút chút khi cần sử dụng là đủ ... rảnh chơi cái khác thú vị hơn

Hiểu ra nguồn trang Web + cách sử dụng Delphi = OK
 
nghe đâu đó còn cách cùng đường là tải HTML về xong bới tung RÁC lên tìm dữ liệu ... cách này xem ra là hết cách hay sao ý :p
 
Bạn cần lấy thông tin gì từ web https://shopee.vn/search?keyword=bánh gạo
và có thể chia sẻ phần code đã viết không ?
Để xem có hướng bẫy capcha, hoặc delay thời gian.
Bạn có thể tìm một số code tương tự trên Stack Overflow nhé, mình cũng lấy ở đây.
Bài đã được tự động gộp:

lâu này ta hay keo sử dụng Python lấy dữ liệu Web Siêu nhanh có lẻ cũng tới lúc xem xét lại rồi

Delphi lấy dữ liệu Web OK sạch chỉ là ta chưa biết cách sử dụng nó + nguồn trang Web cần lấy thôi

có điều lấy dữ liệu Web phụ thuộc vào máy chủ của nó cho hay không cho và nó chỉ cần thay đổi tí teo xem như căng mắt ra dò lại từ đầu

nên tôi chỉ dừng ở bức biết chút chút khi cần sử dụng là đủ ... rảnh chơi cái khác thú vị hơn

Hiểu ra nguồn trang Web + cách sử dụng Delphi = OK
Quan trọng là tiện lợi, nhanh và gọn mà bạn.
Python viết xong cái là chạy được ngay. Nhà mình có con Android TV box cùi chạy Armbian, toàn là viết mã Python xong vứt lên đó cho nó cào dữ liệu trên internet 24/7, tiết kiệm điện, chứ nếu cần cũng có thể viết C# .NET Core biên dịch thành ARM64 chạy trên Armbian vẫn ngon.
 
Lần chỉnh sửa cuối:
Bạn có thể tìm một số code tương tự trên Stack Overflow nhé, mình cũng lấy ở đây.
Bài đã được tự động gộp:


Quan trọng là tiện lợi, nhanh và gọn mà bạn.
Python viết xong cái là chạy được ngay. Nhà mình có con Android TV box cùi chạy Armbian, toàn là viết mã Python xong vứt lên đó cho nó cào dữ liệu trên internet 24/7, tiết kiệm điện, chứ nếu cần cũng có thể viết C# .NET Core biên dịch thành ARM64 chạy trên Armbian vẫn ngon.
có thấy nguồn nào xác định đó là loại JSON gì hay không kiểu như xác định Type khi truyền tham số DataJSON vào hàm sẻ trả về nếu là TJSONArray hay TJSONObject và cách loại khác .. cho link tham khảo chút

Mới xong cái hàm xác định chính xác 2 loại JSON cơ bản là TJSONArray và TJSONObject

còn kiểu sau là tịt :p

Mã:
/*O_o*/
google.visualization.Query.setResponse({"version":"0.6","reqId":"0","status":"ok","sig":"1617797785","table":{"cols":[{"id":"A","label":"","type":"number","pattern":"General"},{"id":"B","label":"","type":"string"},{"id":"C","label":"","type":"string"},{"id":"D","label":"","type":"string"},{"id":"E","label":"","type":"number","pattern":"General"},{"id":"F","label":"","type":"string"},{"id":"G","label":"","type":"string"},{"id":"H","label":"","type":"number","pattern":"General"},{"id":"I","label":"","type":"string"},{"id":"J","label":"","type":"string"},{"id":"K","label":"","type":"number","pattern":"General"},{"id":"L","label":"","type":"string"},{"id":"M","label":"","type":"string"},{"id":"N","label":"","type":"number","pattern":"General"},{"id":"O","label":"","type":"string"},{"id":"P","label":"","type":"string"},{"id":"Q","label":"","type":"number","pattern":"General"},{"id":"R","label":"","type":"string"},{"id":"S","label":"","type":"string"},{"id":"T","label":"","type":"number","pattern":"General"},{"id"
:"U","label":"","type":"string"},{"id":"V","label":"","type":"string"},{"id":"W","label":"","type":"number","pattern":"General"},{"id":"X","label":"","type":"string"},{"id":"Y","label":"","type":"string"},{"id":"Z","label":"","type":"number","pattern":"General"},{"id":"AA","label":"","type":"string"},{"id":"AB","label":"","type":"string"},{"id":"AC","label":"","type":"number","pattern":"General"},{"id":"AD","label":"","type":"string"},{"id":"AE","label":"","type":"string"},{"id":"AF","label":"","type":"number","pattern":"General"},{"id":"AG","label":"","type":"string"},{"id":"AH","label":"","type":"string"},{"id":"AI","label":"","type":"number","pattern":"General"},{"id":"AJ","label":"","type":"string"},{"id":"AK","label":"","type":"string"},{"id":"AL","label":"","type":"number","pattern":"General"},{"id":"AM","label":"","type":"string"},{"id":"AN","label":"","type":"string"},{"id":"AO","label":"","type":"number","pattern":"General"},{"id":"AP","label":"","type":"string"},{"id":"AQ","label":"","type":"string
"},{"id":"AR","label":"","type":"number","pattern":"General"},{"id":"AS","label":"","type":"string"},{"id":"AT","label":"","type":"string"},{"id":"AU","label":"","type":"number","pattern":"General"},{"id":"AV","label":"","type":"string"},{"id":"AW","label":"","type":"string"},{"id":"AX","label":"","type":"number","pattern":"General"},{"id":"AY","label":"","type":"string"},{"id":"AZ","label":"","type":"string"},{"id":"BA","label":"","type":"number","pattern":"General"},{"id":"BB","label":"","type":"string"},{"id":"BC","label":"","type":"string"},{"id":"BD","label":"","type":"number","pattern":"General"},{"id":"BE","label":"","type":"string"},{"id":"BF","label":"","type":"string"},{"id":"BG","label":"","type":"number","pattern":"General"},{"id":"BH","label":"","type":"string"},{"id":"BI","label":"","type":"string"},{"id":"BJ","label":"","type":"number","pattern":"General"},{"id":"BK","label":"","type":"string"},{"id":"BL","label":"","type":"string"},{"id":"BM","label":"","type":"number","pattern":"General"},{"
id":"BN","label":"","type":"string"},{"id":"BO","label":"","type":"string"},{"id":"BP","label":"","type":"number","pattern":"General"},{"id":"BQ","label":"","type":"string"},{"id":"BR","label":"","type":"string"},{"id":"BS","label":"","type":"number","pattern":"General"},{"id":"BT","label":"","type":"string"},{"id":"BU","label":"","type":"string"},{"id":"BV","label":"","type":"number","pattern":"General"},{"id":"BW","label":"","type":"string"},{"id":"BX","label":"","type":"string"},{"id":"BY","label":"","type":"number","pattern":"General"},{"id":"BZ","label":"","type":"string"},{"id":"CA","label":"","type":"string"},{"id":"CB","label":"","type":"number","pattern":"General"},{"id":"CC","label":"","type":"string"},{"id":"CD","label":"","type":"string"},{"id":"CE","label":"","type":"number","pattern":"General"},{"id":"CF","label":"","type":"string"},{"id":"CG","label":"","type":"string"},{"id":"CH","label":"","type":"number","pattern":"General"},{"id":"CI","label":"","type":"string"},{"id":"CJ","label":"","type
":"string"},{"id":"CK","label":"","type":"number","pattern":"General"},{"id":"CL","label":"","type":"string"},{"id":"CM","label":"","type":"string"},{"id":"CN","label":"","type":"number","pattern":"General"},{"id":"CO","label":"","type":"string"},{"id":"CP","label":"","type":"string"},{"id":"CQ","label":"","type":"number","pattern":"General"},{"id":"CR","label":"","type":"number","pattern":"General"},{"id":"CS","label":"","type":"string"},{"id":"CT","label":"","type":"string"}],"rows":[{"c":[null,null,{"v":"1"},null,null,{"v":"2"},null,null,{"v":"3"},null,null,{"v":"4"},null,null,{"v":"5"},null,null,{"v":"6"},null,null,{"v":"7"},null,null,{"v":"8"},null,null,{"v":"9"},null,null,{"v":"10"},null,null,{"v":"11"},null,null,{"v":"12"},null,null,{"v":"13"},null,null,{"v":"14"},null,null,{"v":"15"},null,null,{"v":"16"},null,null,{"v":"17"},null,null,{"v":"18"},null,null,{"v":"19"},null,null,{"v":"20"},null,null,{"v":"21"},null,null,{"v":"22"},null,null,{"v":"23"},null,null,{"v":"24"},null,null,{"v":"25"},null,null,
{"v":"26"},null,null,{"v":"27"},null,null,{"v":"28"},null,null,{"v":"29"},null,null,{"v":"30"},null,null,{"v":"31"},null,null,null,null,{"v":null}]},{"c":[null,{"v":"Tên"},{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"G
i? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,{"v":"Gi? vào"},{"v":"Gi? ra"},null,null,null,{"v":null}]},{"c":[{"v":3.0,"f":"3"},{"v":"B"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"}
,{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":31.0,"f":"31"},null,{"v":null}]},{"c":[{"v":4.0,"f":"4"},{"v":"B"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":
8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"1
7:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":31.0,"f":"31"},null,{"v":null}]},{"c":[{"v":8.0,"f":"8"},{"v":"B"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"}
,{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":"7:00"},{"v":"17:00"},{"v":8.0,"f":"8"},{"v":31.0,"f":"31"},null,{"v":null}]},{"c":[null,{"v":"T?ng c?ng"},null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,nu
ll,null,null,null,null,null,null,null,null,null,null,null,null,null,{"v":93.0,"f":"93"},null,{"v":null}]}],"parsedNumHeaders":0}});
 
JSON chỉ có một tiêu chuẩn mà thế giới thống nhất chứ không phải Google khác đâu. JSON cho phép khai báo các đối tượng mà các thành phần của chúng lại có thể là loại đối tượng khác nhau nhau. Ví dụ cấu trúc mẹ là một array - TJSONArray, trong mỗi phần tử của array lại là một object - TJSONObject, trong object này khai báo các thuộc tính, và một trong các thuộc tính đó lại có kiểu array - TJSONArray,.... Đây là một kiểu khai báo dạng Tree. Vậy nên mục tiêu hàm lấy cái gì thì phải lấy thành phần đó, các thành phần con đưa nó về string nếu cần.
Kiểm tra cấu trúc JSON thuộc loại nào thì chỉ cần:
{Delphi code}
var jv: TJsonValue;
begin
jv := TJSONValue.ParseJSONValue(responseString);
if jv is TJSONArray then
//xử lý kiể array
else
if jv is TJSONObject then
//Xử lý kiểu object
else
....
end;
 
JSON chỉ có một tiêu chuẩn mà thế giới thống nhất chứ không phải Google khác đâu. JSON cho phép khai báo các đối tượng mà các thành phần của chúng lại có thể là loại đối tượng khác nhau nhau. Ví dụ cấu trúc mẹ là một array - TJSONArray, trong mỗi phần tử của array lại là một object - TJSONObject, trong object này khai báo các thuộc tính, và một trong các thuộc tính đó lại có kiểu array - TJSONArray,.... Đây là một kiểu khai báo dạng Tree. Vậy nên mục tiêu hàm lấy cái gì thì phải lấy thành phần đó, các thành phần con đưa nó về string nếu cần.
Kiểm tra cấu trúc JSON thuộc loại nào thì chỉ cần:
{Delphi code}
var jv: TJsonValue;
begin
jv := TJSONValue.ParseJSONValue(responseString);
if jv is TJSONArray then
//xử lý kiể array
else
if jv is TJSONObject then
//Xử lý kiểu object
else
....
end;
hiểu đơn giản thì nó là vậy không lẻ tôi lại không biết điều đó

đi vào thực tế viết hàm trả về TypeName của một DataJSON bất kỳ truyền vào hàm xong kiểm trả xem nó là TJSONArray hay TJSONObject và các loại DataJSON khác nữa thì là chuyện khó lên rồi

Như bài số 22 thì 2 mục bạn nêu Tôi đã làm xong rồi nhưng không đơn giản là if jv is TJSONArray then ... thế là xong


1726124445952.png

Mở rộng trong Hàm ra kiểm tra mọi loại DataJSON truyền vào hàm thì mới là vấn đề tôi đang dò ... còn 2 mục cơ bản trên xong rồi
 
hiểu đơn giản thì nó là vậy không lẻ tôi lại không biết điều đó

đi vào thực tế viết hàm trả về TypeName của một DataJSON bất kỳ truyền vào hàm xong kiểm trả xem nó là TJSONArray hay TJSONObject và các loại DataJSON khác nữa thì là chuyện khó lên rồi

Như bài số 22 thì 2 mục bạn nêu Tôi đã làm xong rồi nhưng không đơn giản là if jv is TJSONArray then ... thế là xong


View attachment 303914

Mở rộng trong Hàm ra kiểm tra mọi loại DataJSON truyền vào hàm thì mới là vấn đề tôi đang dò ... còn 2 mục cơ bản trên xong rồi

Nếu bạn hiểu đúng thì cố làm đúng là ra nhé.
 
Rảnh tôi thử hàm mô tả bài số 6 Web khác xem sao thấy sử dụng tốt.. khi kết hợp với các thự viện khác có sẳn của Windows
Lấy tỷ giá VCB


Mã:
Rem https://portal.vietcombank.com.vn/UserControls/TVPortal.TyGia/pListTyGia.aspx?txttungay=25/02/2021&BacrhID=1&isEn=False
Sub TestSendHttpRequest3_vietcombank()
    Dim response As String
    Dim URL As String
    Dim Method As String
    Dim ContentType As String
    Dim Accept As String
    Dim UserAgent As String
    Dim DataToSend As String
    Dim html As New HTMLDocument ' Thay d?i cách khai báo
    Dim tbl As Object
    Dim row As Object
    Dim cell As Object
    Dim i As Integer, j As Integer

    ' Khai báo thông tin c?n thi?t
    URL = "https://portal.vietcombank.com.vn/UserControls/TVPortal.TyGia/pListTyGia.aspx?txttungay=25/02/2021&BacrhID=1&isEn=False"
    Method = "GET"
    ContentType = "application/json"
    Accept = "application/json"
    UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0"
    DataToSend = ""  ' D? li?u d? g?i, d? tr?ng cho phuong th?c GET

    ' G?i hàm SendHttpRequest
    Rem response = SendHttpRequest(Method, URL, ContentType, Accept, UserAgent, DataToSend)
    response = SendHttpRequest(Method, URL, "", "", "", DataToSend)

    ' Gán n?i dung ph?n h?i vào d?i tu?ng HTMLDocument
    html.body.innerHTML = response

    ' Tìm b?ng d? li?u d?a trên id c?a b?ng
    Set tbl = html.getElementById("ctl00_Content_ExrateView")
    
    Cells.ClearContents

    ' N?u tìm th?y b?ng thì x? lý
    If Not tbl Is Nothing Then
        ' Thêm tiêu d? b?ng t? th? <th>
        i = 1
        j = 1
        For Each cell In tbl.getElementsByTagName("th")
            Cells(i, j).Value = cell.innerText
            j = j + 1
        Next cell

        ' Duy?t qua các hàng và ô c?a b?ng và dua d? li?u vào Excel
        i = 2 ' B?t d?u t? dòng 2 vì dòng 1 ch?a tiêu d?
        For Each row In tbl.getElementsByTagName("tr")
            j = 1
            For Each cell In row.getElementsByTagName("td")
                Cells(i, j).Value = cell.innerText
                j = j + 1
            Next cell
            i = i + 1
        Next row
    Else
        MsgBox "Không tìm th?y b?ng d? li?u."
    End If
End Sub

Trên Delphi thư viện Indy quá tuyệt vời chạy đa nền tảng không lệ thuộc tây lông nào cả :p
 
Mấy trang khác thì không nói chứ trang tỷ giá Vietcombank hiện tại thì quá đơn giản, có sẳn API mà xài thôi. :D

JavaScript:
Option Explicit

Sub getVCBExchangeRate()
    Dim js As Object, dicData As Object
    Dim url As String, sDate As String, res As String, k As Long
  
    Application.ScreenUpdating = False
    
    '"https://www.vietcombank.com.vn/api/exchangerates?date=2024-09-12"
    url = "https://www.vietcombank.com.vn/api/exchangerates?date="
    sDate = Format(Sheets("VCB").Range("A1").value, "yyyy-mm-dd")
    url = url & sDate
    res = httpGet(url)

    Set js = JsonConverter.ParseJSON(res)
    Set dicData = js("Data")
    
    ReDim arr(dicData.Count, 4)
    For k = 1 To dicData.Count
        arr(k - 1, 0) = dicData(k)("currencyCode")
        arr(k - 1, 1) = dicData(k)("currencyName")
        arr(k - 1, 2) = dicData(k)("cash")
        arr(k - 1, 3) = dicData(k)("transfer")
        arr(k - 1, 4) = dicData(k)("sell")
    Next
    Sheets("VCB").Range("B3").value = js("Date")
    Sheets("VCB").Range("A5").Resize(UBound(arr, 1) + 1, UBound(arr, 2) + 1).value = arr
    
End Sub

Screen Shot 2024-09-13 at 13.13.11.png
 
Mấy trang khác thì không nói chứ trang tỷ giá Vietcombank hiện tại thì quá đơn giản, có sẳn API mà xài thôi. :D

JavaScript:
Option Explicit

Sub getVCBExchangeRate()
    Dim js As Object, dicData As Object
    Dim url As String, sDate As String, res As String, k As Long
 
    Application.ScreenUpdating = False
 
    '"https://www.vietcombank.com.vn/api/exchangerates?date=2024-09-12"
    url = "https://www.vietcombank.com.vn/api/exchangerates?date="
    sDate = Format(Sheets("VCB").Range("A1").value, "yyyy-mm-dd")
    url = url & sDate
    res = httpGet(url)

    Set js = JsonConverter.ParseJSON(res)
    Set dicData = js("Data")
 
    ReDim arr(dicData.Count, 4)
    For k = 1 To dicData.Count
        arr(k - 1, 0) = dicData(k)("currencyCode")
        arr(k - 1, 1) = dicData(k)("currencyName")
        arr(k - 1, 2) = dicData(k)("cash")
        arr(k - 1, 3) = dicData(k)("transfer")
        arr(k - 1, 4) = dicData(k)("sell")
    Next
    Sheets("VCB").Range("B3").value = js("Date")
    Sheets("VCB").Range("A5").Resize(UBound(arr, 1) + 1, UBound(arr, 2) + 1).value = arr
 
End Sub

View attachment 303941
Link trên nó sử dụng JSON Data Object khi Bạn sử dụng Module trên VBA nó hổ trợ 2 loại JSON mà tôi nói bài 22 và 24 xem trong Module đó khoãng trên 1000 dòng code

Tôi mới viết xong hàm đa luồng song song xử lý 2 loại JSON đó trong Delphi khõang 70 dòng code chi đó mà chạy nhanh hơn

1726212745918.png
Hình trên xử lý JSON 12 luồng và duyệt JSON gán lên Sheet 12 luồng nếu dữ liệu nhiều bay vèo vèo còn ít thì nó như nhau cả có sai tí ti không đáng tính
 
Copy Code sau vào File bài Số 1 xong gõ hàm trên Sheet theo các tham số xong Enter cái Cộp ... dữ liệu Google Sheet nó bay về Excel
Mã:
Declare PtrSafe Function GoogleSheetAsRecordset Lib "GoogleSheets64.dll" _
            (ByRef SheetId As Variant, _
             ByRef APIKey As Variant, _
             ByRef SheetName As Variant) As Variant
           
Function GetValue(value As Variant) As Variant
    If IsNull(value) Then
        GetValue = ""
    ElseIf IsNumeric(value) Then
        If Len(value) > 12 Then
            GetValue = Format(value, "#,##0.00")
        Else
            GetValue = Format(value, "#,##0")
        End If
    ElseIf IsDate(value) Then
        GetValue = Format(value, "dd/mm/yyyy")
    ElseIf Len(value) = 10 And IsNumeric(value) Then
        GetValue = Format(value, "####-###-###")
    Else
        GetValue = value
    End If
End Function

Function LoadDataFromGoogleSheets(SheetId As String, APIKey As String, SheetName As String) As Variant
    Dim Data As Variant
    Dim Res() As Variant
    Dim i As Long, j As Long
    Dim Rows As Long, Cols As Long
    Dim rs As Object

    ' Get the recordset from Google Sheets
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, SheetName)

    If Not rs Is Nothing Then
        If Not rs.EOF Then
            ' Retrieve data
            Data = rs.GetRows() ' Get all data from recordset

            ' Calculate Rows and Columns
            Rows = UBound(Data, 2) + 1
            Cols = UBound(Data, 1) + 1

            ' Resize the result array
            ReDim Res(1 To Rows, 1 To Cols)

            ' Fill the result array with data
            For j = 1 To Rows
                For i = 1 To Cols
                    Res(j, i) = GetValue(Data(i - 1, j - 1)) ' Get data value
                Next i
            Next j

            ' Assign the result to the output variable
            LoadDataFromGoogleSheets = Res
        Else
            LoadDataFromGoogleSheets = Array() ' Return empty array if no data
        End If
    Else
        LoadDataFromGoogleSheets = Array() ' Return empty array if rs is Nothing
    End If
End Function

Cơ bản là vậy còn tuỳ chỉnh phụ thuộc ai đó nếu ko thích thì viết kiểu khác = Tuỳ

Hàm GoogleSheetAsRecordset trả về các phương thức thuộc tính của ADODB trên Excel ...
sử dụng lấy dữ liệu còn không chèn dữ liệu


sau Khi gõ cái cộp sẻ ra như hình sau .. APIKey đã xoá ... ai đó tự làm lấy

1726307727482.png
 
Copy Code sau vào File bài Số 1 xong gõ hàm trên Sheet theo các tham số xong Enter cái Cộp ... dữ liệu Google Sheet nó bay về Excel
Mã:
Declare PtrSafe Function GoogleSheetAsRecordset Lib "GoogleSheets64.dll" _
            (ByRef SheetId As Variant, _
             ByRef APIKey As Variant, _
             ByRef SheetName As Variant) As Variant
          
Function GetValue(value As Variant) As Variant
    If IsNull(value) Then
        GetValue = ""
    ElseIf IsNumeric(value) Then
        If Len(value) > 12 Then
            GetValue = Format(value, "#,##0.00")
        Else
            GetValue = Format(value, "#,##0")
        End If
    ElseIf IsDate(value) Then
        GetValue = Format(value, "dd/mm/yyyy")
    ElseIf Len(value) = 10 And IsNumeric(value) Then
        GetValue = Format(value, "####-###-###")
    Else
        GetValue = value
    End If
End Function

Function LoadDataFromGoogleSheets(SheetId As String, APIKey As String, SheetName As String) As Variant
    Dim Data As Variant
    Dim Res() As Variant
    Dim i As Long, j As Long
    Dim Rows As Long, Cols As Long
    Dim rs As Object

    ' Get the recordset from Google Sheets
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, SheetName)

    If Not rs Is Nothing Then
        If Not rs.EOF Then
            ' Retrieve data
            Data = rs.GetRows() ' Get all data from recordset

            ' Calculate Rows and Columns
            Rows = UBound(Data, 2) + 1
            Cols = UBound(Data, 1) + 1

            ' Resize the result array
            ReDim Res(1 To Rows, 1 To Cols)

            ' Fill the result array with data
            For j = 1 To Rows
                For i = 1 To Cols
                    Res(j, i) = GetValue(Data(i - 1, j - 1)) ' Get data value
                Next i
            Next j

            ' Assign the result to the output variable
            LoadDataFromGoogleSheets = Res
        Else
            LoadDataFromGoogleSheets = Array() ' Return empty array if no data
        End If
    Else
        LoadDataFromGoogleSheets = Array() ' Return empty array if rs is Nothing
    End If
End Function

Cơ bản là vậy còn tuỳ chỉnh phụ thuộc ai đó nếu ko thích thì viết kiểu khác = Tuỳ

Hàm GoogleSheetAsRecordset trả về các phương thức thuộc tính của ADODB trên Excel ...
sử dụng lấy dữ liệu còn không chèn dữ liệu


sau Khi gõ cái cộp sẻ ra như hình sau .. APIKey đã xoá ... ai đó tự làm lấy

View attachment 303970
Cái API key của Google Sheets API chỉ có tác dụng với những endpoint dùng phương thức (method) GET, chứ để mà chỉnh sửa được spreadsheet thì cần phải dùng đến xác thực OAuth 2.0 để Google biết ai đang tương tác với spreadsheet, chưa kể cần phải chia sẻ spreadsheet công khai nữa.
Mà nhìn chung, nếu dùng để truy xuất dữ liệu từ spreadsheet thì mình thấy cũng ok.
1726498975514.png
 
Cái API key của Google Sheets API chỉ có tác dụng với những endpoint dùng phương thức (method) GET, chứ để mà chỉnh sửa được spreadsheet thì cần phải dùng đến xác thực OAuth 2.0 để Google biết ai đang tương tác với spreadsheet, chưa kể cần phải chia sẻ spreadsheet công khai nữa.
Mà nhìn chung, nếu dùng để truy xuất dữ liệu từ spreadsheet thì mình thấy cũng ok.
View attachment 304002
Việc lấy dữ liệu Từ Google Sheet về Excel nó quá đơn giản luôn.. có APIkey không không có lấy ok sạch .. miễn là chia Sẻ Google Sheet

Việc lấy theo APIkey nó an toàn hơn thôi vvv...

Như bài số 29 mục đích tôi viết hàm trả về ADODB Recordset với các phương thức, thuộc tính của ADODB cho người dùng tuỳ biến nhiều hơn trên VBA

Với lại chưa thấy ai viết hay Tôi chưa biết ... Nên viết theo cách khác chút không trùng lặp của ai đó và không cần thiết mua Component của bên thứ 3


1/ Hàm trả về khi ta sử dụng
Mã:
 ' Call the Delphi function
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, SheetName)
   
    ' S? d?ng rs nhu Recordset
    Do While Not rs.EOF
        Debug.Print rs.Fields(1).value ''1 là cot 2
        rs.MoveNext
    Loop

2/ Hàm trả về khi ta sử dụng
Mã:
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, SheetName)
   
    Cells.ClearContents
    ' Paste the data into the worksheet
    Set ws = ThisWorkbook.Sheets("Sheet1")  ' Change to your target sheet
    ws.Range("A1").CopyFromRecordset rs
   
    ' Cleanup
    rs.Close
    Set rs = Nothing

3/ Hàm trả về khi ta sử dụng

Mã:
' Call the Delphi function
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, SheetName)
   
    Cells.ClearContents
    ' Paste the data into the worksheet
    Set ws = ThisWorkbook.Sheets("Sheet1")  ' Change to your target sheet

    ' Get all data from the recordset
    Data = rs.GetRows()

    ' Paste data, skipping the first row (header)
    For i = LBound(Data, 2) + 1 To UBound(Data, 2) ' Start from the second row
        ws.Range("A" & (i + 1)).Resize(1, UBound(Data, 1) + 1).value = Application.Index(Data, 0, i)
    Next i

    ' Cleanup
    rs.Close
    Set rs = Nothing

đại ý là hàm trả về các phương thức, thuộc tính của ADODB khi ta khai báo ADODB trên VBA trả về Recordset


còn cách bạn viết trực tiếp trên Google Sheet thì tôi lại không dùng mà sẻ áp dụng cách liên kết máy chủ Web Server của tôi ...dự án đang dò
 
Không cần Google Sheets API, không cần OAuth, không cần chia sẻ tập tin Google Sheets
VẪN
đồng bộ
thời gian thực ngon lành.
Chứ chỉ đẩy dữ liệu một chiều thì chưa ăn thua gì.
 
Không cần Google Sheets API, không cần OAuth, không cần chia sẻ tập tin Google Sheets
VẪN
đồng bộ
thời gian thực ngon lành.
Chứ chỉ đẩy dữ liệu một chiều thì chưa ăn thua gì.
Thong thả ta chơi cho vui như chơi game thôi ... biết hết và nhanh quá mai mốt hết trò chơi :p
 
Việc lấy dữ liệu Từ Google Sheet về Excel nó quá đơn giản luôn.. có APIkey không không có lấy ok sạch .. miễn là chia Sẻ Google Sheet

Việc lấy theo APIkey nó an toàn hơn thôi vvv...

Như bài số 29 mục đích tôi viết hàm trả về ADODB Recordset với các phương thức, thuộc tính của ADODB cho người dùng tuỳ biến nhiều hơn trên VBA

Với lại chưa thấy ai viết hay Tôi chưa biết ... Nên viết theo cách khác chút không trùng lặp của ai đó và không cần thiết mua Component của bên thứ 3


1/ Hàm trả về khi ta sử dụng
Mã:
 ' Call the Delphi function
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, SheetName)
 
    ' S? d?ng rs nhu Recordset
    Do While Not rs.EOF
        Debug.Print rs.Fields(1).value ''1 là cot 2
        rs.MoveNext
    Loop

2/ Hàm trả về khi ta sử dụng
Mã:
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, SheetName)
 
    Cells.ClearContents
    ' Paste the data into the worksheet
    Set ws = ThisWorkbook.Sheets("Sheet1")  ' Change to your target sheet
    ws.Range("A1").CopyFromRecordset rs
 
    ' Cleanup
    rs.Close
    Set rs = Nothing

3/ Hàm trả về khi ta sử dụng

Mã:
' Call the Delphi function
    Set rs = GoogleSheetAsRecordset(SheetId, APIKey, SheetName)
 
    Cells.ClearContents
    ' Paste the data into the worksheet
    Set ws = ThisWorkbook.Sheets("Sheet1")  ' Change to your target sheet

    ' Get all data from the recordset
    Data = rs.GetRows()

    ' Paste data, skipping the first row (header)
    For i = LBound(Data, 2) + 1 To UBound(Data, 2) ' Start from the second row
        ws.Range("A" & (i + 1)).Resize(1, UBound(Data, 1) + 1).value = Application.Index(Data, 0, i)
    Next i

    ' Cleanup
    rs.Close
    Set rs = Nothing

đại ý là hàm trả về các phương thức, thuộc tính của ADODB khi ta khai báo ADODB trên VBA trả về Recordset


còn cách bạn viết trực tiếp trên Google Sheet thì tôi lại không dùng mà sẻ áp dụng cách liên kết máy chủ Web Server của tôi ...dự án đang dò

Hình như bác đã tư duy sai vấn đề. Bác đang dùng link google đã exoort ra csv hay json gì đó đây chỉ còn là link ldownload thông thường như bao thứ khác trên mạng. Đó chính là vì sao link bác yêu cầu phải share quyền cho everyone và cái apikey kia chỉ là hình thức.

Vậy là trong dll của bác không phải làm việc với google sheets. Khi nào bác nhận được dữ liệu từ một spreadsheetId với địa chỉ vùng dữ liệu bất ký, link được share với quyền giới hạn tuỳ vào user thì mới gọi là là lấy dữ liệu từ google sheets.
 
Hình như bác đã tư duy sai vấn đề. Bác đang dùng link google đã exoort ra csv hay json gì đó đây chỉ còn là link ldownload thông thường như bao thứ khác trên mạng. Đó chính là vì sao link bác yêu cầu phải share quyền cho everyone và cái apikey kia chỉ là hình thức.

Vậy là trong dll của bác không phải làm việc với google sheets. Khi nào bác nhận được dữ liệu từ một spreadsheetId với địa chỉ vùng dữ liệu bất ký, link được share với quyền giới hạn tuỳ vào user thì mới gọi là là lấy dữ liệu từ google sheets.
có thể nhầm lẫn gì chăng mà tôi chưa hình dung ra

Nhưng Tôi đang lấy dữ liệu Google Sheet về Excel Với APIkey SheetID là chính xác tuyệt đối không sai được

còn keo nó là chi Tôi cũng không quan tâm lắm :p .. còn APIkey tôi lấy theo mục khoanh màu đỏ vì thấy nó keo là APIkey ... còn tây keo là chi kệ nó

Tôi keo theo cách của tôi và biết sử dụng là OK

1726541982988.png

á ........... đoán thôi .. nghi ngờ tôi tải File Google Sheet về máy tính xong sử dụng ADODB mới trả về phương thức , thuộc tính Rs

nếu vậy thì lại nhầm lẫn gì chăng --=0_)()(-
 
Lần chỉnh sửa cuối:
Copy Code sau vào File Bài số 1 lấy SheetId khi truyền Tham số URL của Google Sheet vào hàm + APIKeys bài trên là ok xong

Mã:
Declare PtrSafe Function ExtractSheetIdFromURL Lib "GoogleSheets64.dll" _
            (ByRef URL As Variant) As Variant

          
Sub Test_ExtractSheetIdFromURL()
    Dim URL As String, SheetId As String
    URL = "https://docs.google.com/spreadsheets/d/1-0nB16Fefr8cTbEV7Gj5VjcegL5GcfMIc4RP5C9Sn4w/edit?gid=553666518#gid=553666518"
    SheetId = ExtractSheetIdFromURL(URL)
    Debug.Print SheetId
End Sub
 
Lấy dữ liệu JSON của Google Sheet thì chỉ càn dùng HTTP Get thông thường với cái Sheet ID là được rồi mà ta…Lấy sheet nào thì khai báo sheet đó thôi.
Cách này chỉ là lấy dữ liệu chứ không Insert, Update, Delete gì được nhé. Muốn chỉnh sửa thì dùng Google Sheet API (đang test).
 
Lần chỉnh sửa cuối:
Lấy dữ liệu JSON của Google Sheet thì chỉ càn dùng HTTP Get thông thường với cái Sheet ID là được rồi mà ta…Lấy sheet nào thì khai báo sheet đó thôi.
Cách này chỉ là lấy dữ liệu chứ không Insert, Update, Delete gì được nhé. Muốn chỉnh sửa thì dùng Google Sheet API (đang test).

Một điều em quan tâm là spreadsheet chia sẻ không phải everyone, và ấy địa chỉ như sheet “ban hàng” vùng A10:H1000 thì thế nào?
 
Nếu sử dụng JSON thì liên quan cái hàm chuyển JSON trên VBA của tây có khoãng trên 1000 dòng code ... dẹp nó đi cho gọn

chuyển qua CSV chỉ vài dòng code

Mượn link của Hướng GPE thử code thay thế hàm SendHttpRequest ... là hàm chi của ai đó tuỳ thích

Mã:
Sub GetDataFromGoogleSheet_CSV2()
    Dim csvData As String
    Dim lines As Variant
    Dim line As Variant
    Dim values As Variant
    Dim cleanedValue As String
    Dim I As Long
    Dim J As Long

    ' Gi? s? csvData ch?a d? li?u CSV t? hàm SendHttpRequest
    csvData = SendHttpRequest("GET", "https://docs.google.com/spreadsheets/d/1-0nB16Fefr8cTbEV7Gj5VjcegL5GcfMIc4RP5C9Sn4w/gviz/tq?tqx=out:csv", "text/csv", "text/csv", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", Null)
    Debug.Print csvData

    ' Tách d? li?u thành t?ng dòng
    lines = Split(csvData, vbLf)
   
    Cells.ClearContents

    ' Duy?t t?ng dòng d? l?y giá tr?
    For I = LBound(lines) To UBound(lines)
        ' Tách t?ng dòng thành các giá tr?
        values = Split(lines(I), ",")

        ' Luu giá tr? vào b?ng tính (thay Sheet1 b?ng tên c?a trang tính c?a b?n)
        For J = LBound(values) To UBound(values)
            ' Lo?i b? các d?u "" th?a
            cleanedValue = Replace(values(J), """", "")
            Sheets("Sheet2").Cells(I + 1, J + 1).Value = Trim(cleanedValue)
        Next J
    Next I

    'MsgBox "D? li?u dã du?c luu vào b?ng tính!"
End Sub
 
Nếu dùng API của Google thì có hết từ lâu lắm rồi mà.

Còn dùng OAuth thì phải hết sức cẩn thận với đám mây, cái này phải có ràng buộc trách nhiệm nghiêm ngặt, và không ai làm như ai đó (có một nhược điểm chí mạng và buồn cười). Lần này thì mặc kệ nó. =]]]

Mà hài nhất là khúc đẩy dữ liệu phải chọn Range trước. :D
 
Nếu dùng API của Google thì có hết từ lâu lắm rồi mà.

Còn dùng OAuth thì phải hết sức cẩn thận với đám mây, cái này phải có ràng buộc trách nhiệm nghiêm ngặt, và không ai làm như ai đó (có một nhược điểm chí mạng và buồn cười). Lần này thì mặc kệ nó. =]]]

Mà hài nhất là khúc đẩy dữ liệu phải chọn Range trước. :D
Rất rõ ràng mạch lạc là

nếu sử dụng các dịch vụ của Google thì User của mình chỉ cần mở trình duyệt lên xong đăng nhập với các thông tin xx

xong đọc và ghi dữ liệu ... còn uỷ quyền làm chi nữa nguy cơ tào lao cao ai mà biết ... còn ko biết hàm đọc và ghi thì mới cần bên thứ 3

Nếu biết rồi thì cũng dep cho sạch
 
Một điều em quan tâm là spreadsheet chia sẻ không phải everyone, và ấy địa chỉ như sheet “ban hàng” vùng A10:H1000 thì thế nào?
Hehe… cách lấy thông thường của tôi chỉ lấy từ cái sheet phải chuẩn dòng đầu tiêu đề, không có thiết kế lung tung thì mới lấy được. Còn không chia sẻ, có range nằm vị trí bất kỳ là bó tay…hehe. Khó quá nên không ngâm cứu vụ này.
 
Nếu dùng API của Google thì có hết từ lâu lắm rồi mà.

Còn dùng OAuth thì phải hết sức cẩn thận với đám mây, cái này phải có ràng buộc trách nhiệm nghiêm ngặt, và không ai làm như ai đó (có một nhược điểm chí mạng và buồn cười). Lần này thì mặc kệ nó. =]]]

Mà hài nhất là khúc đẩy dữ liệu phải chọn Range trước. :D

Phải chọn vùng mới đẩy được dữ liệu á? Cờ líp đâu bác. Nếu không dùng OAuth mà truy cập được vào dữ liệu private trên Google sheet thì hoá ra hack được google à. Hay lại có chuyện buồn cười rũ rượi nữa đây?
Bài đã được tự động gộp:

Rất rõ ràng mạch lạc là

nếu sử dụng các dịch vụ của Google thì User của mình chỉ cần mở trình duyệt lên xong đăng nhập với các thông tin xx

xong đọc và ghi dữ liệu ... còn uỷ quyền làm chi nữa nguy cơ tào lao cao ai mà biết ... còn ko biết hàm đọc và ghi thì mới cần bên thứ 3

Nếu biết rồi thì cũng dep cho sạch

Bác càng phán càng thấy tào lao quá. Hãy làm với đư liệu private với link của user nào đó không phải của mình đi.
 
Phải chọn vùng mới đẩy được dữ liệu á? Cờ líp đâu bác. Nếu không dùng OAuth mà truy cập được vào dữ liệu private trên Google sheet thì hoá ra hack được google à. Hay lại có chuyện buồn cười rũ rượi nữa đây?
chủ đề này bàn chuyện chuyện Môn code két he ... mọi cái tôn trọng nhau chút chơi cho vui vẻ

Còn muốn acvbs vui lòng tạo thớt mới mà bàn .... trên này ai sao Tôi biết Sạch có điều nói hay không nói

... và muốn trao đổi trên tinh thần code két thì trao .. còn không thì thôi ... bớt bớt lại chút đi cho lành
 
Nếu sử dụng JSON thì liên quan cái hàm chuyển JSON trên VBA của tây có khoãng trên 1000 dòng code ... dẹp nó đi cho gọn

chuyển qua CSV chỉ vài dòng code

Mượn link của Hướng GPE thử code thay thế hàm SendHttpRequest ... là hàm chi của ai đó tuỳ thích

Mã:
Sub GetDataFromGoogleSheet_CSV2()
    Dim csvData As String
    Dim lines As Variant
    Dim line As Variant
    Dim values As Variant
    Dim cleanedValue As String
    Dim I As Long
    Dim J As Long

    ' Gi? s? csvData ch?a d? li?u CSV t? hàm SendHttpRequest
    csvData = SendHttpRequest("GET", "https://docs.google.com/spreadsheets/d/1-0nB16Fefr8cTbEV7Gj5VjcegL5GcfMIc4RP5C9Sn4w/gviz/tq?tqx=out:csv", "text/csv", "text/csv", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0", Null)
    Debug.Print csvData

    ' Tách d? li?u thành t?ng dòng
    lines = Split(csvData, vbLf)
  
    Cells.ClearContents

    ' Duy?t t?ng dòng d? l?y giá tr?
    For I = LBound(lines) To UBound(lines)
        ' Tách t?ng dòng thành các giá tr?
        values = Split(lines(I), ",")

        ' Luu giá tr? vào b?ng tính (thay Sheet1 b?ng tên c?a trang tính c?a b?n)
        For J = LBound(values) To UBound(values)
            ' Lo?i b? các d?u "" th?a
            cleanedValue = Replace(values(J), """", "")
            Sheets("Sheet2").Cells(I + 1, J + 1).Value = Trim(cleanedValue)
        Next J
    Next I

    'MsgBox "D? li?u dã du?c luu vào b?ng tính!"
End Sub
Không cần API Key nữa à?
 
chủ đề này bàn chuyện chuyện Môn code két he ... mọi cái tôn trọng nhau chút chơi cho vui vẻ

Còn muốn acvbs vui lòng tạo thớt mới mà bàn .... trên này ai sao Tôi biết Sạch có điều nói hay không nói


... và muốn trao đổi trên tinh thần code két thì trao .. còn không thì thôi ... bớt bớt lại chút đi cho lành

Ah, mình chủ tò mò cái mà mình chưa làm được như mọi người nói và đang muốn được khai sáng để học hỏi thêm thôi. Chờ chút mình gửi link nhờ bác và ai đó làm thử coi sao nhé.
 
Sao bạn viết bài #20 với code bài #26 mâu thuẫn nhau vậy??? :p
bởi vì duyệt tìm kiếm quá nhiều cung đoạn và chi phí mất thời gian xử lý dữ liệu nên tôi tạm keo vậy
có lẻ không quan trọng lắm nên không quan tâm các bài đó đó
 
bởi vì duyệt tìm kiếm quá nhiều cung đoạn và chi phí mất thời gian xử lý dữ liệu nên tôi tạm keo vậy
có lẻ không quan trọng lắm nên không quan tâm các bài đó đó
Nếu vậy thì hoá ra cái thư viện Beautifull Soup (parse HTML và XML) hay ho của thằng Python được viết ra chỉ để xử lý RÁC thôi. Python dùng nó để cào dữ liệu web cũng chỉ là RÁC, cùng đường thôi…haha…giờ tôi mới biết luôn.
 
rảnh chơi game trí tuệ và có ích thôi mà ... Tôi mới nâng cấp hàm GoogleSheetAsRecordset thành hàm mở rộng là GoogleSheetAsRecordsetEx

áp dụng cho các máy đời cao CPU + RAM nhiều chạy đa luồng song song thì 1000 dòng Google Sheet nó cũng nhẹ lắm

hên thế nhờ thớt bên kia mà hiểu nguyên lý đa luồng song song --=0

Mã:
Declare PtrSafe Function GoogleSheetAsRecordsetEx Lib "GoogleSheets64.dll" _
            (ByRef SheetId As Variant, _
             ByRef APIKey As Variant, _
             ByRef SheetName As Variant, _
             Optional ByVal ThreadCount As LongPtr = 0) As Variant

ThreadCount mặc định là 0 ... nếu máy tốt thì cho nó vào 20 là 20 luồng vậy ... xem ra hàm này không đụng hàng của ai đó
vậy là có hai hàm cũng song song cùng một mục đích lấy dữ liệu Google Sheet trả về ADODB Recordset với các phương thức, thực tính của nó sử dụng thuần ADODB trên Windows
 
Mới thử 999 dòng bỏ lại 1 dòng mà sao nó nhẹ lắm hay máy của tôi 20 luồng nên nó nhanh vậy

1726624589992.png
 
Mới thử 999 dòng bỏ lại 1 dòng mà sao nó nhẹ lắm hay máy của tôi 20 luồng nên nó nhanh vậy

View attachment 304020
Không biết bạn có thể hỗ trợ thêm xác thực OAuth không nhỉ? Những spreadsheet không chia sẻ "everyone" thì bắt buộc phải xác thực OAuth, ví dụ như mình viết phương thức thêm sheet mới bằng Office Scripts, mà Office Scripts hiện chưa hỗ trợ OAuth, không hỗ trợ cookie, LocalStorage hay SessionStorage để lưu trữ thông tin về access token và refresh token nên mình phải chèn trực tiếp vào code (hardcoded) không an toàn.
1726671724757.png
 
Không biết bạn có thể hỗ trợ thêm xác thực OAuth không nhỉ? Những spreadsheet không chia sẻ "everyone" thì bắt buộc phải xác thực OAuth, ví dụ như mình viết phương thức thêm sheet mới bằng Office Scripts, mà Office Scripts hiện chưa hỗ trợ OAuth, không hỗ trợ cookie, LocalStorage hay SessionStorage để lưu trữ thông tin về access token và refresh token nên mình phải chèn trực tiếp vào code (hardcoded) không an toàn.
View attachment 304054
đang dò nó sẻ có giao diện này .. qua TWebBrowser nhưng tôi không thích .. dò tiếp

thong thả và thư giản vi hành Google xem tình hình sao xong tính

1726711891115.png
 
nếu thấy khó qua thì thử dùng Python xem .. tham khảo chơi


Nó hổ trợ cho thư viện dựng sẳn còn ta chỉ việc gắp

* Cài đặt các thư viện gspread, oauth2client, PyOpenSSL:

Delphi chưa hổ trợ oauth2client mà do người dùng tự dò hay viết ... nếu ko làm được thì Mua = xong
Còn tôi mua là cái gì đó hoang đường --=0
vì Tôi xem nó như trò chơi Game tiêu kiển cho vui
 
Lần chỉnh sửa cuối:
Không biết bạn có thể hỗ trợ thêm xác thực OAuth không nhỉ? Những spreadsheet không chia sẻ "everyone" thì bắt buộc phải xác thực OAuth, ví dụ như mình viết phương thức thêm sheet mới bằng Office Scripts, mà Office Scripts hiện chưa hỗ trợ OAuth, không hỗ trợ cookie, LocalStorage hay SessionStorage để lưu trữ thông tin về access token và refresh token nên mình phải chèn trực tiếp vào code (hardcoded) không an toàn.
View attachment 304054

Cái này người ta gọi là "Đăng nhập" và "Ủy quyền" (cách tôi dùng từ Tiếng Việt nôm na là vậy). Dùng OAuth2.0 là chuẩn chung của tất cả các nền tảng cloud trên thế giới để cho phép ứng dụng truy cập vào dữ liệu người dùng khi họ cấp phép. Một số nền tảng lập trình các hãng cloud hỗ trợ SDK cho người lập trình. Vớ Delphi thì người lập trình phải tự viết (bản thân hãng cloud không viết sẵn cho, việc này không có gì to tát cả). Tôi làm Delphi và đã làm việc này được giới thiệu tại đây.
 
Lần chỉnh sửa cuối:
đăng nhập và uỷ quyền thì con ních nó cũng biết là hai từ khác nhau hoàn toàn :{{

còn người lớn mà nhầm lẫn hai từ đó là một thì Tôi chịu
 
đăng nhập và uỷ quyền thì con ních nó cũng biết là hai từ khác nhau hoàn toàn :{{

còn người lớn mà nhầm lẫn hai từ đó là một thì Tôi chịu

"Đăng nhập" VÀ "Ủy quyền" nó không phải đồng nghĩa mà là một quy trình xử lý của OAuth. Khổ đọc hiểu lại có vấn đề à bạn.
Vấn đề bây giờ bạn đã sáng ra chưa về vấn đề làm việc với cloud ? Giờ lại đi mày mò cái OAuth mà chính bạn đã đã chê bai à. Lẽ ra trình độ này mình phải nên biết lắng nghe học hỏi thì game mới hay hơn chứ.
Bài đã được tự động gộp:

nếu thấy khó qua thì thử dùng Python xem .. tham khảo chơi
...
Nó hổ trợ cho thư viện dựng sẳn còn ta chỉ việc gắp

* Cài đặt các thư viện gspread, oauth2client, PyOpenSSL:

Delphi chưa hổ trợ oauth2client mà do người dùng tự dò hay viết ... nếu ko làm được thì Mua = xong
Còn tôi mua là cái gì đó hoang đường --=0
vì Tôi xem nó như trò chơi Game tiêu kiển cho vui

Bạn có biết oauth2client là gì không? Nó là thư viện để làm quy trình OAuth đó, chính là cái gọi là "Đăng nhập" và "Ủy quyền" đấy. Dùng ChatGPT chưa đúng cách hay sao mà không thấy tiến bộ.
 
đang dò nó sẻ có giao diện này .. qua TWebBrowser nhưng tôi không thích .. dò tiếp

thong thả và thư giản vi hành Google xem tình hình sao xong tính
1726711891115-png.304058

View attachment 304058

Bạn đang tìm cách học làm OAuth phải không? Nó chính là Học để làm "Đăng nhập" và "Ủy quyền" đấy . Bạn trí nhớ kém thật, thế này viết game thì nhân vật chính cứ đâm xuống ruộng mất thôi. Chỉ vừa mấy hôm trước còm tranh luận về vấn đề này lắm. Tôi trích lại đây để nhớ nhé. Từ nay phải nói ít hơn, chiu khó đọc hiểu, học kỹ để game được hay và logic hơn.

"Game" từ #727 mời các bạn xem tại đây

km1.png

km2.png


km3.png


km3.png


km4.png
 
Bạn đang tìm cách học làm OAuth phải không? Nó chính là Học để làm "Đăng nhập" và "Ủy quyền" đấy . Bạn trí nhớ kém thật, thế này viết game thì nhân vật chính cứ đâm xuống ruộng mất thôi. Chỉ vừa mấy hôm trước còm tranh luận về vấn đề này lắm. Tôi trích lại đây để nhớ nhé. Từ nay phải nói ít hơn, chiu khó đọc hiểu, học kỹ để game được hay và logic hơn.

"Game" từ #727 mời các bạn xem tại đây


View attachment 304075
Thớt nào ra thớt đó ... lịch sự chút đi

Đăng nhập và uỷ quyền là hai từ khác nhau con nich cũng biết không bàn cải nữa

còn đầu đất mới không hiểu thôi ............ Thôi nha

Vui lòng lập thớt mới qua đó bàn cải tiếp...
 
Lần chỉnh sửa cuối:
Nếu dùng API của Google thì có hết từ lâu lắm rồi mà.

Còn dùng OAuth thì phải hết sức cẩn thận với đám mây, cái này phải có ràng buộc trách nhiệm nghiêm ngặt, và không ai làm như ai đó (có một nhược điểm chí mạng và buồn cười). Lần này thì mặc kệ nó. =]]]

Mà hài nhất là khúc đẩy dữ liệu phải chọn Range trước. :D

Ah nay mới nhìn code Py trong bài này. Chọn vùng để upload kìa. ôi thế mà lại nói gì là chọn vùng cập nhật là buồn cười? Video thì lấp liếm , kiến thức vay mượl lại rất chi là mờ nhạt. Ôi be phèn ơi là be phèn.
 
Ah nay mới nhìn code Py trong bài này. Chọn vùng để upload kìa. ôi thế mà lại nói gì là chọn vùng cập nhật là buồn cười? Video thì lấp liếm , kiến thức vay mượl lại rất chi là mờ nhạt. Ôi be phèn ơi là be phèn.
đề nghị qua đó có gì chưa rõ thì yêu cầu làm rõ đi .... xem họ trả lời sao thì biết thôi
 
Change event, Target.address.
Chán con cò be bé, đọc 1.0 thì hiểu 1.0001 chứ.
 
đề nghị qua đó có gì chưa rõ thì yêu cầu làm rõ đi .... xem họ trả lời sao thì biết thôi

nhìn vào cái nội dung đăng và cờ líp kiểu lấp lửng lấp liếm vậy là biết chả có kiến thức và giá trị chia sẻ gì đâu. Chả thế mà từ năm 2019 chả ai thèm hỏi. Vì họ share vào đây rồi phán như chiên rau nên mình mới ngó xem thế nào. họ chia sẻ, phán phiếc kiểu “phông bạt” thôi.
 
Thôi hai bên bớt lại chút cho lành đi ... tôi thù dai một gốc trả 10 lời luôn ... nhưng cũng có chừng mực tới lằm danh đỏ là dừng

quay lại chủ đề này ghi dữ liệu xong rồi ... đơn giản thôi ... thong thả tôi xuất hàm API cho dùng ... @

befaint theo bài đó ok​


1726795177907.png
 
Lần chỉnh sửa cuối:
đang dò nó sẻ có giao diện này .. qua TWebBrowser nhưng tôi không thích .. dò tiếp

thong thả và thư giản vi hành Google xem tình hình sao xong tính

View attachment 304058
Mấy năm trước, Google bắt đầu chặn yêu cầu ủy quyền thông qua webview hoặc webview được nhúng vào WinForm, đại loại là thường trả về lỗi "This browser or app may not be secure". Mình có tra cứu trên GitHub và tìm thấy đoạn code mẫu C# .NET này của Google, nhìn chung logic như sau:
  1. Tạo uri yêu cầu ủy quyền.
  2. Tạo redirect uri (tức là uri chuyển hướng sau khi thực hiện xong ủy quyền) để theo dõi.
  3. Dùng lớp (class) HttpListener để theo dõi lưu lượng HTTP từ redirect uri trên.
  4. Tạo tiến trình (process) với uri yêu cầu ủy quyền để Windows mở bằng trình duyệt mặc định.
  5. Khi người dùng thực hiện ủy quyền xong (thành công hay thất bại) sẽ trả về redirect uri, HttpListener bắt được thông tin từ redirect uri và gửi thông báo đến trình duyệt rằng người dùng có thể đóng trình duyệt lại.
  6. Tiến hành phân tích thông tin nhận được, trao đổi access token nếu có authorization code, thông báo lỗi nếu có.
1726795066055.png
OAuthDesktopApp/OAuthDesktopApp/MainWindow.xaml.cs
Cái này mình không rõ Delphi có thư viện nào tương tự như vậy không, gửi bạn tham khảo thử.
 
có lẻ keo xác thực chỉ lần đầu đúng nghĩa hơn vì Tôi sử dụng File *.JSON nên nó quá đơn giản luôn

1/ Người dùng tạo APIKeys

2/ Tải File *.JSON

3/ chạy mã chỉ xác thực lần đầu tiên để lấy access token xong lưu vào đâu đó tuỳ thích

3/ các lần sau không cần nữa ... chờ ngày mail qua một ngày tôi thử đi thử lại xem sao xong tính tiếp

4/ hai hàm cơ bản khoãng 40 dòng code là xong ...

vài thông tin cơ bản sau thôi còn code trên Tools nào thì tuỳ chỉnh lại trên Tools đó là xong

1726797468006.png
 
có lẻ keo xác thực chỉ lần đầu đúng nghĩa hơn vì Tôi sử dụng File *.JSON nên nó quá đơn giản luôn

1/ Người dùng tạo APIKeys

2/ Tải File *.JSON

3/ chạy mã chỉ xác thực lần đầu tiên để lấy access token xong lưu vào đâu đó tuỳ thích

3/ các lần sau không cần nữa ... chờ ngày mail qua một ngày tôi thử đi thử lại xem sao xong tính tiếp

4/ hai hàm cơ bản khoãng 40 dòng code là xong ...

vài thông tin cơ bản sau thôi còn code trên Tools nào thì tuỳ chỉnh lại trên Tools đó là xong

View attachment 304091
Nếu dùng Service Account thì đơn giản hơn nhưng chỉ giới hạn trong phạm vi một tài khoản, muốn thay người dùng khác thì phải dùng JSON từ chính người dùng đó, cách này phù hợp với ứng dụng dạng daemon hơn.
 
Nếu dùng Service Account thì đơn giản hơn nhưng chỉ giới hạn trong phạm vi một tài khoản, muốn thay người dùng khác thì phải dùng JSON từ chính người dùng đó, cách này phù hợp với ứng dụng dạng daemon hơn.
1726798387851.png

Thêm mục tôi khoanh đó thành số 1000 xem sao ???!!!
 
1726798770780.png

Nếu có khó khăn gì bên ta luôn có Em ChatGPT tuyệt đỉnh rồi ... còn ta chỉ tư duy logis xong lắp ráp lại là xong
 
cách tạo một Service Accounts + API Keys vài thao tác chuột là xong rồi sử dụng hoài ...

quá đơn giản chỉ một ngày định hình khung xong ... thong thả và thư giản thử các kiểu một thời gian xem xét xong xuất một Hàm API bao quát nhật là xong

chuyển qua giai đoạn liên kết Google Sheets với máy chủ Web Server lấy dữ liệu từ PC lên Google Sheet ... này mới đang chơi

còn vài trò vặt kia chơi cho vui thôi
 
Lần chỉnh sửa cuối:
sau một ngày thử lại Token cũ OK và 3 máy = OK

quá đơn giản ... thử đi thử lại sau 1 tuần nếu ok tiến hành viết hàm bao quát nhất xong xuất API thì từ VBA cứ thế Call
 
Nó sang tới Google Drive rồi .. xong sẻ qua Gmail luôn cho đủ bộ .. đang dò list File Google Drive

cần quái gì uỷ quyền .. cho dù có cam kết gì đi nữa thì cũng làm tâm lý người dùng lo lắng

Không biết đặt tâm lý người sử dụng vào mình xem rồi biết thôi ...
y trang tâm lý khoá cửa xong gửi chìa khoá tay hàng xóm
_)()(-,,,,,,,


1726925570391.png
 
Lần chỉnh sửa cuối:
Mấy năm trước, Google bắt đầu chặn yêu cầu ủy quyền thông qua webview hoặc webview được nhúng vào WinForm, đại loại là thường trả về lỗi "This browser or app may not be secure". Mình có tra cứu trên GitHub và tìm thấy đoạn code mẫu C# .NET này của Google, nhìn chung logic như sau:
  1. Tạo uri yêu cầu ủy quyền.
  2. Tạo redirect uri (tức là uri chuyển hướng sau khi thực hiện xong ủy quyền) để theo dõi.
  3. Dùng lớp (class) HttpListener để theo dõi lưu lượng HTTP từ redirect uri trên.
  4. Tạo tiến trình (process) với uri yêu cầu ủy quyền để Windows mở bằng trình duyệt mặc định.
  5. Khi người dùng thực hiện ủy quyền xong (thành công hay thất bại) sẽ trả về redirect uri, HttpListener bắt được thông tin từ redirect uri và gửi thông báo đến trình duyệt rằng người dùng có thể đóng trình duyệt lại.
  6. Tiến hành phân tích thông tin nhận được, trao đổi access token nếu có authorization code, thông báo lỗi nếu có.
View attachment 304087
OAuthDesktopApp/OAuthDesktopApp/MainWindow.xaml.cs
Cái này mình không rõ Delphi có thư viện nào tương tự như vậy không, gửi bạn tham khảo thử.
Rảnh tôi thử nhúng xem nhưng mọi cái đã bị chặn sạch kể cả TWebBrowser có lẻ do bảo mật tài khoản nên Google chặn lại

nhưng có rất nhiều cách mà không cần nhúng .. chỉ cần File JSON xong người dùng bấm nút là xong

Tới khúc này rồi mọi cái tự động sạch ... Viết máy chủ cho nó tự động lấy mã xác thực của Google xong lấy Token luôn


1727231203870.png
 
Rảnh tôi thử nhúng xem nhưng mọi cái đã bị chặn sạch kể cả TWebBrowser có lẻ do bảo mật tài khoản nên Google chặn lại

nhưng có rất nhiều cách mà không cần nhúng .. chỉ cần File JSON xong người dùng bấm nút là xong

Tới khúc này rồi mọi cái tự động sạch ... Viết máy chủ cho nó tự động lấy mã xác thực của Google xong lấy Token luôn


View attachment 304247
Nói chung là Google đã chặn hết các thể loại từ trình duyệt cho đến webview. Bạn có thể xem chi tiết ở đây trên Github: [UPDATE] Google Auth Flows and WebView2 #3828
Theo hướng dẫn từ Google mà tôi đã đề cập, tôi đã dựa vào đó để viết một script nhỏ xin quyền gửi email từ người dùng để gửi một email đơn giản bằng Gmail API. Script này được viết bằng PowerShell mặc dù hướng dẫn viết bằng C#, nhưng C# chính là đứa đẻ ra PowerShell nên viết code vẫn chạy tốt.
Script này chạy tốt trên Windows, nếu muốn chạy trên phiên bản PowerShell của Linux thì phải sửa biến môi trường $env: cho phù hợp với đường dẫn của Linux với thay đổi cách xác định kiểu MIME.
Mã:
class AuthorizationRequestException: System.Exception {
    AuthorizationRequestException() {  
    }
    AuthorizationRequestException([string]$Message): base($Message) {
    }
    AuthorizationRequestException([string]$Message, [System.Exception]$InnerException): base($Message, $InnerException) {
    }
}
class DataStoreFolderNotFoundException: System.Exception {
    DataStoreFolderNotFoundException() {  
    }
    DataStoreFolderNotFoundException([string]$Message): base($Message) {
    }
    DataStoreFolderNotFoundException([string]$Message, [System.Exception]$InnerException): base($Message, $InnerException) {
    }
}
function Read-AccessToken {
    param(
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$JsonFile,
        [bool]$KeepMeSignedIn,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ApplicationName,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientId,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientSecret,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Scope
    )
    #Nếu tập tin credential.json tồn tại
    if ([System.IO.File]::Exists($JsonFile)) {
        #Đọc nội dung tập tin chứa thông tin access token và refresh token (nếu có), credential.json
        [PSCustomObject]$CredentialJson = Get-Content -Path $JsonFile | ConvertFrom-Json
        #Xác định ngày hết hạn của access token
        [datetime]$AccessTokenExpirationDateTime = [System.DateTime]::Parse($CredentialJson.expires_in)
        #Nếu access token đã hết hạn
        if ([System.DateTime]::Compare([System.DateTime]::Now, $AccessTokenExpirationDateTime) -ge 0) {
            #Trường hợp có refresh token
            if ($KeepMeSignedIn) {
                #Gửi refresh token để lấy access token mới
                [string]$RefreshToken = $CredentialJson.refresh_token
                [System.Func[string, string]]$RefreshAccessToken = {
                    param([string]$RefreshToken)
                    [string]$Uri = "https://oauth2.googleapis.com/token"
                    [string]$FormData = "code=$($AuthorizationCode)&client_id=$($ClientId)&client_secret=$($ClientSecret)&grant_type=refresh_token&refresh_token=$($RefreshToken)"
                    [PsCustomObject]$Response = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/x-www-form-urlencoded" -Body $FormData
                    [datetime]$NewExpiration = [System.DateTime]::Now.AddSeconds($Response.expires_in)
                    [PSCustomObject]$OldResponse = Get-Content $JsonFile | ConvertFrom-Json
                    $OldResponse.access_token = $Response.access_token
                    $OldResponse.expires_in = $NewExpiration
                    $OldResponse | ConvertTo-Json | Out-File -FilePath $JsonFile -Force
                    return $Response.access_token
                }
                return $RefreshAccessToken.Invoke($RefreshToken)
            }
            #Nếu không có refresh token thì gửi yêu cầu ủy quyền để lấy access token mới
            else {
                Remove-Item -Path $JsonFile -Force
                New-AuthorizationRequest -ClientId $ClientId -ClientSecret $ClientSecret -KeepMeSignedIn $KeepMeSignedIn -ApplicationName $ApplicationName -Scope $Scope
                return Read-AccessToken -JsonFile $JsonFile -KeepMeSignedIn $KeepMeSignedIn -ClientId $ClientId -ClientSecret $ClientSecret -ApplicationName $ApplicationName -Scope $Scope
            }
        }
        else {
            #Nếu access token chưa hết hạn thì lấy thông tin access token từ tập tin credential.json
            return $CredentialJson.access_token
        }
    }
    #Nếu tập tin credential.json không tồn tại thì gửi yêu cầu ủy quyền để lấy access token
    else {
        New-AuthorizationRequest -ClientId $ClientId -ClientSecret $ClientSecret -KeepMeSignedIn $KeepMeSignedIn -ApplicationName $ApplicationName -Scope $Scope
        return Read-AccessToken -JsonFile $JsonFile -KeepMeSignedIn $KeepMeSignedIn -ClientId $ClientId -ClientSecret $ClientSecret -ApplicationName $ApplicationName -Scope $Scope
    }
}
function Start-AuthorizationProcess {
    param(
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientId,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientSecret,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ApplicationName,
        [bool]$KeepMeSignedIn,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Scope
    )
    if ($null -eq $ApplicationName) {
        throw [System.ArgumentNullException]::new("Please specify a name for your application. ApplicationName is null.")
    }
    [string]$CredentialJsonFile = [System.IO.Path]::Combine($env:APPDATA, $ApplicationName, "credential.json")
    [string]$AccessToken = Read-AccessToken -JsonFile $CredentialJsonFile -KeepMeSignedIn $KeepMeSignedIn -ClientId $ClientId -ClientSecret $ClientSecret -ApplicationName $ApplicationName -Scope $Scope
    return $AccessToken
}
function New-AuthorizationRequest {
    param(
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientId,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientSecret,
        [bool]$KeepMeSignedIn,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ApplicationName,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$Scope
    )
    [System.Func[string]]$GenerateCodeVerifier = {
        return ConvertTo-Base64EncodedNoPaddingString -Data (Get-RandomNumber -Length 128)
    }
    [string]$CodeVerifier = $GenerateCodeVerifier.Invoke()
    [string]$State = $GenerateCodeVerifier.Invoke()
    [System.Func[string, string]]$GenerateCodeChallenge = {
        param([string]$CodeVerifier)
        [System.Func[string, byte[]]]$ConvertToSha256 = {
            param([string]$Data)
            [byte[]]$InputAsBinary = [System.Text.ASCIIEncoding]::UTF8.GetBytes($Data)
            [System.Security.Cryptography.SHA256]$Sha256 = [System.Security.Cryptography.SHA256]::Create()
            [byte[]]$InputSha256Hash = $Sha256.ComputeHash($InputAsBinary)
            return $InputSha256Hash
        }
        return ConvertTo-Base64EncodedNoPaddingString -Data ($ConvertToSha256.Invoke($CodeVerifier))
    }
    [string]$CodeChallenge = $GenerateCodeChallenge.Invoke($CodeVerifier)
    [string]$AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth"
    [System.Func[int]]$GetRandomUnusedPort = {
        [System.Net.Sockets.TcpListener]$Server = New-Object System.Net.Sockets.TcpListener -ArgumentList ([System.Net.IPAddress]::Loopback, 0)
        $Server.Start()
        [System.Net.IPEndPoint]$IP = $Server.LocalEndpoint
        $Server.Stop()
        return $IP.Port
    }
    [int]$UnusedPort = $GetRandomUnusedPort.Invoke()
    [string]$RedirectUri = "http://$([System.Net.IPAddress]::Loopback):$($UnusedPort)/"
    [System.Net.HttpListener]$HttpServer = New-Object System.Net.HttpListener
    $HttpServer.Prefixes.Add($RedirectUri)
    try {
        $HttpServer.Start()
        [string]$AuthorizationUri = "$($AuthorizationEndpoint)?response_type=code&scope=$([System.Web.HttpUtility]::UrlEncode($Scope -join " "))&redirect_uri=$([System.Uri]::EscapeDataString($RedirectUri))&client_id=$($ClientId)&code_challenge=$($CodeChallenge)&code_challenge_method=S256&state=$($State)"
        if ($KeepMeSignedIn) {
            $AuthorizationUri += "&access_type=offline"
        }
        try {
            Start-Process $AuthorizationUri
        }
        catch {
            if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
                Start-Proces -ArgumentList "xdg-open", $AuthorizationUri
            }
            if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) {
                Start-Proces -ArgumentList "open", $AuthorizationUri
            }
            else {
                throw [System.NotSupportedException]::new("Operating system not supported.")
            }
        }
        [System.Net.HttpListenerContext]$Context = $HttpServer.GetContext()
        [System.Action[string]]$DisplayInfoOnBrowser = {
            param([string]$Text)
            if ($null -eq $Text) {
                throw [System.ArgumentNullException]::new("Parameter $("$Text") is required.")
            }
            [byte[]]$Buffer = [System.Text.Encoding]::UTF8.GetBytes("<html><body><p>$($Text)</p><body></html>")
            $Context.Response.ContentType = "text/html"
            $Context.Response.ContentLength64 = $Buffer.Length
            $Context.Response.OutputStream.Write($Buffer)
        }
        [string]$IncomingState = $Context.Request.QueryString.Get("state")
        if ([string]::IsNullOrEmpty($State)) {
            throw [AuthorizationRequestException]::new("Received request with invalid state: $($IncomingState)")
        }
        if ([string]::IsNullOrEmpty($Context.Request.QueryString.Get("error")) -eq $false) {
            [string]$Error = $Context.Request.QueryString.Get("error")
            switch ($Error) {
                "access_denied"
                {
                    $DisplayInfoOnBrowser.Invoke("Authorization request failed.")
                    throw [AuthorizationRequestException]::new("The user denied granting access to your application. Please try again!")
                }
                Default {}
            }
        }
        [string]$Code = $Context.Request.QueryString.Get("code")
        if ([string]::IsNullOrEmpty($Code)) {
            throw [AuthorizationRequestException]::new("Malformed authorization response.")
        }
        [System.Action[string, string, string]]$ExchangeAuthorizationCodeForAccessToken = {
            param([string]$AuthorizationCode, [string]$CodeVerifier, [string]$OutputFile)
            [string]$Uri = "https://oauth2.googleapis.com/token"
            [string]$FormData = "code=$($AuthorizationCode)&client_id=$($ClientId)&client_secret=$($ClientSecret)&redirect_uri=$([System.Uri]::EscapeDataString($RedirectUri))&grant_type=authorization_code&code_verifier=$($CodeVerifier)"
            [PSCustomObject]$Response = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/x-www-form-urlencoded" -Body $FormData
            [datetime]$NewExpiration = [System.DateTime]::Now.AddSeconds($Response.expires_in)
            $Response = $Response | Select-Object -ExcludeProperty expires_in
            Add-Member -InputObject $Response -MemberType NoteProperty -Name expires_in -Value $NewExpiration
            [string]$DataStoreFolder = [System.IO.Path]::GetDirectoryName($OutputFile)
            if ((Test-Path $DataStoreFolder) -eq $false) {
                New-Item -Path $DataStoreFolder -ItemType Directory
            }
            $Response | ConvertTo-Json | Out-File -FilePath $OutputFile -Force
        }
        [string]$CredentialJsonFile = [System.IO.Path]::Combine($env:APPDATA, $ApplicationName, "credential.json")
        $DisplayInfoOnBrowser.Invoke("Authorization complete. You can close this browser now.")
        $ExchangeAuthorizationCodeForAccessToken.Invoke($Code, $CodeVerifier, $CredentialJsonFile)
    }
    finally {
        if ($HttpServer.IsListening) {
            $HttpServer.Close()
        }
    }
}
function Get-RandomNumber {
    param(
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [int]$Length
    )
    [System.Security.Cryptography.RandomNumberGenerator] $RandomNums = [System.Security.Cryptography.RandomNumberGenerator]::Create()
    [byte[]]$NumsAsBinary = [byte[]] @(,0) * $Length
    $RandomNums.GetBytes($NumsAsBinary)
    return $NumsAsBinary
}
function ConvertTo-Base64EncodedNoPaddingString {
    param(
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [byte[]]$Data
    )
    [string]$Base64EncodedString = [System.Convert]::ToBase64String($Data)
    return $Base64EncodedString.Replace("+", "-").Replace("=", "").Replace("/", "_")
}
function Revoke-AccessToken {
    param(
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$ApplicationName
    )
    if ($null -eq $ApplicationName) {
        throw [System.ArgumentNullException]::new("Please specify a name for your application. ApplicationName is null.")
    }
    [string]$DataStoreFolder = [System.IO.Path]::Combine($env:APPDATA, $ApplicationName)
    if ((Test-Path $DataStoreFolder) -eq $false) {
        throw [DataStoreFolderNotFoundException]::new("The data store folder for $($ApplicationName) was not found.")
    }
    [string]$JsonFile = [System.IO.Path]::Combine($DataStoreFolder, "credential.json")
    [System.Func[string, string]]$RetriveRefreshToken = {
        param([string]$JsonFile)
        if ([System.IO.File]::Exists($JsonFile) -eq $false) {
            throw [System.IO.FileNotFoundException]::new("The 'credential.json' was not found.")
        }
        [PSCustomObject]$Json = Get-Content -Path $JsonFile | ConvertFrom-Json | Select-Object -Property refresh_token
        if ($null -eq $JsonFile.refresh_token) {
            throw [System.Exception]::new("The data store folder $($ApplicationName) does not contain a refresh token to be revoked.")
        }
        return $Json.refresh_token
    }
    [string]$RefreshToken = $RetriveRefreshToken.Invoke($JsonFile)
    [string]$Uri = "https://oauth2.googleapis.com/revoke?token=$($RefreshToken)"
    [Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject]$Response = Invoke-WebRequest -Uri $Uri -ContentType "application/x-www-form-urlencoded" -Method Post
    if ($Response.StatusCode -eq 200) {
        Remove-Item -Path $JsonFile
    }
    else {
        throw [System.Exception]::new("Failed to revoke the access token.")
    }
}
function Send-GmailMessage {
    param(
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$From,
        [string]$To,
        [string]$Subject,
        [string]$Body,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$AccessToken,
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [bool]$HasAttach,
        [string[]]$AttachmentFiles,
        [parameter(Mandatory = $true)]
        [ValidateSet("Plain", "Html")]
        [ValidateNotNullOrEmpty()]
        [string]$BodyFormat
    )
    [System.Func[hashtable, PSCustomObject]] $CreateMessage = {
        param([hashtable]$Headers)
        if ($HasAttach -and ($null -eq $AttachmentFiles)) {
            throw [System.ArgumentNullException]::new("Missing attachment file(s) while 'HasAttach' is set to true.")
        }
        [System.Text.StringBuilder]$StringBuilder = New-Object System.Text.StringBuilder
        $StringBuilder.Append("Content-Type: multipart/mixed; boundary=EmailWithAttachments")
        $StringBuilder.AppendLine()
        $StringBuilder.Append("MIME-Version: 1.0")
        $StringBuilder.AppendLine()
        $StringBuilder.Append("to: $($To)")
        $StringBuilder.AppendLine()
        $StringBuilder.Append("from: $($From)")
        $StringBuilder.AppendLine()
        $StringBuilder.Append("subject: $($Subject)")
        $StringBuilder.AppendLine()
        $StringBuilder.AppendLine()
        $StringBuilder.Append("--EmailWithAttachments")
        $StringBuilder.AppendLine()
        switch ($BodyFormat) {
            "Plain" { $StringBuilder.Append("Content-Type: text/plain; charset=UTF-8"); break }
            "Html" { $StringBuilder.Append("Content-Type: text/html; charset=UTF-8"); break }
            Default {}
        }
        $StringBuilder.AppendLine()
        $StringBuilder.Append($Body)
        $StringBuilder.AppendLine()
        $StringBuilder.AppendLine()
        $StringBuilder.Append("--EmailWithAttachments--")
        [string]$Message = $StringBuilder.ToString()
        $Headers.Add("Content-Length", [System.Text.Encoding]::UTF8.GetByteCount($Message)) ([System.Text.Encoding]::UTF8.GetBytes($StringBuilder.ToString()))
        if ($HasAttach -eq $false) {
            return [PSCustomObject]@{
                Message = $Message
                Uri = "https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media"
                Headers = $Headers
                ContentType = "message/rfc822"
            }
        }
        else {
            [System.Func[string, string]]$GetMimeType = {
                param([string]$FileExtension)
                New-PSDrive -Name "HKCR" -PSProvider Registry -Root "HKEY_CLASSES_ROOT" | Out-Null
                [string]$Path = "HKCR:\$($FileExtension)"
                if (Test-Path $Path) {
                    [string]$MimeType = Get-ItemPropertyValue -Path "HKCR:\$($FileExtension)" -Name "Content Type"
                }
                else {
                    return "application/unknown"
                }
                Remove-PSDrive -Name "HKCR"
                return $MimeType
            }
            [System.Text.StringBuilder]$MessageWithAttachmentsBuilder = New-Object System.Text.StringBuilder
            $MessageWithAttachmentsBuilder.Append("Content-Type: multipart/mixed; boundary=EmailWithAttachments")
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.Append("MIME-Version: 1.0")
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.Append("to: $($To)")
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.Append("from: $($From)")
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.Append("subject: $($Subject)")
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.Append("--EmailWithAttachments")
            $MessageWithAttachmentsBuilder.AppendLine()
            switch ($BodyFormat) {
                "Plain" { $MessageWithAttachmentsBuilder.Append("Content-Type: text/plain; charset=UTF-8"); break }
                "Html" { $MessageWithAttachmentsBuilder.Append("Content-Type: text/html; charset=UTF-8"); break }
                Default {}
            }
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.Append($Body)
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.AppendLine()
            ForEach-Object -InputObject $AttachmentFiles {
                if ([System.IO.File]::Exists($_) -eq $false) {
                    throw [System.IO.FileNotFoundException]::new("The attachment file $($_) was not found.")
                }
                $File = Get-Item -Path $_
                [string]$MimeType = $GetMimeType.Invoke($File.Extension)
                $MessageWithAttachmentsBuilder.Append("--EmailWithAttachments")
                $MessageWithAttachmentsBuilder.AppendLine()
                $MessageWithAttachmentsBuilder.Append("Content-Type: $($MimeType)")
                $MessageWithAttachmentsBuilder.AppendLine()
                $MessageWithAttachmentsBuilder.Append("MIME-Version: 1.0")
                $MessageWithAttachmentsBuilder.AppendLine()
                $MessageWithAttachmentsBuilder.Append("Content-Transfer-Encoding: base64")
                $MessageWithAttachmentsBuilder.AppendLine()
                $MessageWithAttachmentsBuilder.Append("Content-Disposition: attachment; filename=`u{0022}$($File.Name)`u{0022}")
                $MessageWithAttachmentsBuilder.AppendLine()
                $MessageWithAttachmentsBuilder.AppendLine()
                [byte[]]$Buffer = [System.IO.File]::ReadAllBytes($_)
                [string]$Base64EncodedString = [System.Convert]::ToBase64String($Buffer)
                $MessageWithAttachmentsBuilder.Append($Base64EncodedString)
              $MessageWithAttachmentsBuilder.AppendLine()
            }
            $MessageWithAttachmentsBuilder.AppendLine()
            $MessageWithAttachmentsBuilder.Append("--EmailWithAttachments--")
            [string]$MessageAsString = $MessageWithAttachmentsBuilder.ToString()
            $Headers.Add("Content-Length", [System.Text.Encoding]::UTF8.GetByteCount($MessageAsString))
            return [PSCustomObject]@{
                Message = $MessageAsString
                Uri = "https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media"
                Headers = $Headers
                ContentType = "message/rfc822"
            }
        }
    }
    [hashtable]$Headers = @{
        Authorization = "Bearer $($AccessToken)"
    }
    [PSCustomObject]$Payload = $CreateMessage.Invoke($Headers)
    [Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject]$Response = Invoke-WebRequest -Uri $Payload.Uri -Method Post -ContentType $Payload.ContentType -Headers $Payload.Headers -Body $Payload.Message -SkipHttpErrorCheck -SkipHeaderValidation
    if ($Response.StatusCode -eq 200) {
        [PSCustomObject]$Json = $Response.Content | ConvertFrom-Json
        Write-Host $Json
    }
    else {
        Write-Error $Response.Content
    }
}
$ClientId = "" #ClientId
$ClientSecret = "" #ClientSecret
[string]$AccessToken = Start-AuthorizationProcess -ClientId $ClientId -ClientSecret $ClientSecret -KeepMeSignedIn $true -ApplicationName "Gmail" -Scope @("https://www.googleapis.com/auth/gmail.send")
[string]$HtmlBody = @"
<h1>Test Mail</h1>
<p>This is a <b>test mail</b>.</p>
"@
Send-GmailMessage -From "abc@gmail.com" -To "bcd@yahoo.com.vn" -Subject "Test Mail" -Body $HtmlBody -BodyFormat "Html" -AccessToken $AccessToken -HasAttach $false
1727237166836.png
 
Lần chỉnh sửa cuối:
Nói chung là Google đã chặn hết các thể loại từ trình duyệt cho đến webview. Bạn có thể xem chi tiết ở đây trên Github: [UPDATE] Google Auth Flows and WebView2 #3828 Theo hướng dẫn từ Google mà tôi đã đề cập, tôi đã dựa vào đó để viết một script nhỏ xin quyền gửi email từ người dùng để gửi một email đơn giản bằng Gmail API. Script này được viết bằng PowerShell mặc dù hướng dẫn viết bằng C#, nhưng C# chính là đứa đẻ ra PowerShell nên viết code vẫn chạy tốt. Script này chạy tốt trên Windows, nếu muốn chạy trên phiên bản PowerShell của Linux thì phải sửa biến môi trường $env: cho phù hợp với đường dẫn của Linux với thay đổi cách xác định kiểu MIME.
Mã:
class AuthorizationRequestException: System.Exception { AuthorizationRequestException() { } AuthorizationRequestException([string]$Message): base($Message) { } AuthorizationRequestException([string]$Message, [System.Exception]$InnerException): base($Message, $InnerException) { } } class DataStoreFolderNotFoundException: System.Exception { DataStoreFolderNotFoundException() { } DataStoreFolderNotFoundException([string]$Message): base($Message) { } DataStoreFolderNotFoundException([string]$Message, [System.Exception]$InnerException): base($Message, $InnerException) { } } function Read-AccessToken { param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$JsonFile, [bool]$KeepMeSignedIn, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ApplicationName, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ClientId, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ClientSecret, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]]$Scope ) #Nếu tập tin credential.json tồn tại if ([System.IO.File]::Exists($JsonFile)) { #Đọc nội dung tập tin chứa thông tin access token và refresh token (nếu có), credential.json [PSCustomObject]$CredentialJson = Get-Content -Path $JsonFile | ConvertFrom-Json #Xác định ngày hết hạn của access token [datetime]$AccessTokenExpirationDateTime = [System.DateTime]::Parse($CredentialJson.expires_in) #Nếu access token đã hết hạn if ([System.DateTime]::Compare([System.DateTime]::Now, $AccessTokenExpirationDateTime) -ge 0) { #Trường hợp có refresh token if ($KeepMeSignedIn) { #Gửi refresh token để lấy access token mới [string]$RefreshToken = $CredentialJson.refresh_token [System.Func[string, string]]$RefreshAccessToken = { param([string]$RefreshToken) [string]$Uri = "https://oauth2.googleapis.com/token" [string]$FormData = "code=$($AuthorizationCode)&client_id=$($ClientId)&client_secret=$($ClientSecret)&grant_type=refresh_token&refresh_token=$($RefreshToken)" [PsCustomObject]$Response = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/x-www-form-urlencoded" -Body $FormData [datetime]$NewExpiration = [System.DateTime]::Now.AddSeconds($Response.expires_in) [PSCustomObject]$OldResponse = Get-Content $JsonFile | ConvertFrom-Json $OldResponse.access_token = $Response.access_token $OldResponse.expires_in = $NewExpiration $OldResponse | ConvertTo-Json | Out-File -FilePath $JsonFile -Force return $Response.access_token } return $RefreshAccessToken.Invoke($RefreshToken) } #Nếu không có refresh token thì gửi yêu cầu ủy quyền để lấy access token mới else { Remove-Item -Path $JsonFile -Force New-AuthorizationRequest -ClientId $ClientId -ClientSecret $ClientSecret -KeepMeSignedIn $KeepMeSignedIn -ApplicationName $ApplicationName -Scope $Scope return Read-AccessToken -JsonFile $JsonFile -KeepMeSignedIn $KeepMeSignedIn -ClientId $ClientId -ClientSecret $ClientSecret -ApplicationName $ApplicationName -Scope $Scope } } else { #Nếu access token chưa hết hạn thì lấy thông tin access token từ tập tin credential.json return $CredentialJson.access_token } } #Nếu tập tin credential.json không tồn tại thì gửi yêu cầu ủy quyền để lấy access token else { New-AuthorizationRequest -ClientId $ClientId -ClientSecret $ClientSecret -KeepMeSignedIn $KeepMeSignedIn -ApplicationName $ApplicationName -Scope $Scope return Read-AccessToken -JsonFile $JsonFile -KeepMeSignedIn $KeepMeSignedIn -ClientId $ClientId -ClientSecret $ClientSecret -ApplicationName $ApplicationName -Scope $Scope } } function Start-AuthorizationProcess { param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ClientId, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ClientSecret, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ApplicationName, [bool]$KeepMeSignedIn, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]]$Scope ) if ($null -eq $ApplicationName) { throw [System.ArgumentNullException]::new("Please specify a name for your application. ApplicationName is null.") } [string]$CredentialJsonFile = [System.IO.Path]::Combine($env:APPDATA, $ApplicationName, "credential.json") [string]$AccessToken = Read-AccessToken -JsonFile $CredentialJsonFile -KeepMeSignedIn $KeepMeSignedIn -ClientId $ClientId -ClientSecret $ClientSecret -ApplicationName $ApplicationName -Scope $Scope return $AccessToken } function New-AuthorizationRequest { param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ClientId, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ClientSecret, [bool]$KeepMeSignedIn, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ApplicationName, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]]$Scope ) [System.Func[string]]$GenerateCodeVerifier = { return ConvertTo-Base64EncodedNoPaddingString -Data (Get-RandomNumber -Length 128) } [string]$CodeVerifier = $GenerateCodeVerifier.Invoke() [string]$State = $GenerateCodeVerifier.Invoke() [System.Func[string, string]]$GenerateCodeChallenge = { param([string]$CodeVerifier) [System.Func[string, byte[]]]$ConvertToSha256 = { param([string]$Data) [byte[]]$InputAsBinary = [System.Text.ASCIIEncoding]::UTF8.GetBytes($Data) [System.Security.Cryptography.SHA256]$Sha256 = [System.Security.Cryptography.SHA256]::Create() [byte[]]$InputSha256Hash = $Sha256.ComputeHash($InputAsBinary) return $InputSha256Hash } return ConvertTo-Base64EncodedNoPaddingString -Data ($ConvertToSha256.Invoke($CodeVerifier)) } [string]$CodeChallenge = $GenerateCodeChallenge.Invoke($CodeVerifier) [string]$AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth" [System.Func[int]]$GetRandomUnusedPort = { [System.Net.Sockets.TcpListener]$Server = New-Object System.Net.Sockets.TcpListener -ArgumentList ([System.Net.IPAddress]::Loopback, 0) $Server.Start() [System.Net.IPEndPoint]$IP = $Server.LocalEndpoint $Server.Stop() return $IP.Port } [int]$UnusedPort = $GetRandomUnusedPort.Invoke() [string]$RedirectUri = "http://$([System.Net.IPAddress]::Loopback):$($UnusedPort)/" [System.Net.HttpListener]$HttpServer = New-Object System.Net.HttpListener $HttpServer.Prefixes.Add($RedirectUri) try { $HttpServer.Start() [string]$AuthorizationUri = "$($AuthorizationEndpoint)?response_type=code&scope=$([System.Web.HttpUtility]::UrlEncode($Scope -join " "))&redirect_uri=$([System.Uri]::EscapeDataString($RedirectUri))&client_id=$($ClientId)&code_challenge=$($CodeChallenge)&code_challenge_method=S256&state=$($State)" if ($KeepMeSignedIn) { $AuthorizationUri += "&access_type=offline" } try { Start-Process $AuthorizationUri } catch { if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) { Start-Proces -ArgumentList "xdg-open", $AuthorizationUri } if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)) { Start-Proces -ArgumentList "open", $AuthorizationUri } else { throw [System.NotSupportedException]::new("Operating system not supported.") } } [System.Net.HttpListenerContext]$Context = $HttpServer.GetContext() [System.Action[string]]$DisplayInfoOnBrowser = { param([string]$Text) if ($null -eq $Text) { throw [System.ArgumentNullException]::new("Parameter $("$Text") is required.") } [byte[]]$Buffer = [System.Text.Encoding]::UTF8.GetBytes("
$($Text)
") $Context.Response.ContentType = "text/html" $Context.Response.ContentLength64 = $Buffer.Length $Context.Response.OutputStream.Write($Buffer) } [string]$IncomingState = $Context.Request.QueryString.Get("state") if ([string]::IsNullOrEmpty($State)) { throw [AuthorizationRequestException]::new("Received request with invalid state: $($IncomingState)") } if ([string]::IsNullOrEmpty($Context.Request.QueryString.Get("error")) -eq $false) { [string]$Error = $Context.Request.QueryString.Get("error") switch ($Error) { "access_denied" { $DisplayInfoOnBrowser.Invoke("Authorization request failed.") throw [AuthorizationRequestException]::new("The user denied granting access to your application. Please try again!") } Default {} } } [string]$Code = $Context.Request.QueryString.Get("code") if ([string]::IsNullOrEmpty($Code)) { throw [AuthorizationRequestException]::new("Malformed authorization response.") } [System.Action[string, string, string]]$ExchangeAuthorizationCodeForAccessToken = { param([string]$AuthorizationCode, [string]$CodeVerifier, [string]$OutputFile) [string]$Uri = "https://oauth2.googleapis.com/token" [string]$FormData = "code=$($AuthorizationCode)&client_id=$($ClientId)&client_secret=$($ClientSecret)&redirect_uri=$([System.Uri]::EscapeDataString($RedirectUri))&grant_type=authorization_code&code_verifier=$($CodeVerifier)" [PSCustomObject]$Response = Invoke-RestMethod -Uri $Uri -Method Post -ContentType "application/x-www-form-urlencoded" -Body $FormData [datetime]$NewExpiration = [System.DateTime]::Now.AddSeconds($Response.expires_in) $Response = $Response | Select-Object -ExcludeProperty expires_in Add-Member -InputObject $Response -MemberType NoteProperty -Name expires_in -Value $NewExpiration [string]$DataStoreFolder = [System.IO.Path]::GetDirectoryName($OutputFile) if ((Test-Path $DataStoreFolder) -eq $false) { New-Item -Path $DataStoreFolder -ItemType Directory } $Response | ConvertTo-Json | Out-File -FilePath $OutputFile -Force } [string]$CredentialJsonFile = [System.IO.Path]::Combine($env:APPDATA, $ApplicationName, "credential.json") $DisplayInfoOnBrowser.Invoke("Authorization complete. You can close this browser now.") $ExchangeAuthorizationCodeForAccessToken.Invoke($Code, $CodeVerifier, $CredentialJsonFile) } finally { if ($HttpServer.IsListening) { $HttpServer.Close() } } } function Get-RandomNumber { param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [int]$Length ) [System.Security.Cryptography.RandomNumberGenerator] $RandomNums = [System.Security.Cryptography.RandomNumberGenerator]::Create() [byte[]]$NumsAsBinary = [byte[]] @(,0) * $Length $RandomNums.GetBytes($NumsAsBinary) return $NumsAsBinary } function ConvertTo-Base64EncodedNoPaddingString { param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [byte[]]$Data ) [string]$Base64EncodedString = [System.Convert]::ToBase64String($Data) return $Base64EncodedString.Replace("+", "-").Replace("=", "").Replace("/", "_") } function Revoke-AccessToken { param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$ApplicationName ) if ($null -eq $ApplicationName) { throw [System.ArgumentNullException]::new("Please specify a name for your application. ApplicationName is null.") } [string]$DataStoreFolder = [System.IO.Path]::Combine($env:APPDATA, $ApplicationName) if ((Test-Path $DataStoreFolder) -eq $false) { throw [DataStoreFolderNotFoundException]::new("The data store folder for $($ApplicationName) was not found.") } [string]$JsonFile = [System.IO.Path]::Combine($DataStoreFolder, "credential.json") [System.Func[string, string]]$RetriveRefreshToken = { param([string]$JsonFile) if ([System.IO.File]::Exists($JsonFile) -eq $false) { throw [System.IO.FileNotFoundException]::new("The 'credential.json' was not found.") } [PSCustomObject]$Json = Get-Content -Path $JsonFile | ConvertFrom-Json | Select-Object -Property refresh_token if ($null -eq $JsonFile.refresh_token) { throw [System.Exception]::new("The data store folder $($ApplicationName) does not contain a refresh token to be revoked.") } return $Json.refresh_token } [string]$RefreshToken = $RetriveRefreshToken.Invoke($JsonFile) [string]$Uri = "https://oauth2.googleapis.com/revoke?token=$($RefreshToken)" [Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject]$Response = Invoke-WebRequest -Uri $Uri -ContentType "application/x-www-form-urlencoded" -Method Post if ($Response.StatusCode -eq 200) { Remove-Item -Path $JsonFile } else { throw [System.Exception]::new("Failed to revoke the access token.") } } function Send-GmailMessage { param( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$From, [string]$To, [string]$Subject, [string]$Body, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$AccessToken, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [bool]$HasAttach, [string[]]$AttachmentFiles, [parameter(Mandatory = $true)] [ValidateSet("Plain", "Html")] [ValidateNotNullOrEmpty()] [string]$BodyFormat ) [System.Func[hashtable, PSCustomObject]] $CreateMessage = { param([hashtable]$Headers) if ($HasAttach -and ($null -eq $AttachmentFiles)) { throw [System.ArgumentNullException]::new("Missing attachment file(s) while 'HasAttach' is set to true.") } [System.Text.StringBuilder]$StringBuilder = New-Object System.Text.StringBuilder $StringBuilder.Append("Content-Type: multipart/mixed; boundary=EmailWithAttachments") $StringBuilder.AppendLine() $StringBuilder.Append("MIME-Version: 1.0") $StringBuilder.AppendLine() $StringBuilder.Append("to: $($To)") $StringBuilder.AppendLine() $StringBuilder.Append("from: $($From)") $StringBuilder.AppendLine() $StringBuilder.Append("subject: $($Subject)") $StringBuilder.AppendLine() $StringBuilder.AppendLine() $StringBuilder.Append("--EmailWithAttachments") $StringBuilder.AppendLine() switch ($BodyFormat) { "Plain" { $StringBuilder.Append("Content-Type: text/plain; charset=UTF-8"); break } "Html" { $StringBuilder.Append("Content-Type: text/html; charset=UTF-8"); break } Default {} } $StringBuilder.AppendLine() $StringBuilder.Append($Body) $StringBuilder.AppendLine() $StringBuilder.AppendLine() $StringBuilder.Append("--EmailWithAttachments--") [string]$Message = $StringBuilder.ToString() $Headers.Add("Content-Length", [System.Text.Encoding]::UTF8.GetByteCount($Message)) ([System.Text.Encoding]::UTF8.GetBytes($StringBuilder.ToString())) if ($HasAttach -eq $false) { return [PSCustomObject]@{ Message = $Message Uri = "https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media" Headers = $Headers ContentType = "message/rfc822" } } else { [System.Func[string, string]]$GetMimeType = { param([string]$FileExtension) New-PSDrive -Name "HKCR" -PSProvider Registry -Root "HKEY_CLASSES_ROOT" | Out-Null [string]$Path = "HKCR:\$($FileExtension)" if (Test-Path $Path) { [string]$MimeType = Get-ItemPropertyValue -Path "HKCR:\$($FileExtension)" -Name "Content Type" } else { return "application/unknown" } Remove-PSDrive -Name "HKCR" return $MimeType } [System.Text.StringBuilder]$MessageWithAttachmentsBuilder = New-Object System.Text.StringBuilder $MessageWithAttachmentsBuilder.Append("Content-Type: multipart/mixed; boundary=EmailWithAttachments") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("MIME-Version: 1.0") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("to: $($To)") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("from: $($From)") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("subject: $($Subject)") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("--EmailWithAttachments") $MessageWithAttachmentsBuilder.AppendLine() switch ($BodyFormat) { "Plain" { $MessageWithAttachmentsBuilder.Append("Content-Type: text/plain; charset=UTF-8"); break } "Html" { $MessageWithAttachmentsBuilder.Append("Content-Type: text/html; charset=UTF-8"); break } Default {} } $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append($Body) $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.AppendLine() ForEach-Object -InputObject $AttachmentFiles { if ([System.IO.File]::Exists($_) -eq $false) { throw [System.IO.FileNotFoundException]::new("The attachment file $($_) was not found.") } $File = Get-Item -Path $_ [string]$MimeType = $GetMimeType.Invoke($File.Extension) $MessageWithAttachmentsBuilder.Append("--EmailWithAttachments") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("Content-Type: $($MimeType)") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("MIME-Version: 1.0") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("Content-Transfer-Encoding: base64") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("Content-Disposition: attachment; filename=`u{0022}$($File.Name)`u{0022}") $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.AppendLine() [byte[]]$Buffer = [System.IO.File]::ReadAllBytes($_) [string]$Base64EncodedString = [System.Convert]::ToBase64String($Buffer) $MessageWithAttachmentsBuilder.Append($Base64EncodedString) $MessageWithAttachmentsBuilder.AppendLine() } $MessageWithAttachmentsBuilder.AppendLine() $MessageWithAttachmentsBuilder.Append("--EmailWithAttachments--") [string]$MessageAsString = $MessageWithAttachmentsBuilder.ToString() $Headers.Add("Content-Length", [System.Text.Encoding]::UTF8.GetByteCount($MessageAsString)) return [PSCustomObject]@{ Message = $MessageAsString Uri = "https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media" Headers = $Headers ContentType = "message/rfc822" } } } [hashtable]$Headers = @{ Authorization = "Bearer $($AccessToken)" } [PSCustomObject]$Payload = $CreateMessage.Invoke($Headers) [Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject]$Response = Invoke-WebRequest -Uri $Payload.Uri -Method Post -ContentType $Payload.ContentType -Headers $Payload.Headers -Body $Payload.Message -SkipHttpErrorCheck -SkipHeaderValidation if ($Response.StatusCode -eq 200) { [PSCustomObject]$Json = $Response.Content | ConvertFrom-Json Write-Host $Json } else { Write-Error $Response.Content } } $ClientId = "" #ClientId $ClientSecret = "" #ClientSecret [string]$AccessToken = Start-AuthorizationProcess -ClientId $ClientId -ClientSecret $ClientSecret -KeepMeSignedIn $true -ApplicationName "Gmail" -Scope @("https://www.googleapis.com/auth/gmail.send") [string]$HtmlBody = @"
[HEADING=1]Test Mail[/HEADING]
This is a test mail.
"@ Send-GmailMessage -From "abc@gmail.com" -To "bcd@yahoo.com.vn" -Subject "Test Mail" -Body $HtmlBody -BodyFormat "Html" -AccessToken $AccessToken -HasAttach $false
View attachment 304251

sao rồi ... thấy bài trang trước có hỏi hổ trợ AccessToken có cần Mạnh hổ trợ thêm cho một hàm đơn giản lắm chỉ 1 tham số là File JSON là xong thôi

Mã:
Declare PtrSafe Function RetrieveAccessTokenFromJSON Lib "GoogleSheets64.dll" _
            (ByRef OAuthJsonPath As Variant) As Variant


Sub AccessToken_OAuthJsonPath()
    Dim OAuthJsonPath As Variant
    Dim AccessToken As Variant
  
    OAuthJsonPath = "C:\Google OAuth2\Desktop client 2.json"
    AccessToken = RetrieveAccessTokenFromJSON(OAuthJsonPath)
  
    Debug.Print AccessToken
End Sub

Muốn ghi dữ liệu lên Google Sheet chỉ cần một hàm trên là đủ rồi xong từ VBA viết mã sử dụng Http của Windows là xong thôi

Làm gì nhiều vẻ chuyện mệt ra ... còn nhanh thì viết Mã trong Delphi ghi dữ liệu lên Google Sheets
 
Lần chỉnh sửa cuối:
1727357794071.png

đơn giản lắm chỉ có thế thôi mà ....................--=0--=0--=0
 
Chốt lại chủ đề này quá đơn giản lại xếp vào ngăn kéo hay xó thôi ... hết trò chơi rồi chuyển game mới

ai có gì cần cứ hỏi tôi xem xét nếu khả năng có thể tôi sẻ hổ trợ

1/ Hàm lấy dữ liệu trả về các phương thức thuộc tính của ADODB Recordset từ đó người dùng tuỳ chỉnh các kiểu = xong
Mã:
Declare PtrSafe Function GoogleSheetAsRecordsetEx Lib "GoogleSheets64.dll" _
            (ByRef SheetId As Variant, _
             ByRef APIKey As Variant, _
             ByRef SheetName As Variant, _
             Optional ByVal ThreadCount As LongPtr = 0) As Variant

2/ Hổ trợ hàm lấy AccessToken để ghi dữ liệu vào Google Sheet = Xong ...
người dùng chỉ cần có thế xong sử dụng Http của Window ghi dữ liệu các kiểu lên Google Sheets tuỳ biến

Mã:
Declare PtrSafe Function RetrieveAccessTokenFromJSON Lib "GoogleSheets64.dll" _
            (ByRef OAuthJsonPath As Variant) As Variant


Sub AccessToken_OAuthJsonPath()
    Dim OAuthJsonPath As Variant
    Dim accessToken As Variant
   
    OAuthJsonPath = "C:\Google OAuth2\Desktop client 2.json"
    accessToken = RetrieveAccessTokenFromJSON(OAuthJsonPath)
   
    Debug.Print accessToken
End Sub
3/ Hàm Làm mới AccessToken = xong

Cơ bản ba hàm trên thôi là người dùng ôm cột múa lửa các kiểu trên VBA rồi ..
Nhưng sẻ chậm hơn xử lý mọi cái trong Delphi


4/ sẻ hổ trợ thêm hàm chuyển Data Excel thành JSON để ghi dữ liệu vào Google Sheets ( Mục này viết trên VBA khoãng 10 dòng code là xong ... nhưng chạy chậm thôi )

Mã:
Private Declare PtrSafe Function ConvertRangeToJSON Lib "GoogleSheets64.dll" _
    (ByVal Data As Variant) As Variant

Ai cần gì hay có ý kiến gì cứ mạnh dạn nêu .. nếu khả năng có thể Tôi sẻ hổ trợ
 
sao rồi ... thấy bài trang trước có hỏi hổ trợ AccessToken có cần Mạnh hổ trợ thêm cho một hàm đơn giản lắm chỉ 1 tham số là File JSON là xong thôi

Mã:
Declare PtrSafe Function RetrieveAccessTokenFromJSON Lib "GoogleSheets64.dll" _
            (ByRef OAuthJsonPath As Variant) As Variant


Sub AccessToken_OAuthJsonPath()
    Dim OAuthJsonPath As Variant
    Dim AccessToken As Variant
 
    OAuthJsonPath = "C:\Google OAuth2\Desktop client 2.json"
    AccessToken = RetrieveAccessTokenFromJSON(OAuthJsonPath)
 
    Debug.Print AccessToken
End Sub

Muốn ghi dữ liệu lên Google Sheet chỉ cần một hàm trên là đủ rồi xong từ VBA viết mã sử dụng Http của Windows là xong thôi

Làm gì nhiều vẻ chuyện mệt ra ... còn nhanh thì viết Mã trong Delphi ghi dữ liệu lên Google Sheets
OK rồi bạn. Bên cạnh hàm này của bạn, tôi cũng đang nghiên cứu một số giải pháp khác như Automation Add-ins, RTD Server, trên Excel để xem việc triển khai có những khó khăn gì, hiệu năng ra sao.
 
OK rồi bạn. Bên cạnh hàm này của bạn, tôi cũng đang nghiên cứu một số giải pháp khác như Automation Add-ins, RTD Server, trên Excel để xem việc triển khai có những khó khăn gì, hiệu năng ra sao.
AccessToken chỉ có thời hạn 1 giờ ... Rảnh tôi viết cái Service.exe cho nó tự động lấy và làm mới theo Windows khởi động là xong thôi

55 phút nó làm mới AccessToken lại 1 lần = xong = chạy tự động từ A To Z

Hàm có hết rồi chỉ ráp lại xong test + điều chỉnh = xong
 
OK rồi bạn. Bên cạnh hàm này của bạn, tôi cũng đang nghiên cứu một số giải pháp khác như Automation Add-ins, RTD Server, trên Excel để xem việc triển khai có những khó khăn gì, hiệu năng ra sao.
Ôi vãi lắm Bạn ôi ... Rảnh tôi thử làm ngược Từ Delphi sang làm thuần trên VBA của Excel

Thì nó còn đơn giản hơn cả viết trên Delphi và code ngắn = 1/3 của C# mới vãi


chỉ cần sử dụng Http của Ms là làm OK sạch từ A To Z ...
Bao gồm lấy
AccessToken , RefreshToken và đọc , ghi dữ liệu lên Google Sheets

mà không cần uỷ quyền gì cả ... thế mới biết rằng ai đó đầu đất mà cứ nghĩ ngọc thạch nguyên khối _+)(9_)()(---=0--=0--=0
 
Ôi vãi lắm Bạn ôi ... Rảnh tôi thử làm ngược Từ Delphi sang làm thuần trên VBA của Excel

Thì nó còn đơn giản hơn cả viết trên Delphi và code ngắn = 1/3 của C# mới vãi


chỉ cần sử dụng Http của Ms là làm OK sạch từ A To Z ...
Bao gồm lấy
AccessToken , RefreshToken và đọc , ghi dữ liệu lên Google Sheets

mà không cần uỷ quyền gì cả ... thế mới biết rằng ai đó đầu đất mà cứ nghĩ ngọc thạch nguyên khối _+)(9_)()(---=0--=0--=0

Đúng là không thể tiến bộ được. Đầu óc chỉ nghĩ đến cái mình muốn nói mà không thể tư duy vì sao người ta làm vậy.

Bây giờ thì tôi cho thiên hạ xem tính phét lác boa hoa của cậu đến đâu.
File tôi gửi link dưới đây lưu trên Google Drive, hãy tìm cách đọc và ghi dữ liệu trong đó. Tôi ra đề như vậy để cậu cho thiên hạ biết cậu là thiên tài hay thực sự chỉ là thành phần chém gió spam làm rác GPE. Nếu các thành viên GPE qua đây mà có biết đến những bài của bạn này và muốn tìm hiểu có phải chém gió hay không thì cứ theo dõi chắc không mất nhiều time đâu, kết quả sớm biết thôi . Tôi thực sự bận việc, không hề muốn bóc phốt ai hay làm cho ai mất mặt vì trình độ yếu kém. Nhưng với cậu này thì tôi sẽ một lần chứng thực để nếu thực sự làm được tôi sẽ dành sự ngưỡng mộ với cậu ấy. Còn nếu cậu ấy không làm được thì mong sau này hãy để GPE có không gian chia sẻ kiến thức trung thực và văn mình.
Link đây. Xin mời Kiều Mạnh!
 
Lần chỉnh sửa cuối:
Đúng là không thể tiến bộ được. Đầu óc chỉ nghĩ đến cái mình muốn nói mà không thể tư duy vì sao người ta làm vậy.

Bây giờ thì tôi cho thiên hạ xem tính phét lác boa hoa của cậu đến đâu.
File tôi gửi link dưới đây lưu trên Google Drive, hãy tìm cách đọc và ghi dữ liệu trong đó. Tôi ra đề như vậy để cậu cho thiên hạ biết cậu là thiên tài hay thực sự chỉ là thành phần chém gió spam làm rác GPE. Nếu các thành viên GPE qua đây mà có biết đến những bài của bạn này và muốn tìm hiểu có phải chém gió hay không thì cứ theo dõi chắc không mất nhiều time đâu, kết quả sớm biết thôi . Tôi thực sự bận việc, không hề muốn bóc phốt ai hay làm cho ai mất mặt vì trình độ yếu kém. Nhưng với cậu này thì tôi sẽ một lần chứng thực để nếu thực sự làm được tôi sẽ dành sự ngưỡng mộ với cậu ấy. Còn nếu cậu ấy không làm được thì mong sau này hãy để GPE có không gian chia sẻ kiến thức trung thực và văn mình.
Link đây. Xin mời Kiều Mạnh!
Những file như này bắt buộc phải uỷ quyền mới chỉnh sửa được !
 
Đúng là không thể tiến bộ được. Đầu óc chỉ nghĩ đến cái mình muốn nói mà không thể tư duy vì sao người ta làm vậy.

Bây giờ thì tôi cho thiên hạ xem tính phét lác boa hoa của cậu đến đâu.
File tôi gửi link dưới đây lưu trên Google Drive, hãy tìm cách đọc và ghi dữ liệu trong đó. Tôi ra đề như vậy để cậu cho thiên hạ biết cậu là thiên tài hay thực sự chỉ là thành phần chém gió spam làm rác GPE. Nếu các thành viên GPE qua đây mà có biết đến những bài của bạn này và muốn tìm hiểu có phải chém gió hay không thì cứ theo dõi chắc không mất nhiều time đâu, kết quả sớm biết thôi . Tôi thực sự bận việc, không hề muốn bóc phốt ai hay làm cho ai mất mặt vì trình độ yếu kém. Nhưng với cậu này thì tôi sẽ một lần chứng thực để nếu thực sự làm được tôi sẽ dành sự ngưỡng mộ với cậu ấy. Còn nếu cậu ấy không làm được thì mong sau này hãy để GPE có không gian chia sẻ kiến thức trung thực và văn mình.
Link đây. Xin mời Kiều Mạnh!
đầu có sao không đó ... Với tư duy đó mãi mãi cũng thế thôi ...mập mờ và lấp liếm
 
đầu có sao không đó ... Với tư duy đó mãi mãi cũng thế thôi ...mập mờ và lấp liếm

Lấp liếm và mập mờ cái gì nhỉ? Hỏi đi tôi trả lời luôn cho nhé. Còn link tôi gửi đó. Hãy làm đi chứ đừng boa hoa nữa.

Thực ra để tôi yêu cầu cậu phải làm những việc nhỏ mọn lặt vặt này chỉ để tôi và mọi người được chứng thực và ngưỡng một nhân tài thôi mà.
 
Lần chỉnh sửa cuối:
các bài link sau hình như của thành viên GPE ???
ai quan tâm tải về xem là viết OK đọc và ghi dữ liệu lên Google Sheet mô ta các bài chi tiết nhất

còn trên GPE này cũng có rồi tìm là thấy nhưng không trình bày mô tả chi tiết như vậy


Tôi viết ngắn gọn hơn các mô tả linh trên và không cần sử dụng Module JsonConverter của tây viết

thông tin module trên như sau
Mã:
' VBA-JSON v2.3.1
' (c) Tim Hall - https://github.com/VBA-tools/VBA-JSON
'
' JSON Converter for VBA
'
' Errors:
' 10001 - JSON parse error
'
' @class JsonConverter
' @author tim.hall.engr@gmail.com
' @license MIT (http://www.opensource.org/licenses/mit-license.php)
'' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
'
' Based originally on vba-json (with extensive changes)
' BSD license included below
'
' JSONLib, http://code.google.com/p/vba-json/
'
' Copyright (c) 2013, Ryo Yokoyama
' All rights reserved.
'

Ai biết chút VBA vào đó xem rất chi tiết và bài bản xong viết OK đọc và ghi dữ liệu lên Google Sheets

Quan trọng mã access_token và refresh_token sử dụng Http của Windows mà lấy xong tách nó ra mà sử dụng ... Viết hàm nhỏ trên VBA khoãng 10 to 15 dòng code là xong mà không cần thiết sử dụng Module của tây trên 1000 dòng code như nói trên

response trả về như sau ... Tôi xoá bớt thông tin rồi

Mã:
{
  "access_token": "ya29.a0AcM612yQ5YlErDV-NPhH4_iJEMnV7aBZhTzBVy07IpjIhoJNiIzuJJNpFgdm4d5EIyWo67JKdFHye8pVX8qtVqUAkr62atb3hsOLQzTkN8ua2W5weRboGYif2xBzPWo8vUs1P_h3tZfMyi9qvRxYhD9aCgYKAc8SARMSFQHGX2MiEYIeZNx9YS3-m2985F16mg0175",
  "expires_in": 3599,
  "refresh_token": "1//0euOptibtx5rDCgYIARANwrue1aqRqdh4rFz15WU-ZT9K5u_XaqawSAoG8zBFSBwINPl7PYwtBLb8w",
  "scope": "https://www.googleapis.com/auth/",
  "token_type": "Bearer"
}

dòng "expires_in": 3599, là thời gian hết hạn AccessToken sau 1 giờ thì ta làm mới lại như mô tả các bài trước của tôi là 55 phút hay 59 phút thì tuỳ

hay cho vào sự kiện Sub Auto_Open() mỗi lần mở file lên nó làm mới là xong thôi

Chốt lại VBA thừa khả năng viết và code còn gọn hơn ...
chỉ xử lý dữ liệu lớn là chậm do phân bổ RAM ít cho VBA và VBA không hổ trợ tốt bằng các Ngôn ngữ lập trình hiện đại
 
Lần chỉnh sửa cuối:
các bài link sau hình như của thành viên GPE ???
ai quan tâm tải về xem là viết OK đọc và ghi dữ liệu lên Google Sheet mô ta các bài chi tiết nhất

còn trên GPE này cũng có rồi tìm là thấy nhưng không trình bày mô tả chi tiết như vậy


Tôi viết ngắn gọn hơn các mô tả linh trên và không cần sử dụng Module JsonConverter của tây viết

thông tin module trên như sau
Mã:
' VBA-JSON v2.3.1
' (c) Tim Hall - https://github.com/VBA-tools/VBA-JSON
'
' JSON Converter for VBA
'
' Errors:
' 10001 - JSON parse error
'
' @class JsonConverter
' @author tim.hall.engr@gmail.com
' @license MIT (http://www.opensource.org/licenses/mit-license.php)
'' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
'
' Based originally on vba-json (with extensive changes)
' BSD license included below
'
' JSONLib, http://code.google.com/p/vba-json/
'
' Copyright (c) 2013, Ryo Yokoyama
' All rights reserved.
'

Ai biết chút VBA vào đó xem rất chi tiết và bài bản xong viết OK đọc và ghi dữ liệu lên Google Sheets

Quan trọng mã access_token và refresh_token sử dụng Http của Windows mà lấy xong tách nó ra mà sử dụng ... Viết hàm nhỏ trên VBA khoãng 10 to 15 dòng code là xong mà không cần thiết sử dụng Module của tây trên 1000 dòng code như nói trên

response trả về như sau ... Tôi xoá bớt thông tin rồi

Mã:
{
  "access_token": "ya29.a0AcM612yQ5YlErDV-NPhH4_iJEMnV7aBZhTzBVy07IpjIhoJNiIzuJJNpFgdm4d5EIyWo67JKdFHye8pVX8qtVqUAkr62atb3hsOLQzTkN8ua2W5weRboGYif2xBzPWo8vUs1P_h3tZfMyi9qvRxYhD9aCgYKAc8SARMSFQHGX2MiEYIeZNx9YS3-m2985F16mg0175",
  "expires_in": 3599,
  "refresh_token": "1//0euOptibtx5rDCgYIARANwrue1aqRqdh4rFz15WU-ZT9K5u_XaqawSAoG8zBFSBwINPl7PYwtBLb8w",
  "scope": "https://www.googleapis.com/auth/",
  "token_type": "Bearer"
}

dòng "expires_in": 3599, là thời gian hết hạn AccessToken sau 1 giờ thì ta làm mới lại như mô tả các bài trước của tôi là 55 phút hay 59 phút thì tuỳ

hay cho vào sự kiện Sub Auto_Open() mỗi lần mở file lên nó làm mới là xong thôi

Chốt lại VBA thừa khả năng viết và code còn gọn hơn ...
chỉ xử lý dữ liệu lớn là chậm do phân bổ RAM ít cho VBA và VBA không hổ trợ tốt bằng các Ngôn ngữ lập trình hiện đại

Thấy chưa thực tế những gì cậu đang TRÌNH BÀY VÀ CÃI CỐ = KHÔNG HIỂU GÌ

Cậu đang tìm tài liệu, mò mẫm học là dùng OAuth đó chính là quy trình đăng nhập và ủy quyền. TÔI NÓI MÃI MÀ VẪN KHÔNG HIỂU?

Từ bài cậu sưu tầm Python, C#, rồi đến bài này là VBA thì vẫn là OAuth - Đây chính là đăng nhập và ủy quyền đấy có biết không? Mà sao lại vẫn còn ghi như thế này?

thinkingProblem.png

Cậu muốn lấp liếm cái lỗi cậu đã phản bác vầ vấn đề "Đăng nhập" và "Ủy quyền" rồi sau lại lén "bắn" lại vấn đề này để nhằm khẳng định cái đã sai thành vẫn đúng. Ai không hiểu tưởng cậu là cái thứ gì khủng lắm. Tôi thì đã hiểu cậu ở mức nào lâu rồi nhưng muốn hiểu hơn nữa và giúp mọi người nhận ra những điều cậu nói đúng hay sai? Thiên tài? Boa hoa, ảo tưởng, hay spam chém gió.

Ta lại quay về câu hỏi để cậu thể hiện bản lĩnh nhé. Hãy ghi và đọc file tôi gửi ở bài #84.
 
Thấy chưa thực tế những gì cậu đang TRÌNH BÀY VÀ CÃI CỐ = KHÔNG HIỂU GÌ

Cậu đang tìm tài liệu, mò mẫm học là dùng OAuth đó chính là quy trình đăng nhập và ủy quyền. TÔI NÓI MÃI MÀ VẪN KHÔNG HIỂU?

Từ bài cậu sưu tầm Python, C#, rồi đến bài này là VBA thì vẫn là OAuth - Đây chính là đăng nhập và ủy quyền đấy có biết không? Mà sao lại vẫn còn ghi như thế này?

View attachment 304340

Cậu muốn lấp liếm cái lỗi cậu đã phản bác vầ vấn đề "Đăng nhập" và "Ủy quyền" rồi sau lại lén "bắn" lại vấn đề này để nhằm khẳng định cái đã sai thành vẫn đúng. Ai không hiểu tưởng cậu là cái thứ gì khủng lắm. Tôi thì đã hiểu cậu ở mức nào lâu rồi nhưng muốn hiểu hơn nữa và giúp mọi người nhận ra những điều cậu nói đúng hay sai? Thiên tài? Boa hoa, ảo tưởng, hay spam chém gió.

Ta lại quay về câu hỏi để cậu thể hiện bản lĩnh nhé. Hãy ghi và đọc file tôi gửi ở bài #84.
rác rác rác ... xác thực và uỷ toàn quyền khác nhau ... Mọi cái do người dùng tự xử lý không cần thiết bên thứ 3 trỏ vào .... ai mà biết làm gì tiếp theo

bài số 88 ai biết chỉ viết khoãng 80 đến dưới 150 dòng code trên VBA là xong mà không cần thiết sử dụng Module của tây trên 1000 dòng code

sử dụng module đó sau nó có lỗi làm sao mà dò ra ... chủ dộng viết lấy tách mã AccessToken ra là xong
 
Lần chỉnh sửa cuối:
rác rác rác ... xác thực và uỷ toàn quyền khác nhau ... Mọi cái do người dùng tự xử lý không cần thiết bên thứ 3 trỏ vào .... ai mà biết làm gì tiếp theo

Bên thứ 3 họ chỉ đưa cái link để người dùng đăng nhập và ủy quyền, cái link đó mở trên trình duyệt hay trên form đều bảo mật như nhau.
Ah. Cậu biết tí về Delphi nhỉ. Đăng tìm cách đưa TWebbrose lên form để chạy đúng không? Nó đấy, chỉ là đưa URL của hãng cloud lên đó và chạy thôi. Cái mà các công ty lập trình đều làm vậy đấy. Bản chất của nó là đưa trình duyệt trên form. Hãy nhấp chuột phải xem mã HTML của Google, Microsoft trên cái form đó là thấy nhé. Ở góc độ lập trình bao gói quy trình người ta phải làm vậy để người dùng tiếp cận dễ dàng.

Lý luận cùn + cái cố = rác.

Tôi khuyên cậu một lần nữa. Hãy đọc và hiểu vấn đề, suy ngẫm kỹ trước khi phản bác, chê bai, hạ uy tín của công ty hay cá nhân ai đó. Bản thân cậu đã tự khẳng định cậu chỉ là code dạo, chỉ như game thì cậu phải tự biết từ trình độ đến nghiên cứu về IT cậu đã học và đã đạt được cái gì và đến đâu. Vậy hãy thận trọng khi nói về những vấn đề mà mình đang đọc còn chưa hiểu nhé.

Ta lại quay về câu hỏi để cậu thể hiện bản lĩnh nhé. Hãy ghi và đọc file tôi gửi ở bài #84.
 
Lần chỉnh sửa cuối:
Em thấy các bác viết nhiều, dài dòng quá, cơ bản cái món này em cũng không hiểu lắm!
Em chỉ quan tâm 2 câu hỏi về bảo mật:
Câu 1: Là có dễ truy cập mà không cần ủy quyền không?(Không lẽ bảo mật của Google lại kém vậy?)
Câu 2: Nếu bị truy cập không cần ủy quền thì làm thế nào để bảo mật được?
https://docs.google.com/spreadsheets/d/1D5qfpgwQRonbttXjVbjPZwWvEUYhHVKB/edit
Các bác nói truy cập được mà không ủy quyền thì người dùng(trong đó có em) khá là lo lắng lộ thông tin bảo mật.
 
Em thấy các bác viết nhiều, dài dòng quá, cơ bản cái món này em cũng không hiểu lắm!
Em chỉ quan tâm 2 câu hỏi về bảo mật:
Câu 1: Là có dễ truy cập mà không cần ủy quyền không?(Không lẽ bảo mật của Google lại kém vậy?)
Câu 2: Nếu bị truy cập không cần ủy quền thì làm thế nào để bảo mật được?
https://docs.google.com/spreadsheets/d/1D5qfpgwQRonbttXjVbjPZwWvEUYhHVKB/edit
Các bác nói truy cập được mà không ủy quyền thì người dùng(trong đó có em) khá là lo lắng lộ thông tin bảo mật.

Bạn yên tâm là không thể nhé. Vì những tuyên bố của cậu ấy nên tôi mới đề nghị làm thử ở bài ở bài #84 đây. Nếu không làm sáng tỏ vụ này thì nhiều người khác cũng như bạn vô tình đọc được những nội dung đó rồi hoài nghi Google thì oan cho họ quá.
 
Em thấy các bác viết nhiều, dài dòng quá, cơ bản cái món này em cũng không hiểu lắm!
Em chỉ quan tâm 2 câu hỏi về bảo mật:
Câu 1: Là có dễ truy cập mà không cần ủy quyền không?(Không lẽ bảo mật của Google lại kém vậy?)
Câu 2: Nếu bị truy cập không cần ủy quền thì làm thế nào để bảo mật được?
https://docs.google.com/spreadsheets/d/1D5qfpgwQRonbttXjVbjPZwWvEUYhHVKB/edit
Các bác nói truy cập được mà không ủy quyền thì người dùng(trong đó có em) khá là lo lắng lộ thông tin bảo mật.
có hai kiểu tách bạch rõ ràng

1/ Người dùng tự đăng nhập lấy mã xác thực uỷ quyền

2/ Uỷ toàn quyền cho bên thứ 3 làm mọi việc

hai cái khác biệt nhau hoàn toàn ... bài trước Tôi cho link rảnh xem cơ bản các bài đó xong tự làm lấy viết khoãng trên dưới 100 dòng code
là bạn sử dụng đọc và ghi dữ liệu vào google Sheets OK ... không cần thiết sử dụng Module phân tích JSon của tây có trên 1000 dòng code

vì khi lỗi ai có khả năng dò nó với lại tự viết lấy mấy cái cần thiết chỉ trên 10 dòng code là xong
 
có hai kiểu tách bạch rõ ràng

1/ Người dùng tự đăng nhập lấy mã xác thực uỷ quyền

2/ Uỷ toàn quyền cho bên thứ 3 làm mọi việc

hai cái khác biệt nhau hoàn toàn ... bài trước Tôi cho link rảnh xem cơ bản các bài đó xong tự làm lấy viết khoãng trên dưới 100 dòng code
là bạn sử dụng đọc và ghi dữ liệu vào google Sheets OK ... không cần thiết sử dụng Module phân tích JSon của tây có trên 1000 dòng code

vì khi lỗi ai có khả năng dò nó với lại tự viết lấy mấy cái cần thiết chỉ trên 10 dòng code là xong
Nói chung là vẫn phải ủy quyền mà bác! Bác làm em và một số người hiểu lầm bảo mật Google kém nên lo lắng. Bác chỉ cần không gây hiểu lầm cho một bộ phận những người không hiểu rõ là được. Còn bác trình diễn kỹ thuật thì ai rảnh thì nghiên cứu học hỏi thôi, chứ bác đừng chê bai, góp ý sai mục đích gây hiểm lầm thì nguy hiểm lắm! Như em không hiểu cũng đang hoang mang lộ thông tin bảo mật thì chết!
 
Việc dùng HTTP Request, trả kết quả về là JSON, HTML trên đây (diễn đàn này thôi) đã có quá trời bài hướng dẫn lấy dữ liệu web từ hơn 5 năm trước rồi. Còn code chuyển đổi Json thì cũng có một đống trên mạng viết trên nhiều loại ngôn ngữ khác nhau. Dân chuyên tương tác với web thấy nó là công cụ phổ thông rồi, còn qua miệng ông KM thì nó như là cái gì ghê gớm lắm, một cái đột phá trong làng code …haha…( chắc do mới tiếp xúc với web)
 
Việc dùng HTTP Request, trả kết quả về là JSON, HTML trên đây (diễn đàn này thôi) đã có quá trời bài hướng dẫn lấy dữ liệu web từ hơn 5 năm trước rồi. Còn code chuyển đổi Json thì cũng có một đống trên mạng viết trên nhiều loại ngôn ngữ khác nhau. Dân chuyên tương tác với web thấy nó là công cụ phổ thông rồi, còn qua miệng ông KM thì nó như là cái gì ghê gớm lắm, một cái đột phá trong làng code …haha…( chắc do mới tiếp xúc với web)
đầu toàn bã đậu lại trỏ mõm vào rồi he .... tôi nói rất rõ ràng rồi còn gì ... nổ khúc nào ???
Bài đã được tự động gộp:

cứ cho là ồn ào người dùng tự làm xác thực uỷ quyền và uỷ toàn quyền cho bên thứ 3 làm mọi việc từ A To Z đi .... dẹp khúc này ra một bên

Vậy tại sao không Viết một hàm ghi dữ liệu lên Google Sheet Với cấu trúc như sau:

TenHam(AccessToken, ApiKeys, SheetID, .... Sheetname) ... xong ghi dự liệu lên Google Sheet là xong

mà cứ phải trỏ mõm đòi uỷ toàn quyền riêng tư dữ liệu cá nhân của người dùng xong mới sử dụng được

1/ Người dùng viết mã VBA hay ta cho họ code khoãng 40 dòng là xong ( tạo AccessToken )

2/ ta viết hàm như mô tả trên hổ trợ người sử dụng ( Mục này viết trên VBA khoãng 20 to 30 dòng code là xong )

3/ dẹp cái Module phân tích JSON của tây trên 1000 dòng code vào thùng rác

đơn giản vậy tại sao cứ trỏ mõm đòi toàn quyền riêng tư của người dùng ???!!!!
 
Lần chỉnh sửa cuối:
Haha… chắc là chưa hiểu được vấn đề rồiiii…Thường thì tranh luận không lại hay chửi bậy, cắn bậy lắm… hai zaaa.
chỉ có khoãng trên 100 dòng code thuần trên VBA viết là đọc và ghi dữ liệu lên Google Sheets OK sạch còn không biết viết

Não toàn bã đậu còn bày đặt trỏ mõm
 
Em có một vài ý kiến cá nhân như này ạ:
1. Không nên công kích cá nhân.
2. Việc tác giả viết "JSON Converter for VBA" để Các hàm JSON parsing/serializing có cú pháp đơn giản và dễ tiếp cận cho người dùng VBA. Nó áp dụng cho nhiều trường hợp khác nhau nên code nó dài là đúng thôi. Ví dụ ta cần phép tính 100*101 trong bảng tính thì tính thì tính nhẩm hoặc dùng máy tính bỏ túi casio cũng được, nhưng nếu dùng Excel cũng chẳng sao. Không vì thế mà nói Excel là quá nặng và vớ vẩn!
 
chỉ có khoãng trên 100 dòng code thuần trên VBA viết là đọc và ghi dữ liệu lên Google Sheets OK sạch còn không biết viết

Não toàn bã đậu còn bày đặt trỏ mõm
Đừng chơi chiêu khích tướng chứ! Để người ta xì code ra rồi học lóm à…
Bài đã được tự động gộp:

Em có một vài ý kiến cá nhân như này ạ:
1. Không nên công kích cá nhân.
2. Việc tác giả viết "JSON Converter for VBA" để Các hàm JSON parsing/serializing có cú pháp đơn giản và dễ tiếp cận cho người dùng VBA. Nó áp dụng cho nhiều trường hợp khác nhau nên code nó dài là đúng thôi. Ví dụ ta cần phép tính 100*101 trong bảng tính thì tính thì tính nhẩm hoặc dùng máy tính bỏ túi casio cũng được, nhưng nếu dùng Excel cũng chẳng sao. Không vì thế mà nói Excel là quá nặng và vớ vẩn!
Những tay tầm nhìn hạn hẹp thường hay ra dzẻ lắm bạn à…
 
Status
Không mở trả lời sau này.

Bài viết mới nhất

Back
Top Bottom