VBA web scraping - change date of a calendar

前端 未结 1 1342
攒了一身酷
攒了一身酷 2020-12-22 09:28

I would like to write a vba Programm which downloads automaticaly historical stock data from a web-page. Selecting the data and click on the download button is already worki

相关标签:
1条回答
  • 2020-12-22 10:10

    Try the following. It essentially clicks the dropdowns for year and month and selects the appropriate item by itemindex attribute value.

    In the case of year, depending on what is currently displayed, the desired year may not be in the drop down. The code uses a fixed number of clicks of the <> buttons to determine if the desired year can be found. This number could be set as a constant, at the top of the code, and altered there if required.

    To select day, the collection of days is looped, and if the desired day value found then it is selected.

    Javascript is used to wait for certain elements to become clickable, as well as a timed loop for a drop down to appear. This times events according to when they are actionable in order to yield desired results.

    Option Explicit
    Public Sub MakeChanges()
        'VBE > Tools > References > Selenium Type Library
        'Download: https://github.com/florentbr/SeleniumBasic/releases/tag/v2.0.9.0
        Const url = "https://www.dukascopy.com/swiss/english/marketwatch/historical/"
        Const MAX_WAIT_SEC As Long = 10
        Const JS_WAIT_CLICKABLE = _
        "var target = this, endtime = Date.now() + arguments[0];" & _
        "(function check_clickable() {" & _
        "  var r = target.getBoundingClientRect(), x = r.left+r.width/2, y = r.top+r.height/2;" & _
        "  for (var e = document.elementFromPoint(x , y); e; e = e.parentElement)" & _
        "    if (e === target){ callback(target); return; }" & _
        "  if (Date.now() > endtime) { callback(target); return; }" & _
        "  setTimeout(check_clickable, 60);" & _
        "})();"                                      'by @florentbr
    
    
    
        Dim d As WebDriver, t As Date
        Dim myYear As String, myMonth As String, myDay As String
    
        Set d = New ChromeDriver
        myYear = "2017"
        myMonth = "January"
        myDay = "1"
    
        With d
            .start "Chrome"
            .get url
            .SwitchToFrame .FindElementByCss("script + iframe") '<==switch to frame
    
            'You should add tests for acceptable values e.g. January-December for MonthName, day as appropriate for month
            Dim monthIndex As Long, yearIndex As Long, item As Object, dropDown As Object
    
            monthIndex = Month(DateValue("01 " & myMonth & " 2019")) - 1  '<== get month number from name and -1 to get value to use in attribute selector
            t = Timer
            Do                                       '<== timed loop for month dropdown to be present
                On Error Resume Next
                Set dropDown = .FindElementByCss(".d-wh-vg-xh span span")
                On Error GoTo 0
                If Timer - t > MAX_WAIT_SEC Then Exit Do
            Loop While dropDown Is Nothing
    
            If dropDown Is Nothing Then Exit Sub
    
            With dropDown                            '<== wait for drop down to be clickable
                .ExecuteAsyncScript(JS_WAIT_CLICKABLE, 3000) _
                .Click
            End With
    
            With .FindElementByCss(".d-Ch-fi-mi")
                .ExecuteAsyncScript(JS_WAIT_CLICKABLE, 3000) _
                .Click   '<== display month dropdown
            End With
            .FindElementByCss(".d-Ch-fi-u [itemindex='" & monthIndex & "']").Click '<select month by index
    
            Dim yearIndices As Object, i As Long, j As Long, currentYear As String, z As Long, dayFound As Boolean
    
            currentYear = .FindElementByCss(".d-Ch-fi-ni").Text '<= find currently displayed year
    
            Set yearIndices = CreateObject("Scripting.Dictionary")
    
            For i = CLng(currentYear) - 5 To CLng(currentYear) + 5 '<== gather range of year options in dropdown into
                'dictionary where key is year and value is the value required to select in attribute selector
                yearIndices(CStr(i)) = CStr(j)
                j = j + 1
            Next
    
            If yearIndices.Exists(myYear) Then '<check dictionary to see if year desired present
                yearIndex = yearIndices(myYear)
                .FindElementByCss(".d-Ch-fi-ni").Click  '<== display year dropdown
                .FindElementByCss("div:nth-child(11) [itemindex='" & yearIndex & "']").Click  '<==select year
            Else '<== year not present so loop clicking either year add or year subtract to see if desired year does become present
                Dim adjustButton As Object
                Set adjustButton = IIf(CLng(currentYear) > CLng(myYear),.FindElementByCss("d-Ch-fi-previousYear"), .FindElementByCss("d-Ch-fi-nextYear")) 
                Do
                    adjustButton.Click
                    If z > 15 Then Exit Sub
                    z = z + 1
               Loop Until .FindElementByCss(".d-Ch-fi-ni").Text = myYear
    
            End If
    
            Dim daysList As Object
            Set daysList = .FindElementsByCss("div:nth-child(11) td") '<==gather all the days
    
            For Each item In daysList                '<==loop days in month until required one found
                If item.Text = myDay Then
                    item.Click
                    Exit For
                End If
            Next
            Stop
            .Quit
        End With
    End Sub
    
    0 讨论(0)
提交回复
热议问题