Morning,
I am having trouble with a webscrape from Excel, whereby getelementsbyclassname is failing to act on some objects, throwing up the "Object doesn't support this property or method" error.
The problem appears when the object I am feeding into getelementsbyclassname is itself the result of a getelementsbyclassname method. I am not sure why, particularly as I can get the class name when acting on a larger object...
Here is a code extract
''''Boring Variables Declaration I've cut out''''
'Initialise IE
Dim IEApp As New InternetExplorer
Set IEApp = New InternetExplorer
IEApp.Visible = True 'JB
'Open page and wait for page to load
IEApp.navigate ("http://www.anicewebsite.com")
Do Until IEApp.readyState = READYSTATE_COMPLETE And IEApp.Busy = False
DoEvents
Loop
Set HTMLdoc = IEApp.document
Set RefLocation = Sheets("INFO_DUMP").Range("LocationRefCell")
Set trElements = HTMLdoc.getElementsByClassName("basic-details")
For Each trElement In trElements
'Select the LHS box and extract info
Set tdElement = trElement.getElementsByClassName("tieredToggle")
'write start/end locations
'''''THIS NEXT LINE THROWS AN ERROR'''''
Data_str = tdElement.getElementsByClassName("title").innerText
'''''AS DOES'''''
MyObject=tdElement.getElementsByClassName("title")
RefLocation.Offset(1, 2).Value = Data_str
Next 'close tr Loop
However, I can get the 'title' object via
For Each trElement In trElements
Set MyObject=trElement.getElementsByClassName("title")
Next 'close tr Loop
so the error is, presumably, something about tdElement (a DispHTMLElement Collection), which I tried to attach an image of but I lack the reputation (see link at end of post)...
Many thanks for any help.
PS. the webpage is structured, roughly, with a 2-column table whose rows I isolate with "basic-details". The first column is the "tiered toggle" and then the items I want are inner text in eg. "title". I need to use tieredtoggle as objects in each column have repeated class names
http://i.stack.imgur.com/1tyb6.png
You can use this to get the innertext.
Data_str = tdElement.getElementsByClassName("title")(0).innerText
Instead of ("title")(0) you can enter the index value where the element is present.
Related
I am struggling to get the right piece of code to scrape a table that is being a password protected website into an excel workbook. I have been able to get all of the code to work up to the scraping of the table part. When I run the code, it opens IE, logins in but then errors out (91: Object variable or WITH block variable not set). The code is below:
Private Sub CommandButton3_Click()
Declare variables
Dim IE As Object
Dim Doc As HTMLDocument
Dim HTMLTable As Object
Dim TableRow As Object
Dim TableCell As Object
Dim myRow As Long
'Create a new instance of Internet Explorer
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
'Navigate to the website
IE.Navigate "https://www.myfueltanksolutions.com/validate.asp"
'Wait for the page to finish loading
Do While IE.ReadyState <> 4
DoEvents
Loop
'Set the document object
Set Doc = IE.Document
'Fill in the security boxes
Doc.all("CompanyID").Value = "ID"
Doc.all("UserId").Value = "Username"
Doc.all("Password").Value = "Password"
'Click the submit button
Doc.all("btnSubmit").Click
'Wait for the page to finish loading
Do Until IE.ReadyState = READYSTATE_COMPLETE
DoEvents
Loop
'Set the HTMLTable object
Set HTMLTable = Doc.getElementById("RecentInventorylistform")
'Loop through each row in the table
For Each TableRow In HTMLTable.getElementsByTagName("tr")
'Loop through each cell in the row
For Each TableCell In TableRow.getElementsByTagName("td")
'Write the table cell value to the worksheet
Worksheets("Sheet1").Range("A5").Offset(myRow, 0).Value = TableCell.innerText
myRow = myRow + 1
Next TableCell
Next TableRow
Do Until IE.ReadyState = READYSTATE_COMPLETE
DoEvents
Loop
'Log out and close website
IE.Navigate ("https://www.myfueltanksolutions.com/signout.asp?action=rememberlogin")
IE.Quit
End Sub
I have included the HTML code of the table I am trying to scrape on the re-directed page after login.
I wont be tired to told it again and again and again and ... ;-)
Don't work with the IE anymore. MS is actively phasing it out!
But for explanation:
I'am sure, this is the code fragment which don't do what you expect:
...
...
'Wait for the page to finish loading
Do Until IE.ReadyState = READYSTATE_COMPLETE
DoEvents
Loop
'Set the HTMLTable object
Set HTMLTable = Doc.getElementById("RecentInventorylistform")
...
...
Waiting for READYSTATE_COMPLETE doesn't work here (for which reasons ever). So the code will go on without a stop and doesn't load the new content. The use of getElementByID() ends up in the named error then because there is no element with that id.
Excursus for some get-methods of the DOM (Document Object Model):
The methods getElementsByTagName() and getElementsByClassName() will build a node collection which contains all elements with the given criterion. If you build a collection like that with getElementsByTagName("a") you get a collection with all anchor tags. Every element of the collection can be called with it's index like in an array. If you want to know how many elements are in a collection like that you can read the attribute length. If there is no element you ask for, in our example a-tags, the length will be 0. But the collection was build so you have an object.
The get-methods which build a collection have an s for plural in ...Elements... But getElementByID() has no s because an id can only be once in a html document. No collection needed here. The method getElementByID() always try to buld an object from the asked criterion. If there is no element like that you will get the error that there is no object.
How to solve the issue:
We must change the termination criterion and the body of the loop. We must ask again and again if the element with the wanted id is present. To do that we must use the given line:
Set HTMLTable = Doc.getElementById("RecentInventorylistform")
Like I said before there will be raising an error if it is not present. That's right. But with On Error Resume Next we can ignore any error in the code.
Attention!
Only use this in specific situations and switch back to error handling with On Error GoTo 0 after the critical part of code.
Replace the code I posted above in this answer with the following one:
(To avoid endless loops it is recommended to use a time out mechanism too. But I will keep it simple here.)
Do
On Error Resume Next
Set HTMLTable = Doc.getElementById("RecentInventorylistform")
On Error GoTo 0
Loop While HTMLTable Is Nothing
I need the span value "0.062540" to pull from website through VBA.
{]1
My code is below:
Dim ie As New InternetExplorer
Dim doc As HTMLDocument
ie.Visible = False
ie.navigate "https://www.tefas.gov.tr/FonAnaliz.aspx?FonKod=MAC"
Do
DoEvents Loop Until ie.readyState = READYSTATE_COMPLETE
Set doc = ie.document
On Error Resume Next
output = doc.getElementsByClassName("top-list").getElementByTagName("span")(0).innerText
Sheet1.Range("B19").Value = output
ie.Quit
End Sub
However, I could not fetch the related value. Could you help with my problem?
First you need to remove On Error Resume Next and check which errors you get! This line hides all your error messages, but the errors still occur, you just cannot see them. If you don't see them you cannot fix them, therefore your code cannot work.
Never use this line as you did. Either remove it or implemet a proper error handling according VBA Error Handling – A Complete Guide.
Then if you do all these actions in one line like
output = doc.getElementsByClassName("top-list").getElementByTagName("span")(0).innerText
the error can be in multiple positions in that line and it is almost impossible to debug it and find out where in that line it is. Therefore we need to split that line up into multiple single actions to see in which part the error exactly occurs.
So we split it up like below which is exactly the same as the one line above:
Dim Divs As Object 'collection of div elements
Set Divs = doc.getElementsByClassName("top-list")
Dim Spans As Object 'collection of span elements
Set Spans = Divs.getElementByTagName("span")
Dim Output As String
Output = Spans(0).innerText
Now we will see that finding the div with a class top-list works. And we get an error at finding the Span elements. So if we have a look at the Divs variable we see that it is an collection of multiple items. Therefore we need to access the first item in that collection like Divs(0). Furthermore it is not getElementByTagName but getElementsByTagName (with an s). So correcting it to the following:
Dim Divs As Object 'collection of div elements
Set Divs = doc.getElementsByClassName("top-list")
Dim Spans As Object 'collection of span elements
Set Spans = Divs(0).getElementsByTagName("span")
Dim Output As String
Output = Spans(0).innerText
and we see it works.
Finally it is a good idea to implement some error handling, so in case something goes wrong you don't end up with hidden Internet Explorer windows that get never closed:
Public Sub FetchNumber()
Dim ie As New InternetExplorer
On Error Goto SAFE_QUIT 'make sure in case of error ie.quit is called.
ie.Visible = False
ie.navigate "https://www.tefas.gov.tr/FonAnaliz.aspx?FonKod=MAC"
Do
DoEvents Loop Until ie.readyState = READYSTATE_COMPLETE
Dim doc As HTMLDocument
Set doc = ie.document
Dim Divs As Object
Set Divs = doc.getElementsByClassName("top-list")
Dim Spans As Object
Set Spans = Divs(0).getElementsByTagName("span")
Dim Output As String
Output = Spans(0).innerText
SAFE_QUIT:
If Err.Number <> 0 Then
Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext
End If
On Error Goto 0 're activate error reporting
ie.Quit
Set ie = Nothing
End Sub
I am trying to create a macro that scrapes a cargo tracking website.
But I have to create 4 such macros as each airline has a different website.
I am new to VBA and web scraping.
I have put together a code that works for 1 website. But when I tried to replicate it for another one, I am stuck in the loop. I think it maybe how I am referring to the element, but like I said, I am new to VBA and have no clue about HTML.
I am trying to get the "notified" value in the highlighted line from the image.
IMAGE:"notified" text to be extracted
Below is the code I have written so far that gets stuck in the loop.
Any help with this would be appreciated.
Sub FlightStat_AF()
Dim url As String
Dim ie As Object
Dim nodeTable As Object
'You can handle the parameters id and pfx in a loop to scrape dynamic numbers
url = "https://www.afklcargo.com/mycargo/shipment/detail/057-92366691"
'Initialize Internet Explorer, set visibility,
'call URL and wait until page is fully loaded
Set ie = CreateObject("InternetExplorer.Application")
ie.Visible = False
ie.navigate url
Do Until ie.readyState = 4: DoEvents: Loop
'Wait to load dynamic content after IE reports it's ready
'We can do that in a loop to match the point the information is available
Do
On Error Resume Next
Set nodeTable = ie.document.getElementByClassName("block-whisper")
On Error GoTo 0
Loop Until Not nodeTable Is Nothing
'Get the status from the table
MsgBox Trim(nodeTable.getElementsByClassName("fs-12 body-font-bold").innerText)
'Clean up
ie.Quit
Set ie = Nothing
Set nodeTable = Nothing
End Sub
Some basics:
For simple accesses, like the present ones, you can use the get methods of the DOM (Document Object Model). But there is an important difference between getElementByID() and getElementsByClassName() / getElementsByTagName().
getElementByID() searches for the unique ID of a html tag. This is written as the ID attribute to html tags. If the html standard is kept by the page, there is only one element with this unique ID. That's the reason why the method begins with getElement.
If the ID is not found when using the method, VBA throws a runtime error. Therefore the call is encapsulated in the loop from the other answer from me, into switching off and on again the error handling. But in the page from this question there is no ID for the html area in question.
Instead, the required element can be accessed directly. You tried the access with getElementsByClassName(). That's right. But here comes the difference to getElementByID().
getElementsByClassName() and getElementsByTagName() begin with getElements. Thats plural because there can be as many elements with the same class or tag name as you want. This both methods create a html node collection. All html elements with the asked class or tag name will be listet in those collections.
All elements have an index, just like an array. The indexes start at 0. To access a particular element, the desired index must be specified. The two class names fs-12 body-font-bold (class names are seperated by spaces, you can also build a node collection by using only one class name) deliver 2 html elements to the node collection. You want the second one so you must use the index 1.
This is the VBA code for the asked page by using the IE:
Sub FlightStat_AF()
Dim url As String
Dim ie As Object
'You can handle the parameters id and pfx in a loop to scrape dynamic numbers
url = "https://www.afklcargo.com/mycargo/shipment/detail/057-92366691"
'Initialize Internet Explorer, set visibility,
'call URL and wait until page is fully loaded
Set ie = CreateObject("InternetExplorer.Application")
ie.Visible = False
ie.navigate url
Do Until ie.readyState = 4: DoEvents: Loop
'Wait to load dynamic content after IE reports it's ready
'We do that with a fix manual break of a few seconds
'because the whole page will be "reload"
'The last three values are hours, minutes, seconds
Application.Wait (Now + TimeSerial(0, 0, 3))
'Get the status from the table
MsgBox Trim(ie.document.getElementsByClassName("fs-12 body-font-bold")(1).innerText)
'Clean up
ie.Quit
Set ie = Nothing
End Sub
Edit: Sub as function
This sub to test the function:
Sub testFunction()
Dim flightStatAfResult As String
flightStatAfResult = FlightStat_AF("057-92366691")
MsgBox flightStatAfResult
End Sub
This is the sub as function:
Function FlightStat_AF(cargoNo As String) As String
Dim url As String
Dim ie As Object
Dim result As String
'You can handle the parameters id and pfx in a loop to scrape dynamic numbers
url = "https://www.afklcargo.com/mycargo/shipment/detail/" & cargoNo
'Initialize Internet Explorer, set visibility,
'call URL and wait until page is fully loaded
Set ie = CreateObject("InternetExplorer.Application")
ie.Visible = False
ie.navigate url
Do Until ie.readyState = 4: DoEvents: Loop
'Wait to load dynamic content after IE reports it's ready
'We do that with a fix manual break of a few seconds
'because the whole page will be "reload"
'The last three values are hours, minutes, seconds
Application.Wait (Now + TimeSerial(0, 0, 3))
'Get the status from the table
result = Trim(ie.document.getElementsByClassName("fs-12 body-font-bold")(1).innerText)
'Clean up
ie.Quit
Set ie = Nothing
'Return value of the function
FlightStat_AF = result
End Function
Sub FindUser()
Dim ie as shdocvw.internetexplorer
Dim ht as htmldocument
Set ie = new internetexplorermedium
Ie.visible = True
Ie.navigate ("url")
Do while ie.busy or ie.readystate <> 4
Doevents
Loop
Set ht = ie.document
Activesheet.range("b30").value = ht.getelementbyid("infobasic").getelementsbytagname("span") (0).innertext
End sub
I get error at bolded text (activesheet...line). But if I continue running code manualy I get the desired value in cell. It's just the error in middle. Please help. I want to get the web data into excel cell. In code I have mentioned only one tag however I will be using more tags to get more results from web.
The error means that you are trying to access a Property of an object that is null.
You need to expand your method a bit, and check each object before you try to access its properties to avoid such errors.
For example:
Dim objInfo as Object
Set objInfo = ht.getelementbyid("infobasic")
If objInfo Is Nothing then
'no need to go any further here as the object is null and
'will throw an error if you try to access its properties.
End if
Dim elements as Object
Set elements = objInfo.getelementsbytagname("span")
If elements Is Nothing then
'same as above
End if
Dim element as Object
Set element = elements(0)
If Not element Is Nothing then
'here you can safely access the .innerText property
End if
After debugging the above, you can see which object has not been set.
Hope this helps.
I'm trying to automate a report of mine using VBA. I was able to do the log in part and all but when it comes to navigating the next webpage with drop downs I can't seem to get a hold of it. I tried multiple ways to fill out the first drop down which is the report type and I can't find the correct code
All commented "'" are all the codes I've tried. I can't post the website since it's a client website.
html "inspect elemet" photo
Sub Get_RawFile()
Dim IE As New InternetExplorer
Dim HTMLDoc As HTMLDocument
Dim addressInput As HTMLInputElement
With IE
.Visible = True
.Navigate ("------------------------")
While IE.Busy Or IE.readyState <> 4: DoEvents: Wend
Set HTMLDoc = IE.document
HTMLDoc.all.UserName.Value = Sheets("Data Dump").Range("A1").Value
HTMLDoc.all.Password.Value = Sheets("Data Dump").Range("B1").Value
HTMLDoc.getElementById("login-btn").Click
While IE.Busy Or IE.readyState <> 4: DoEvents: Wend
'HTMLDoc.getElementByName("ddlReportType")(0).Value = "1"
'Set HTMLDoc = IE.document
'Set evtChange = HTMLDoc.createEvent("HTMLEvents")
'evtChange.initEvent "change", True, False
'Set selectElement = HTMLDoc.getElementById("ddlReportType")
'selectElement.Value = "1" 'Attendance
'selectElement.dispatchEvent evtChange
'Set htmlSelectElem = HTMLDoc.getElementsByTagName("ddlReportType")
'htmlSelectElem.selectedIndex = 1
'Set reporttype = IE.document.getElementById("ddlReportType")
'For i = 1 To reporttype.Options.Length
'If reporttype.Options(i).Text = "Attendance" Then
'reporttype.selectedIndex = i
'Exit For
'End If
'Next i
End With
Run-time error '438'
Object doesn't support this property or method
This method .getElementByName does not exist. It should be .getElementsByName("Something"), in which case the method returns a collection of elements whose Name attribute is Something.
The following would access the first element of the collection of elements whose Name attribute is ddlReportType and set its value to 1:
HTMLDoc.getElementsByName("ddlReportType")(0).Value = "1"
Having said that, you don't seem so sure as to what ddlReportType is.
If it's the id of an element then the element should look like so:
<tagName id="ddlReportType">Something</tagName>
In that case you should use the method .getElementByID()
If it's the Name of an element then the element should look like so:
<tagName Name="ddlReportType">Something</tagName>
In that case you should use the method .getElementsByName(), which returns a collection of elements.
If it's the Class of an element then the element should look like so:
<tagName Class="ddlReportType">Something</tagName>
In that case you should use the method .getElementsByClassName(), which returns a collection of elements.
Finally, I can tell you for sure that it's not a Tag Name so you definitely shouldn't use the .getElementsByTagName method.
EDIT
From the image you provided after editing your original post, it is clear that ddlReportType is the id of a <select></select> element. So you should do it this way:
Dim HTMLselect As HTMLSelectElement
Set HTMLselect = HTMLDoc.getElementById("ddlReportType")
HTMLselect.Value = "1"
References used: Microsoft HTML Object Library (VBE>Tools>References>...)
That's how to select one of the drop down options. To navigate further you would have to press some button I presume.