简体   繁体   English

Excel VBA 麻烦填写 web 文本框

[英]Excel VBA trouble filling in web textbox

I am trying to enter text into a textbox on a webpage.我正在尝试在网页上的文本框中输入文本。

<div lightning-input_input="" class="slds-form-element__control slds-grow">
<input lightning-input_input="" type="text" id="input-32" placeholder="Enter an address, city, zip, or place" class="slds-input">
</div>

I am using this line in my excel macro to try to pull enter "test" in that box:我在我的 excel 宏中使用这一行来尝试在该框中输入“测试”:

Application.Wait DateAdd("s", 5, Now)
objIE.document.all.item("input-32")(0).Value="test"

This code works for other sites but I can't figure out why it doesn't work for the object above.此代码适用于其他网站,但我不知道为什么它不适用于上面的 object。

Full code:完整代码:

sub searchbot()
 Dim objIE as InternetExplorer
 Dim aEle as IHTMLElement
 
 Set objIE = New InternetExplorer
 objIE.visible = True
 objIE.navigate "https://myturnvolunteer.ca.gov/s/#search"

 Do While objIE.Busy = True or objIE.readyState <>4: DoEvents:  Loop
 'That doesnt seem to wait long enough so
 Application.Wait DateAdd("s", 5, Now)

 objIE.document.all.Item("radioButtonGroup")(1).Click  'this works!
 Set device = objIE.document.getElementsByClassName("input-slds") 'Error!
 device(0).Value = "test"

End Sub

After navigating to your webpage and waiting for it to load, if you run this line of code:导航到您的网页并等待它加载后,如果您运行这行代码:

Debug.Print TypeName(IE.Document.getElementsByClassName("slds-form"))

You will see that in the Immediate Window you get something like JScriptTypeInfo when in fact you were expecting a DispHTMLElementCollection .您会看到,在Immediate Window中,您会得到类似JScriptTypeInfo的内容,而实际上您期望的是DispHTMLElementCollection

To fix this you will need to add a reference to Microsoft HTML Object Library .要解决此问题,您需要添加对Microsoft HTML Object Library的引用。 If you don't have it in the list then simply browse for mshtml.tlb type library:如果列表中没有它,则只需浏览mshtml.tlb类型库:
在此处输入图像描述

Now the above line of code could become:现在上面的代码行可以变成:

Dim doc As HTMLDocument
Set doc = IE.Document
    
Debug.Print TypeName(doc.getElementsByClassName("slds-form"))

which now prints DispHTMLElementCollection correctly to the immediate window.现在DispHTMLElementCollection正确打印到立即 window。

If you use the doc variable, all the functionality like doc.getElementsByClassName or doc.getElementById will work.如果您使用doc变量,则doc.getElementsByClassNamedoc.getElementById等所有功能都将起作用。

Last thing that needs fixing is the waiting.最后需要修复的是等待。 There are at least 4 reasons why Do While objIE.Busy = True or objIE.readyState <>4: DoEvents: Loop does not work: Do While objIE.Busy = True or objIE.readyState <>4: DoEvents: Loop不起作用至少有 4 个原因:

  1. Immediately after navigating and waiting, a script can be triggered that forces the browser to be busy again so we need to wait again在导航和等待之后,可以立即触发一个脚本,迫使浏览器再次忙碌,所以我们需要再次等待
  2. The document itself needs to be checked for readyState文档本身需要检查readyState
  3. The IE Object can get disconnected IE Object 可以断开连接
  4. The IE Object does not get updated after certain actions IE Object 在某些操作后没有得到更新

To fix this, just drop the following code into a standard code module.要解决此问题,只需将以下代码放入标准代码模块中。 Call the module LibIE as it will act as a supporting library:调用模块LibIE ,因为它将充当支持库:

Option Explicit
Option Private Module

Public Enum IEFlags
    navOpenInNewWindow = 1
    navNoHistory = 2
    navNoReadFromCache = 4
    navNoWriteToCache = 8
    navAllowAutosearch = 16
    navBrowserBar = 32
    navHyperlink = 64
    navEnforceRestricted = 128
    navNewWindowsManaged = 256
    navUntrustedForDownload = 512
    navTrustedForActiveX = 1024
    navOpenInNewTab = 2048
    navOpenInBackgroundTab = 4096
    navKeepWordWheelText = 8192
    navVirtualTab = 16384
    navBlockRedirectsXDomain = 32768
    navOpenNewForegroundTab = 65536
End Enum

#If VBA7 Then
    Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else
    Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If

'Creates an instance of InternetExplorer
Public Function CreateIEBrowser(Optional ByVal mediumSecurity As Boolean = False) As InternetExplorer
    Const maxLoops As Long = 1000
    '
    Dim IE As InternetExplorer
    Dim loopsCount As Long
    '
    'If there is another instance of IE that is trying to shut down, then a new
    '   instance cannot get created and a -2147023706 error is thrown:
    '   "A system shutdown has already been scheduled. Automation Error"
    'If a new instance is not created then loop and wait/pause between tries
    On Error Resume Next
    Do While loopsCount < maxLoops And IE Is Nothing
        If mediumSecurity Then
            Set IE = New InternetExplorerMedium
            'If the library reference is missing then use (late binding):
            'Set IE = CreateObject("new:{D5E8041D-920F-45e9-B8FB-B1DEB82C6E5E}")
        Else
            Set IE = New InternetExplorer
            'If the library reference is missing then use (late binding):
            'Set IE = CreateObject("InternetExplorer.Application")
        End If
        loopsCount = loopsCount + 1
        Sleep 10
    Loop
    On Error GoTo 0
    '
    Set CreateIEBrowser = IE
End Function

'Check if IE got disconnected
Public Function IsIEDisconnected(ByVal IE As InternetExplorer) As Boolean
    IsIEDisconnected = (IE Is Nothing) Or (TypeName(IE) = "Object")
End Function

'Waits for an IE browser to be idle
Public Sub WaitIE(ByVal IE As InternetExplorer _
                , Optional ByVal timeoutSeconds As Long = 60 _
)
    If IsIEDisconnected(IE) Then Exit Sub
    If timeoutSeconds < 0 Then timeoutSeconds = 0
    '
    Const waitMilliPerLoop As Long = 10
    Dim maxTotalLoops As Long
    Dim maxInnerLoops As Long
    Dim innerLoopsCount As Long
    Dim outerLoopsCount As Long
    '
    maxTotalLoops = timeoutSeconds * 1000 / waitMilliPerLoop
    maxInnerLoops = maxTotalLoops / 10
    '
    #If VBA7 Then
        Dim storedHandle As LongPtr
    #Else
        Dim storedHandle As Long
    #End If
    '
    'Although the browser may look like it's not busy anymore and the state is
    '   "Complete", it might happen that the page must trigger a script
    'Thus, two loops are required:
    '   - an inner loop to track if IE is busy and ready state is complete
    '     while making sure it times-out after a pre-defined number of loops
    '   - an outer loop which runs the inner loop and then pauses for a few
    '     milliseconds (to allow the scripts on page to fire) and checks the IE
    '     status again
    storedHandle = IE.hwnd
    Do While (IE.Busy Or IE.ReadyState <> READYSTATE_COMPLETE _
    ) And outerLoopsCount < maxTotalLoops
        innerLoopsCount = 0
        Do While (IE.Busy Or IE.ReadyState <> READYSTATE_COMPLETE) _
        And innerLoopsCount < maxInnerLoops
            Sleep waitMilliPerLoop
            innerLoopsCount = innerLoopsCount + 1
            Set IE = GetIEByHandle(storedHandle)
        Loop
        outerLoopsCount = outerLoopsCount + innerLoopsCount
        Set IE = GetIEByHandle(storedHandle)
    Loop
    Do While IE.Document.ReadyState <> READYSTATE_COMPLETE _
    And outerLoopsCount < maxTotalLoops
        Sleep waitMilliPerLoop
        outerLoopsCount = outerLoopsCount + innerLoopsCount
        Set IE = GetIEByHandle(storedHandle)
    Loop
End Sub

'Returns an Internet Explorer object by providing the window handle
'   (if the handle exists in the collection of opened shell windows)
#If VBA7 Then
Public Function GetIEByHandle(ByVal hwnd As LongPtr) As InternetExplorer
#Else
Public Function GetIEByHandle(ByVal hwnd As Long) As InternetExplorer
#End If
    If hwnd = 0 Then Exit Function
    '
    Dim tempObj As Object
    Dim IE As InternetExplorer
    '
    On Error Resume Next
    For Each tempObj In GetShellWindows()
        If tempObj.hwnd = hwnd Then
            Set IE = tempObj
            Exit For
        End If
    Next tempObj
    On Error GoTo 0
    '
    Set GetIEByHandle = IE
End Function

Private Function GetShellWindows() As ShellWindows
    Const maxLoops As Long = 1000
    '
    Dim collShellWindows As ShellWindows
    Dim loopsCount As Long
    '
    On Error Resume Next
    Do While loopsCount < maxLoops
        Set collShellWindows = New ShellWindows
        If Not collShellWindows Is Nothing Then
            If collShellWindows.Count > 0 Then Exit Do
        End If
        loopsCount = loopsCount + 1
        Sleep 1
    Loop
    On Error GoTo 0
    Set GetShellWindows = collShellWindows
End Function

'Returns the first found opened Internet Explorer instance
Public Function GetOpenedIE() As InternetExplorer
    Const maxLoops As Long = 1000
    '
    Dim tempObj As Object
    Dim IE As InternetExplorer
    '
    On Error Resume Next
    For Each tempObj In GetShellWindows()
        If tempObj.Name = "Internet Explorer" Then
            Set IE = tempObj
            Exit For
        End If
    Next tempObj
    On Error GoTo 0
    '
    Set GetOpenedIE = IE
End Function

'Navigate a URL inside a specific InternetExplorer instance
Public Sub NavigateUrl(ByVal IE As InternetExplorer _
                     , ByVal Url As String _
                     , ByVal flags As IEFlags _
                     , Optional ByVal postData As Variant _
                     , Optional ByVal headers As Variant _
)
    If IsIEDisconnected(IE) Then Exit Sub
    '
    #If VBA7 Then
        Dim storedHandle As LongPtr
    #Else
        Dim storedHandle As Long
    #End If
    '
    'The Navigate command (depending on configuration and IE security) causes the
    '   IE object to lose the reference to the actual instance of InternetExplorer
    storedHandle = IE.hwnd
    '
    IE.Navigate Url:=Url, flags:=flags, postData:=postData, headers:=headers
    Sleep 10
    '
    'Please note that the initial window might have been destroyed
    '   and a new one created (with a new handle) which requires a different approach,
    '   like storing a collection of window handles from ShellWindows collection
    '   (before Navigate command) and comparing them with the handles after the
    '   Navigate command. Not implemented
    Set IE = GetIEByHandle(storedHandle)
End Sub

Here is a demo method that uses the above LibIE library:下面是一个使用上述LibIE库的演示方法:

Option Explicit

Public Declare PtrSafe Function ShowWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal nCmdShow As Long) As Long
Const SW_MAXIMIZE As Long = 3

Sub Demo()
    Dim IE As InternetExplorer
    Dim flags As IEFlags: flags = navNoHistory + navNoReadFromCache + navNoWriteToCache 'Or whatever you need
    '
    On Error GoTo ErrorHandler
    Set IE = LibIE.CreateIEBrowser(mediumSecurity:=False)
    '
    'Maybe try medium security settings
    'If IE Is Nothing Then Set IE = LibIE.CreateIEBrowser(mediumSecurity:=True) 'Uncomment if needed!
    '
    'Maybe get an already opened instance
    'If IE Is Nothing Then Set IE = LibIE.GetOpenedIE() 'Uncomment if needed!
    '
    If IE Is Nothing Then
        MsgBox "Cannot create IE"
        Exit Sub
    End If
    '
    IE.Visible = True
    IE.Silent = True
    '
    'Maybe Maximize
    'ShowWindow IE.hwnd, SW_MAXIMIZE 'Uncomment if needed!
    '
    LibIE.NavigateUrl IE, "https://myturnvolunteer.ca.gov/s/#search", flags
    LibIE.WaitIE IE
    '
    Dim doc As HTMLDocument
    Set doc = IE.Document
    '
    With doc.getElementsByClassName("slds-form")(0)
        .elements("input-13").Value = "MyFirstName"
        .elements("input-14").Value = "MyLastName"
        .elements("input-15").Value = "MyZipCode"
        .elements("input-16").Value = "MyEMail"
        .elements("input-17").Value = "MyPhone"
        .elements("agreedToTermsConditions").Checked = True
    End With
    '
    Stop 'Go and inpect the results in the browser!
Clean:
    If Not LibIE.IsIEDisconnected(IE) Then IE.Quit
Exit Sub
ErrorHandler:
    Resume Clean
End Sub

I've added some extra lines that you can uncomment for getting an already opened IE browser or maximizing the IE window.我添加了一些额外的行,您可以取消注释以获得已打开的 IE 浏览器或最大化 IE window。

Sub searchbot()
   Dim objIE As InternetExplorer
   Dim aEle As IHTMLElement
   Set objIE = New InternetExplorer
   objIE.Visible = True
   objIE.navigate "https://myturnvolunteer.ca.gov/s/#search"

   Do While objIE.Busy = True Or objIE.readyState <> 4: DoEvents: Loop
   Application.Wait DateAdd("s", 7, Now)

   For i = 1 To objIE.Document.all.Length - 1
        itmName = objIE.Document.all.Item(i).ID
        Set target = objIE.Document.all.Item(i)
        
        Select Case itmName
        
            Case "input-13" 'First Name
            target.Value = "My First Name"
            
            Case "input-14" 'Last Name
            target.Value = "My Last Name"
        
            Case "input-15" 'Zip Code
            target.Value = "111111"
            
            Case "input-16" 'Mail
            target.Value = "mymail@gmail.com"
            
            Case "input-17" 'Phone
            target.Value = "111111"
        End Select
   Next i

End Sub

Yes.是的。 I'am out here;-)我在这里;-)

After the most ideas I had, I'am sure... This page isn't to automate or I'am completly incompetent.在我有最多的想法之后,我敢肯定......这个页面不是自动化的,或者我完全无能。 Another chance of my failure is that the page only works in Canada.我失败的另一个机会是该页面仅在加拿大有效。 Now I give up.现在我放弃了。

  1. When I try your script I run into an error.当我尝试你的脚本时,我遇到了一个错误。 You wrote it works for you but I already get an error in the line objIE.document.all.Item("radioButtonGroup")(1).Click你写的它对你有用,但我已经在objIE.document.all.Item("radioButtonGroup")(1).Click行中遇到错误
  2. I found out... Do While objIE.Busy = True or objIE.readyState <>4: DoEvents: Loop don't work for me.我发现... Do While objIE.Busy = True or objIE.readyState <>4: DoEvents: Loop对我不起作用。 Realy for you it does?真的适合你吗? You get the whole HTML code of the page at once?您一次获得页面的整个 HTML 代码?
  3. I replaced the code line with a loop until the body tag was found我用循环替换了代码行,直到找到body标签

This is what I got:这就是我得到的:

 <body class="null loading"> <div class="auraMsgBox auraLoadingBox" id="auraLoadingBox"> <div class="logo"></div> <div class="spinner"></div><span>Loading</span> </div> <div id="auraErrorMask"> <div class="auraErrorBox" id="auraError"><span><a class="close" id="dismissError">×</a>Sorry to interrupt</span> <div id="auraErrorMessage">CSS Error</div> <div id="auraErrorStack"></div> <div class="auraErrorFooter"><a id="auraErrorReload" href="?">Refresh</a></div> </div> </div> <script data-src="/jslibrary/1615500424000/canvas/CanvasRendering.js"></script> <script data-src="/jslibrary/1615500424000/ui-analytics-reporting/EclairNG.js"></script> <script src="/s/sfsites/l/%7B%22mode%22%3A%22PROD%22%2C%22app%22%3A%22siteforce%3AcommunityApp%22%2C%22fwuid%22%3A%22Q8onN6EmJyGRC51_NSPc2A%22%2C%22loaded%22%3A%7B%22APPLICATION%40markup%3A%2F%2Fsiteforce%3AcommunityApp%22%3A%224cm95xKNoonR9yZ2JR2osw%22%7D%2C%22apce%22%3A1%2C%22apck%22%3A%22u9iioD98ab206u8hlyEMmg%22%2C%22mlr%22%3A1%2C%22pathPrefix%22%3A%22%22%2C%22dns%22%3A%22c%22%2C%22ls%22%3A1%2C%22ct%22%3A1%7D/inline.js?aura.attributes=%7B%22schema%22%3A%22Published%22%2C%22brandingSetId%22%3A%22f5c37b15-72c4-4421-af84-37960d2fa7e0%22%2C%22authenticated%22%3A%22false%22%2C%22ac%22%3A%22%22%2C%22formFactor%22%3A%22LARGE%22%2C%22publishedChangelistNum%22%3A%2227%22%2C%22viewType%22%3A%22Published%22%2C%22themeLayoutType%22%3A%22nAtCOQTE4aYQruDNHEwADFuctWEGBf%22%2C%22language%22%3A%22en_US%22%2C%22isHybrid%22%3A%22false%22%2C%22pageId%22%3A%224d72295e-92a7-4b09-9a8d-fe789ec4b457%22%7D"></script> <script src="/s/sfsites/l/%7B%22mode%22%3A%22PROD%22%2C%22app%22%3A%22siteforce%3AcommunityApp%22%2C%22fwuid%22%3A%22Q8onN6EmJyGRC51_NSPc2A%22%2C%22loaded%22%3A%7B%22APPLICATION%40markup%3A%2F%2Fsiteforce%3AcommunityApp%22%3A%224cm95xKNoonR9yZ2JR2osw%22%7D%2C%22apce%22%3A1%2C%22apck%22%3A%22u9iioD98ab206u8hlyEMmg%22%2C%22mlr%22%3A1%2C%22pathPrefix%22%3A%22%22%2C%22dns%22%3A%22c%22%2C%22ls%22%3A1%2C%22ct%22%3A1%7D/resources.js?pv=16158482120001346086951&amp;rv=1615910240000"></script> </body>

  1. That is a part of the HTML code after the opening body tag.那是 HTML 代码在开始正文标记之后的一部分。 So I thought I give it a little time to get the whole html code.所以我想我给它一点时间来获取整个 html 代码。 I do so and was very happy when I get the whole code for the body我这样做了,当我得到身体的整个代码时非常高兴
  2. Now I tried to got all input tags.现在我尝试获取所有输入标签。 But I got the same error like before instead.但是我得到了和以前一样的错误。 It seemed there are no input tags.似乎没有输入标签。 but if I looked into the grabbed HTML code everthing was there.但如果我查看抓取的 HTML 代码,一切都在那里。 Very mysterious很神秘
  3. With the same VBA code I got sometimes the body and sometimes not.使用相同的 VBA 代码,我有时得到身体,有时没有。 What is wrong with that page?那个页面有什么问题? It's not possible for me to get same results with the same action.我不可能用同样的动作得到同样的结果。 I don't know why.我不知道为什么。 But like I wrote... Now I give up但就像我写的......现在我放弃了

This is my last VBA code to try something:这是我最后尝试的 VBA 代码:

Private Sub FillHtmlForm()
  
  Const url = "https://myturnvolunteer.ca.gov"
  Dim ie As Object
  Dim nodeBody As Object
  Dim nodeInput As Object
  Dim timeOutStart As Double
  
  'Create Internet Explorer
  Set ie = CreateObject("InternetExplorer.Application")
  ie.Visible = True
  ie.navigate url
  'Do While ie.readyState <> 4: DoEvents: Loop
  timeOutStart = Timer
  
  Do
    On Error Resume Next
    Set nodeBody = ie.document.getElementsByTagName("body")(0)
    On Error GoTo 0
    DoEvents
    If Not nodeBody Is Nothing Then
      If InStr(1, nodeBody.innertext, "Sorry to interrupt") > 0 Then
        Application.Wait (Now + TimeSerial(0, 0, 5))
      End If
    End If
  Loop Until Not nodeBody Is Nothing Or Timer - timeOutStart > 15 'Timeout in seconds
  
  If Not nodeBody Is Nothing Then
    Set nodeInput = nodeBody.getElementsByTagName("input")
    MsgBox nodeInput.Length 'I get an error here
  Else
    MsgBox "No body ;-)" 'Sometimes this occours with the same code without timeout
  End If
End Sub

Like QHarr wrote the code of the page seems to be in progress.就像 QHarr 写的页面的代码似乎正在进行中。 Yor HTML snippet has a PlaceHolder but in the HTML of the current page are no place holders in use.您的 HTML 片段有一个PlaceHolder ,但在当前页面的 HTML 中没有使用占位符。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM