Excel vba 运行时错误 91 对象变量或未设置块

Posted

技术标签:

【中文标题】Excel vba 运行时错误 91 对象变量或未设置块【英文标题】:Excel vba Runtime Error 91 Object Variable or With block not set 【发布时间】:2021-02-04 07:43:17 【问题描述】:

我编写了以下代码来从网站获取数据。一切正常,但是当在工作表(L 列)中收到结果时,出现带有黄色标记的错误 91(代码中断)。但是当再次按 F5 时,又会在特定列中得到结果,它会在同一位置停止。

Sub Pull_Data()
Dim IE As InternetExplorer
Dim doc As htmlDocument
Dim ElementCol As Object
Dim Link As Object


Dim i As Long
Dim output As Integer
Dim wkb As Workbook

Set wkb = ThisWorkbook

output = 212

Dim ws As Worksheet

Set ws = wkb.ActiveSheet

Set IE = New InternetExplorer

IE.Visible = False

  
        IE.navigate "http://119.40.95.162:8991/Pages/User/ConsumerInfo.aspx"
       
        Do While IE.Busy Or IE.readyState <> READYSTATE_COMPLETE
        Application.Wait Now + TimeValue("00:00:03")
        DoEvents
        Loop
           
      For i = 212 To ws.Range("A55000").End(xlUp).Row
     
        Set doc = IE.document
       
        doc.getElementById("cphMain_txtConsumer").Value = ThisWorkbook.Sheets("H-3").Range("D" & i).Value
       
        doc.getElementById("cphMain_btnReport").Click
       
        Do While IE.Busy Or IE.readyState <> READYSTATE_COMPLETE
        Application.Wait Now + TimeValue("00:00:05")
        DoEvents
        Loop

  With ws

        .Range("L" & i).Value = doc.getElementById("example3").getElementsByTagName("tr").Item(1).getElementsByTagName("td").Item(3).innerText
        .Range("M" & i).Value = doc.getElementById("example3").getElementsByTagName("tr").Item(1).getElementsByTagName("td").Item(5).innerText
        .Range("N" & i).Value = doc.getElementById("example3").getElementsByTagName("tr").Item(1).getElementsByTagName("td").Item(12).innerText
        .Range("O" & i).Value = doc.getElementById("example3").getElementsByTagName("tr").Item(1).getElementsByTagName("td").Item(11).innerText
  End With
             
        Set doc = IE.document
        Set ElementCol = doc.getElementsByTagName("a")
        
       
        For Each Link In ElementCol
            If Link.innerHTML = "Search Again " Then
                Link.Click
            End If
        Next Link
       
       Do While IE.Busy Or IE.readyState <> READYSTATE_COMPLETE
        Application.Wait Now + TimeValue("00:00:03")
        DoEvents
        Loop
       
    Next i


Set IE = Nothing


IE.Quit

NoItems:

End Sub

【问题讨论】:

突出显示哪一行/具体在哪里引发错误? .Range("L" & i).Value = doc.getElementById("example3").getElementsByTagName("tr").Item(1).getElementsByTagName("td").Item( 3).innerText doc.getElementById("example3").getElementsByTagName("tr").Item(1).getElementsByTagName("td").Item(3).innerText 可能有问题,但从这里无法判断。将此命令拆分为多个部分并找出失败的部分(是否有 ID example3,如果有:此元素是否有 tr,如果有,是否有 item(1) 等等) example3, tr & item(1) 没问题,我们得到了我想要的数据。当代码在特定行中运行并显示错误时,我再次单击 F5 然后结果显示 你可以试试...我给你一些D列数据23171213、23171228、23171232、23171247、23171251、23171266 【参考方案1】:

在大多数情况下,我知道,如果有按 F5 或 F8 不存在的问题,则存在时间问题。这正是您的代码所发生的事情。我将解释它,因为这是许多使用 IE 进行网页抓取项目的主要错误。

您使用以下代码等待页面加载:

Do While ie.Busy Or ie.readyState <> READYSTATE_COMPLETE
Application.Wait Now + TimeValue("00:00:03")
DoEvents
Loop

这行得通……但只有一次。如果 IE 的状态一旦设置为READYSTATE_COMPLETE,它将永远不会被重置,直到代码结束。因此,您第二次使用上述代码 sn-p 不起作用。循环将立即离开。页面未加载,这就是 运行时错误 91 对象变量或未设置 With 块的原因readyState 属性是只读的。所以我们不能手动重置它。

我已经完全重写了宏。它将所有数据集从 Web 表读取到 Excel。 (对于每一行,您需要 4 个值。)因此,我假设客户编号放置在另一张表 (H-3) 中,而不是您需要来自网页的数据。

如果您使用ActiveSheet 作为表格来导入数据,请使用空白表格查看会发生什么。第一个导入的行将是工作表中的第一个空行。因此,您可以使用相同的数据表开始另一个客户编号列表。

我已将 IE 可见性设置为 True,因此您可以看到页面访问时间有多么不同。您当然可以随时将可见性设置为False。在开发过程中,我建议您始终使 IE 可见。这样您就可以看到正在发生的事情,并且没有 IE 尸体在内存中堆积。例如,使用您问题中的代码,您可以看到错误 91 出现在包含表格的页面加载之前。

有很多cmets。请仔细阅读。我认为你可以学到很多东西:

Sub Pull_Data()

Const url As String = "http://119.40.95.162:8991/Pages/User/ConsumerInfo.aspx"

Dim ie As Object
Dim nodeDataTable As Object
Dim nodeAllDataRows As Object
Dim nodeOneDataRow As Object
Dim nodeDropdown As Object
Dim nodeDate As Object
Dim wkb As Workbook
Dim numberSheet As Worksheet 'Excel sheet with the consumer numbers in column D
Dim dataSheet As Worksheet   'Excel sheet for the wanted data from the internet
Dim currRowNumberSheet As Long
Dim firstRowNumberSheet As Long
Dim lastRowNumberSheet As Long
Dim currRowDataSheet As Long
Dim timeoutStart As Double

  Set wkb = ThisWorkbook
  Set numberSheet = wkb.Sheets("H-3")
  'If you use ActiveSheet as dataSheet you MUST start the makro from from the dataSheet!!!
  'Otherwise the makro will write all data in the real ActiveSheet
  Set dataSheet = wkb.ActiveSheet
  firstRowNumberSheet = 1 '212 'Are you sure that's your first row with a number in column D?
  lastRowNumberSheet = numberSheet.Cells(Rows.Count, 4).End(xlUp).Row 'Last row column D of the numberSheet
  currRowDataSheet = dataSheet.UsedRange.Rows.Count + 1 'Last used row of the dataSheet in general
  
  'Loop over all numbers in column D
  For currRowNumberSheet = firstRowNumberSheet To lastRowNumberSheet
    'Since the IE is a real old diva I recommend to start it new in every loop run
    'This way we have a defined state for each page call
    Set ie = CreateObject("InternetExplorer.Application")
    ie.Visible = False
    ie.navigate url
    'The following line only works one time for an open ie
    'After the readyState is once set to complete there is no reset
    Do While ie.readyState <> 4: DoEvents: Loop
    
    'Place current number from the number sheet to the webpage and click submit button
    ie.document.getElementById("cphMain_txtConsumer").Value = numberSheet.Range("D" & currRowNumberSheet).Value
    ie.document.getElementById("cphMain_btnReport").Click
    
    'Here you must wait to load the page you want, but like I wrote obove, we need another way for the right break
    'Aplication.Wait() is a possibility, but it wastes time in any case because you have to set up a fixed waiting period of time
    'A much more very prettier better way is a loop
    'In those loop we try to catch a HTML element from the page we are waiting for
    'In this way the optimal break is achieved
    'To prevent an endless loop we insert a timeout
    'As the period of time for the timeout you can set the time in seconds you otherwise would use for Aplication.Wait()
    'In most cases this time will never reached
    timeoutStart = Timer
    Do
      'Do the trick by switching off error handling
      On Error Resume Next
      'Try to catch the table with the needed data
      Set nodeDataTable = ie.document.getElementById("example3")
      'Switch back on error handling
      On Error GoTo 0
    'Try again till the table could be catched or till timeout
    Loop While nodeDataTable Is Nothing Or Timer - timeoutStart > 15 'Timeout in seconds
    
    If Not nodeDataTable Is Nothing Then
      'Read data from all rows the whole table:
      'To make this possible, before accessing the table, we manipulate the dropdown responsible for setting the number of rows displayed.
      'If we manually set the number of rows to e.g. 50, this amount of rows will be displayed immediately. This means that there is no
      'further access to the server. All data is already contained in the document. It cannot be seen in the HTML code and there is no JSON.
      'Therefore, I assume that the data is stored in a javascript variable.
      '
      'For security reasons, JavaScript variables cannot be accessed from outside. But we have the "Dropdown" interface. If we had an entry
      'showing the maximum number of search hits or more, we would be able to display all rows at once.
      '
      'Attention:
      'With most internet pages, setting the records to be displayed higher only works in the dropdown. But the entry itself has no effect.
      '
      'With this website it works:
      'We change the entry for 100 search hits to be displayed to 10000. If more search hits are needed, this number can simply be increased.
      'Then we select the manipulated entry and read the whole table into the Excel sheet.
      '
      'Let's manipulate the 100 value of the dropdown to 10000
      'Here we don't need to switch off error handling, because we know the right page was loaded
      'Get the right dropdown
      Set nodeDropdown = ie.document.getElementById("example3_length").getElementsByTagName("select")(0)
      'Now select the entry to manipulate
      nodeDropdown.selectedIndex = 3
      'Manipulate it to 10000 (The value. Not the entry to display)
      nodeDropdown.getElementsByTagName("option")(3).Value = 10000
      'To make the entry work we must trigger the change event of the dropdown
      Call TriggerEvent(ie.document, nodeDropdown, "change")
      'Now we need a short periot of time to generate the whole table
      'Since we don't know the HTML code of the last row of the HTML table to use the loop trick, we wait flatly a second
      'That is the price for "immediately", without any knowlage
      Application.Wait (Now + TimeSerial(0, 0, 1))
      'At this point we have the complete table
      'So we can read the data from all rows
      Set nodeAllDataRows = nodeDataTable.getElementsByTagName("tbody")(0).getElementsByTagName("tr")
      For Each nodeOneDataRow In nodeAllDataRows
        With nodeOneDataRow
          dataSheet.Range("L" & currRowDataSheet) = .getElementsByTagName("td")(3).innerText  'Meter Con.
          dataSheet.Range("M" & currRowDataSheet) = .getElementsByTagName("td")(5).innerText  'Consumption (kWh)
          dataSheet.Range("N" & currRowDataSheet) = .getElementsByTagName("td")(12).innerText 'Balance
          Set nodeDate = .getElementsByTagName("td")(11)
          If IsDate(nodeDate.innerText) Then
            dataSheet.Range("O" & currRowDataSheet) = CDate(nodeDate.innerText) 'Pay Date
          Else
            dataSheet.Range("O" & currRowDataSheet) = nodeDate.innerText 'Pay Date
          End If
          currRowDataSheet = currRowDataSheet + 1
        End With
      Next nodeOneDataRow
    Else
      'If there is no data table (e.g. because the customer number is wrong)
      'you should do a notice about that. I do it in the data sheet.
      'I think another place would be better. But I don't know your whole project
      dataSheet.Range("L" & currRowDataSheet) = "No data table"
    End If
    
    'Clean up
    'You must close the IE first since it is a third party application
    'If you first delete the VBA reference to it you can't reach the ie longer from the makro
    ie.Quit
    Set ie = Nothing
    'If you don't set nodeDataTable to Nothing here
    'you will get a 462 (Server not found) in the second loop run
    'The reason is the termination condition of the loop to wait for the data table
    'Without the following line nodeDataTable is never Nothing again after the first
    'loop run, but than the code try to enter data without an object and terminates
    'in the named error in .Range("L" & currRow) = nodeDataTable...
    Set nodeDataTable = Nothing
    
    'Select the last row in the dataSheet. You have an optical feedback while reading data then
    dataSheet.Range("L" & currRowDataSheet).Select
  Next currRowNumberSheet
End Sub

这个 sub() 来触发 HTML 事件:

Private Sub TriggerEvent(htmlDocument As Object, htmlElementWithEvent As Object, eventType As String)

  Dim theEvent As Object

  htmlElementWithEvent.Focus
  Set theEvent = htmlDocument.createEvent("HTMLEvents")
  theEvent.initEvent eventType, True, False
  htmlElementWithEvent.dispatchEvent theEvent
End Sub

【讨论】:

谢谢你先生.....它的工作。但有一个问题……我只需要上个月所有消费者的详细信息,而不是所有详细信息。提前致谢 @SayedAli 好的,这总是表中的第一行,对吧? Bill Month 恰好包含历史记录中每个月的一行。那么是不是读出的数据还是和客户编号在同一张表呢? 谢谢你,先生......我已经按照我的意愿做了......现在我想在 Python 代码中做同样的事情......请帮助我学习它......在此先感谢。 .. 亲爱的先生,希望你没事。您提供的代码在 Windows 7 中很好,但是当我在 Windows 10 专业版中运行代码时出现问题。它显示问题“运行时错误462。远程服务器机器不存在或不可用。

以上是关于Excel vba 运行时错误 91 对象变量或未设置块的主要内容,如果未能解决你的问题,请参考以下文章

运行时错误“91”(对象变量或未设置块变量)

运行时错误“91”:对象变量或未设置块变量

VBA Excel 错误对象变量或未设置块变量

EXCEL VBA - 对象变量或未设置块变量

Excel VBA“对象变量或未设置块变量”

类函数中的错误 91