Website does not recognize my inputs [how to fire

2019-01-23 14:24发布

问题:

I woud like to buy on gdax automatically. But my inputs in the Amount window doesn´t get recognized. I can see that on the little field, that says: Total (LTC) ≈ 0.00000000

My code:

Sub test()

    Dim ObjIE As New InternetExplorer
    Dim Ohtml As HTMLDocument
    Dim HTMLtags As IHTMLElementCollection
    Dim HTMLtag As IHTMLElement
    Dim HTMLobjekt As IHTMLElement
    Dim item_limit As Object
    Dim y As Integer

    With ObjIE
        .Visible = True
        .navigate "https://www.gdax.com/trade/LTC-EUR"
        Do Until .readyState = READYSTATE_COMPLETE: Loop
        Set Ohtml = .document
    End With

    'Amount
    Do
        Set HTMLtags = Ohtml.getElementsByClassName("OrderForm_input-box_XkGmi")
        DoEvents
    Loop While HTMLtags.Length = 0
    For Each HTMLtag In HTMLtags
        If HTMLtag.Children(1).innerText = "EUR" Then
            Set HTMLobjekt = HTMLtag.Children(0)
            HTMLobjekt.Value = 100      ' this is the part that i excanged
        End If
    Next HTMLtag

    'get the Total(LTC) to cross check
    Do
        Set HTMLtags = Ohtml.getElementsByClassName("OrderForm_total_6EL8d")
        DoEvents
    Loop While HTMLtags.Length = 0
    For Each HTMLtag In HTMLtags
        Debug.Print HTMLtag.innerText & "Total(LTC)"
    Next HTMLtag

End Sub

This is what the website says when the code is done:

and this is how it should look like, and looks when I type the number in manually:

I also exchange the marked part with things like:

HTMLobjekt.innerText = 100

or

HTMLobjekt.innerText = "100"

or

HTMLobjekt.Value = "100"

but nothing worked.

回答1:

You need to make sure the page is fully loaded before you take any initiative. I checked for the availability of one such class generated dynamically OrderBookPanel_text_33dbp. Then I did the rest what you tried to do. Lastly, you need to Focus the target before putting that amount in the placeholder. Applying all of the above, your script should more like below:

Sub Get_Value()
    Dim HTML As HTMLDocument, tags As Object
    Dim tag As Object, ival As Object

    With CreateObject("InternetExplorer.Application")
        .Visible = True
        .navigate "https://www.gdax.com/trade/LTC-EUR"
        While .Busy = True Or .readyState < 4: DoEvents: Wend
        Set HTML = .document

        Do: Set tags = HTML.querySelector("[class^='OrderBookPanel_text_']"): DoEvents: Loop While tags Is Nothing
        Do: Set tag = HTML.querySelector("input[name='amount']"): DoEvents: Loop While tag Is Nothing
        tag.Focus
        tag.innerText = 100

        Set ival = HTML.querySelector("[class^='OrderForm_total_']")
        [A1] = ival.innerText
        .Quit
    End With
End Sub

Output at this moment:

0.79139546


回答2:

Not a complete answer, just a direction. Open the webpage https://www.gdax.com/trade/LTC-EUR in a browser (e. g. Chrome). Click to open context menu on the target element (step 1 on the below screenshot), click Inspect (2), from opened developer tools on the right you can see target element (3), and that there is a bunch of event listeners attached to target object (4). One of the handlers is input on the document node level. Actually the amount is updated by this event handler, when event is bubbled from <input> node. You can easily check that by deleting all other event handlers (click on their Remove buttons). But if you delete particularly this input handler (5) then there will be no update (until you reload the webpage).

So, to mimic user activity you need to create such input event object and dispatch it to the target <input> node. Note <input> node and input control event is completely different things just having the same name.

Tracing event handler execution in IE can be done in debugger. Open IE developer tools (press F12), go to Debugger tab (step 1 on the below screenshot), Breakpoints tab (2), click Add event breakpoint (3), choose input event (4). Now any input event will pause code execution and open debugger window (5).

If you try to type in the amount textbox on the webpage, debugger will show event handler function code:

You may resume execution (F5), or make step into / over / out. To see event object passed to the function, type arguments in console, there is output for standard event handler call after typing manually:

I tested the below test code to trigger that event from VBA:

Sub Test()

    Dim oEvt As Object

    With CreateObject("InternetExplorer.Application")
        .Visible = True
        .Navigate "https://www.gdax.com/trade/LTC-EUR"
        Do While .ReadyState < 4 Or .Busy
            DoEvents
        Loop
        With .Document
            Do While IsNull(.querySelector("div.OrderForm_input-box_XkGmi input"))
                DoEvents
            Loop
            Set oEvt = .createEvent("Event")
            oEvt.initEvent "input", True, False, .parentWindow, 1
            With .querySelector("div.OrderForm_input-box_XkGmi input")
                .Focus
                .Value = "1000"
                .dispatchEvent oEvt
                Stop
            End With
        End With
    End With

End Sub

As a result, absolutely the same actions take place as in case of typing the amount manually. It works without any errors. But Total (LTC) isn't updated. Debugger pauses execution as well, and the output for event object is as follows:

The only difference is that isTrusted property is True for standard call, and False for call via VBA. I guess that is why updating is skipped somewhere in the handler code. I tried to trace the entire code execution till event handler completion, but seems that is huge reverse engineering work, which I can't devote time for at the moment.

Currently I'm stucked in my investigations at that point.



回答3:

Sorry could not get to work but have nudged along the problem.

I managed to figure out the correct syntax for creating and raising an event in higher versions of Internet Explorer (thanks to this SO answer ) . I managed to raise an input event for the input box, that followed on from omegastripes excellent lead.

So it works for a local file which I give below that has the same essential structure of the real page.

But when I run the code on the real page it simply does not work and I do not know why.

Where possible I'm feeding javascript into IE for it to execute to miminise issues with the VBA/COM/MSHTML interoperability bridge. I do this by calling window.execScript passing the javascript as a string.

I hope someone can pick up this ball and run it over the goal line. For me, I have reached the end.

Here is the local html file

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <input id="Button1" type="button" value="Programmatically write textbox value" onclick="TestAlert()" />

    <form class="OrderForm_form_25r0u">
        <ul class="OrderForm_trade-type_2QyK4">
            <li class="OrderForm_trade-type-tab_uWGMp OrderForm_active_Di-9p">MARKET</li>
            <li class="OrderForm_trade-type-tab_uWGMp">LIMIT</li>
            <li class="OrderForm_trade-type-tab_uWGMp">STOP</li>
        </ul>
        <ul class="OrderForm_toggle_120Ka">
            <li class="OrderForm_toggle-tab_bZZnC OrderForm_buy_38n5g OrderForm_active_Di-9p">BUY</li>
            <li class="OrderForm_toggle-tab_bZZnC OrderForm_sell_3vYRQ">SELL</li>
        </ul>
        <div class="market-order">
            <div class="OrderForm_section_2Znad">
                <div class="OrderForm_section-header_fwFDB">Amount</div>
                <div class="OrderForm_input-box_XkGmi">
                    <input type="number" step="0.01" min="0" name="amount" placeholder="0.00" value="" autocomplete="off" oninput="myOnInputHandler()">
                    <span>EUR</span>
                </div>
            </div>
        </div>
        <div class="OrderForm_order-total_3Mkdz">
            <div>
                <b>Total</b>
                <span>(LTC)</span>
                <b>≈</b>
            </div>
            <div class="OrderForm_total_6EL8d" >0.00000000</div>
        </div>
    </form>

    <script language="javascript">
        function myOnInputHandler() {
            print_call_stack();
            alert('you input something');
        }

        function print_call_stack() { console.trace(); }

        function print_call_stack2() {
            var stack = new Error().stack;
            console.log("PRINTING CALL STACK");
            console.log(stack);
        }


        function TestAlert() { setInputBox(document); }

        function setInputBox() {
            try {
                var inputBox = document.querySelector('div.OrderForm_input-box_XkGmi input'); inputBox.value = 100; if (document.createEvent) { var event2 = document.createEvent("HTMLEvents"); event2.initEvent("input", true, true); event2.eventName = "input"; inputBox.dispatchEvent(event2); }

                return ({ success: true });
            }
            catch (ex) {
                return ({ exception: ex, myMsg: '#error in setInputBox!' });
            }
        }

    </script>
</body>
</html>

Here is the VBA

Option Explicit

'* Tools - References
'*      MSHTML      Microsoft HTML Object Library                   C:\Windows\SysWOW64\mshtml.tlb
'*      SHDocVw     Microsoft Internet Controls                     C:\Windows\SysWOW64\ieframe.dll
'*      Shell32     Microsoft Shell Controls And Automation         C:\Windows\SysWOW64\shell32.dll


Private Function ReacquireInternetExplorer(ByVal sMatch As String) As Object
    Dim oShell As Shell32.Shell: Set oShell = New Shell32.Shell
    Dim wins As Object: Set wins = oShell.Windows
    Dim winLoop As Variant
    For Each winLoop In oShell.Windows
        If "C:\Program Files (x86)\Internet Explorer\IEXPLORE.EXE" = winLoop.FullName Then

            Dim sFile2 As String
            sFile2 = "file:///" & VBA.Replace(sMatch, "\", "/")
            If StrComp(sFile2, winLoop.LocationURL, vbTextCompare) = 0 Then
                Set ReacquireInternetExplorer = winLoop.Application
                GoTo SingleExit
            End If
        End If
    Next
SingleExit:
End Function

Sub test()

    Dim objIE As InternetExplorer
    Set objIE = New InternetExplorer
    Dim oHtml As HTMLDocument
    Dim HTMLtags As IHTMLElementCollection


    Dim sUrl As String
    sUrl = "C:\Users\Simon\source\repos\WebApplication2\WebApplication2\HtmlPage1.html"
    'sUrl = "http://localhost:50367/HtmlPage1.html"
    sUrl = "https://www.gdax.com/trade/LTC-EUR"

    objIE.Visible = True
    objIE.Navigate sUrl

    If StrComp(Left(sUrl, 3), "C:\") = 0 Then
        Stop '* give chance to clear the activex warning box for the local file
        Set objIE = ReacquireInternetExplorer(sUrl)
    End If
    Do Until objIE.readyState = READYSTATE_COMPLETE: DoEvents: Loop
    Set oHtml = objIE.Document

    Do
        '* wait for the input box to be ready
        Set HTMLtags = oHtml.getElementsByClassName("OrderForm_input-box_XkGmi")
        DoEvents
    Loop While HTMLtags.Length = 0

    Dim objWindow As MSHTML.HTMLWindow2
    Set objWindow = objIE.Document.parentWindow


    '* next line would be really useful if it worked because it prints the stack trace in the console window
    '* and we would be able to see the needle in the haystack where te order total gets updated
    '* works on local file but not on https://www.gdax.com/trade/LTC-EUR  **sigh**
    objWindow.execScript "var divTotal = document.querySelector('div.OrderForm_total_6EL8d'); divTotal.onchange = function() { console.trace(); }"


    '* next line sets the input box and raises an event, works on local file but not on GDAX
    objWindow.execScript "var inputBox = document.querySelector('div.OrderForm_input-box_XkGmi input'); inputBox.value = 100; if (document.createEvent) { var event2 = document.createEvent('HTMLEvents'); event2.initEvent('input', false, false); event2.eventName = 'input'; inputBox.dispatchEvent(event2);  }"


    'get the Total(LTC) to cross check
    Do
        '* wait for the order total div to be ready
        Set HTMLtags = oHtml.getElementsByClassName("OrderForm_total_6EL8d")
        DoEvents
    Loop While HTMLtags.Length = 0

    Dim divTotal As HTMLDivElement
    Set divTotal = oHtml.querySelector("div.OrderForm_total_6EL8d")
    Debug.Print divTotal.innerText & " Total(LTC)"

    Stop

End Sub