Getting Web Element Using VBA - vba

I can't seem to return the web element using this function. Any help would be appreciated.
Function scrape2(id As String)
Set objhttp1 = New WinHttp.WinHttpRequest
Dim doc1 As MSHTML.HTMLDocument
objhttp1.Open "GET", "https://www.axie.tech/axie-pricing/" & id, True
objhttp1.send
Set doc1 = New MSHTML.HTMLDocument
doc1.body.innerHTML = objhttp1.responseText
Set Result = doc1.FindElementByClass("axie-pricing_decodedGenesCapsule__m9R-C")
scrape2 = doc1.Text
End Function
Function Input: 373595
Expected Result:
D
R1
R2
Telescope
Telescope
Sleepless
Sakura
Pink Cheek
Curly
Anemone
Sponge
Blue Moon
Piranha
Piranha
Lam
Lagging
Scaly Spear
Teal Shell
Koi
Nimo
Snake Jar

doc1 is an MSHTML.HTMLDocument instance which does not have a FindElementByClass method. Nor a .text property of any nodes within the DOM. The method you are referencing is one from Selenium Type class where you have an instance of WebDriver.
Then there a variety of other issues. What you see on the page is dynamically rendered, and the data you want comes from XHR POST requests using graphql queries that return JSON. You would need to formulate those requests and use a JSON parser, such as in my example below, on the results.
Also, use Option Explicit, declare all your variables, use meaningful names and make the request synchronous by passing False in the .Open line.
parts is a collection of dictionaries you need to For Each over, then For Each Key, where Key is variant, over the .Keys of each dictionary.
Select the items you want. The key abilities returns a collection, so needs to be handled differently if used.
TODO:
R1 and R2 you need to use the parent ids and extract from requests for those ids info. The code below is for the child id and returns column D of your desired results.
data requests a lot more info that it seems you need so you could remove a lot of the parameters within it.
JSON library:
I use JsonConverter.bas. Download raw code from here and add to standard module called JsonConverter . Remove the top Attribute .... line from the copied code and Option Explicit if already specified.
You then need to go:
VBE > Tools > References > Add references to:
Microsoft Scripting Runtime
Microsoft HTML Object Library
Microsoft WinHTTP Services, version 5.1 Library (or your version).
In VBA for JSON the [] denotes a collection and the {} represents a dictionary.
VBA:
Option Explicit
Public Sub PrintGetAxieDetail()
Dim response As Object
Set response = GetAxieGeneDetail("5016162")
Debug.Print JsonConverter.ConvertToJson(response)
End Sub
Public Function GetAxieGeneDetail(ByVal id As String) As Object
Dim http As WinHttp.WinHttpRequest
Dim doc1 As MSHTML.HTMLDocument
Dim data As String
Set http = New WinHttp.WinHttpRequest
Set doc1 = New MSHTML.HTMLDocument
With http
.Open "POST", "https://axieinfinity.com/graphql-server-v2/graphql", False
.setRequestHeader "content-type", "application/json"
.setRequestHeader "user-agent", "Mozilla/5.0"
.setRequestHeader "referer", "https://www.axie.tech/"
.setRequestHeader "accept-language", "en-GB,en-US;q=0.9,en;q=0.8"
data = "{""operationName"":""GetAxieDetail"",""variables"":{""axieId"":""" & id & """},""query"":""query GetAxieDetail($axieId: ID!)" & _
"{\n axie(axieId: $axieId) {\n ...AxieDetail\n __typename\n }\n}\n\nfragment AxieDetail on Axie {\n id\n image" & _
"\n class\n chain\n name\n genes\n owner\n birthDate\n bodyShape\n class\n sireId\n sireClass\n matronId\n matronClass" & _
"\n stage\n title\n breedCount\n level\n figure {\n atlas\n model\n image\n __typename\n }" & _
"\n parts {\n ...AxiePart\n __typename\n }\n stats {\n ...AxieStats\n __typename\n }" & _
"\n auction {\n ...AxieAuction\n __typename\n }\n ownerProfile {\n name\n __typename\n }" & _
"\n battleInfo {\n ...AxieBattleInfo\n __typename\n }" & _
"\n children {\n id\n name\n class\n image\n title\n stage\n __typename\n }\n __typename" & _
"\n}\n\nfragment AxieBattleInfo on AxieBattleInfo {\n banned\n banUntil\n level\n __typename" & _
"\n}\n\nfragment AxiePart on AxiePart {\n id\n name\n class\n type\n specialGenes\n stage\n abilities" & _
"{\n ...AxieCardAbility\n __typename\n }\n __typename\n}\n\nfragment AxieCardAbility on AxieCardAbility" & _
"{\n id\n name\n attack\n defense\n energy\n description\n backgroundUrl\n effectIconUrl\n __typename\n}" & _
"\n\nfragment AxieStats on AxieStats {\n hp\n speed\n skill\n morale\n __typename\n}\n\nfragment AxieAuction on Auction" & _
"{\n startingPrice\n endingPrice\n startingTimestamp\n endingTimestamp\n duration\n timeLeft\n currentPrice" & _
"\n currentPriceUSD\n suggestedPrice\n seller\n listingIndex\n state\n __typename\n}\n""}"
.send data
Dim axieDetail As Object, geneResults As Object
Set axieDetail = JsonConverter.ParseJson(.responseText)
Set geneResults = axieDetail("data")("axie")("parts") 'this returns a collection to For Each over
Set GetAxieGeneDetail = geneResults
End With
End Function
Sample of JSON:

Related

How do i load structure into array / object / class?

How do I load the below structure into VB.Net array / object / class? and how do I get access to specific element?
I retrieved it from the binance api using this link:
https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=30m&limit=2
[
[
1571896800000,
"7412.72000000",
"7414.38000000",
"7361.01000000",
"7376.28000000",
"964.39453100",
1571898599999,
"7124637.09263142"
,8360,
"491.13171700",
"3627861.01587878",
"0"
],
[
1571898600000,
"7376.39000000",
"7395.06000000",
"7337.99000000",
"7370.76000000",
"926.16601400",
1571900399999,
"6820821.48483493",
7666,
"449.47121800",
"3311765.73197726",
"0"
]
]
i know how to parse string, i wonder if there is better / easier way.
This is how i retrieve the data from the API:
Function APICall2(ByVal Security As String, ByVal command As String, Optional ByVal param1 As String = "", Optional ByVal param2 As String = "", Optional ByVal param3 As String = "")
Dim APIUrl As String
APIUrl = "https://api.binance.com/api/v3/" + command + "?symbol=" + Security + "&interval=" + param1 + "&limit=1"
Dim Request As System.Net.HttpWebRequest = DirectCast(System.Net.HttpWebRequest.Create(APIUrl), System.Net.HttpWebRequest)
Dim Response As System.Net.HttpWebResponse = DirectCast(Request.GetResponse(), System.Net.HttpWebResponse)
Dim Read = New System.IO.StreamReader(Response.GetResponseStream).ReadToEnd
APICall2 = Read
End Function
Like the comments suggested, install Newtonsoft.Json.Net to assist with parsing the json data. Then, it's as simple as
'Array option
Dim stringArray= Newtonsoft.Json.JsonConvert.DeserializeObject(Of String())(yourJsonString)
'List option
Dim listOfStrings = Newtonsoft.Json.JsonConvert.DeserializeObject(Of List(Of String))(yourJsonString)
This essentially parses json "structure" and loads it into your desired collection type.

Formula Written in Module Producing #VALUE

With assistance from outside sources I have this code that takes latitude and longitude and extracts zipcodes. Here is the code:
Public Function ReverseGeoCode(myInput1 As String, myInput2 As String) As String
'You will need to reference Microsoft XML, v6.0 object library
Dim XMLDoc As New DOMDocument60
Dim XMLNODE As IXMLDOMNode
Dim I As Long
Dim lat, lng, myAddress, myZipcode, reportZipcode As String
Dim splitAddress, splitZipcode As Variant
lat = myInput1
lng = myInput2
XMLDoc.Load "http://maps.googleapis.com/maps/api/geocode/xml?latlng=" & lat & "," & lng & " &sensor=false"
Do Until XMLDoc.readyState = 4
DoEvents
Loop
If Len(XMLDoc.Text) = 0 Then
Call MsgBox("No Data!")
Exit Function
End If
Set XMLNode = XMLDoc.SelectSingleNode("/GeocodeResponse/result/formatted_address")
For i= 0 To XMLNode.ChildNodes.Length - 1
myAddress = XMLNode.ChildNodes(i).Text
Next i
splitAddress = Split(myAddress, ",")
myZipcode = splitAddress(2)
myZipcode = Trim(myZipcode)
splitZipcode = Split(myZipcode, " ")
reportZipcode = splitZipcode(1)
reportZipcode = Trim(reportZipcode)
ReverseGeoCode = reportZipcode
End Function
So the code works, and I know it might not be the cleanest. But the issue is when I call it in the Excel sheet using "=ReverseGeoCode(Cell1, Cell2)". Sometimes it works fine, other times it produces the return "#VALUE!" and I am not entirely sure why. I attached an image below to show you an example of the error. Does anyone have an idea of why this error is producing?
General observations:
So to pick up on what I wrote in the comments, here is an outline.
You don't want to use an User Defined Function. This will keep on making repeated calls. You definitely risk hitting a call limit to the API without an API key, and possibly with; it is inefficient and it is not necessary. Instead, write a sub which you call once and which loops all the required cells in the sheet and issues the API calls and returns the zip codes. An API key is a method of authentication used with many API calls. You shouldn't share it by the way.
These repeated calls, possibly hitting a limit and the fact that UDFs are frequently calculated maybe the source of your woes.
With efficiency in mind, first remove duplicates from the sheet to avoid calls that are not required. Switch of Screen-Updating and anything else e.g. CalculationMode to manual whilst performing.
From what I have read you require an API key once you have hit a daily limit. Not sure what the API limit is for free version or without API key.
Outline code (XML request with some psuedo code):
Option Explicit
Public Sub ListZipCodes()
Dim lat As Double, longitude As Double
Const APIKEY As String = "yourAPIkey"
Application.ScreenUpdating = False '<==Speed up code when actually working with sheet
'Code to remove duplicates
'Code to loop sheet and call function on each input set of values
'Example call. These would be picked up from cells
lat = 40.714224
longitude = -73.961452
Debug.Print GetZipCode(lat, longitude, APIKEY)
Application.ScreenUpdating = True
End Sub
Public Function GetZipCode(ByVal lat As Double, ByVal longitude As Double, ByVal APIKEY As String) As String
Dim sResponse As String
With CreateObject("MSXML2.XMLHTTP")
Dim URL As String
URL = "https://maps.googleapis.com/maps/api/geocode/xml?latlng=" & lat & "," & longitude & "&key=" & APIKEY
.Open "GET", URL, False
.send
If .Status <> 200 Then
GetZipCode = "API call failed"
Exit Function
End If
Dim XMLDoc As New DOMDocument60, XMLNODE As IXMLDOMNode
XMLDoc.Load .responseBody
If Len(XMLDoc.Text) = 0 Then GetZipCode = "No data"
Set XMLNODE = XMLDoc.SelectSingleNode("/GeocodeResponse/result/formatted_address")
GetZipCode = Split(Trim$(Split(XMLNODE.Text, Chr$(44))(2)), Chr$(32))(1)
End With
End Function
Requesting JSON rather than XML response:
The reason calling as JSON was falling over was that the response needed to be decoded. Here is the function re-written to handle a JSON response.
This requires the download of JSONConverter, which you then import and add a reference to Microsoft Scripting Runtime via VBE > Tools > References.
The example below was run with
latitude: 42.9865913391113,
longitude: -100.137954711914
VBA:
Public Function GetZipCode(ByVal lat As Double, ByVal longitude As Double, ByVal APIKEY As String) As String
Dim sResponse As String, json As Object
With CreateObject("MSXML2.XMLHTTP")
Dim URL As String, formattedAddress As String
URL = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & lat & "," & longitude & "&key=" & APIKEY
.Open "GET", URL, False
.send
If .Status <> 200 Then
GetZipCode = "API call failed"
Exit Function
End If
Set json = JsonConverter.ParseJson(StrConv(.responseBody, vbUnicode))
formattedAddress = json("results").item(1)("formatted_address")
GetZipCode = Split(Trim$(Split(formattedAddress, Chr$(44))(2)), Chr$(32))(1)
End With
End Function
With a JSON request the initial object you get back is a dictionary, as denoted by the opening "{" in the decoded response:
i.e. Set json = JsonConverter.ParseJson(StrConv(.responseBody, vbUnicode)) returns a dictionary object
The data of interest, in the dictionary, has the key "results", as you may observe from the above.
This can be accessed with json("results"), which returns a collection of dictionaries. This being denoted by the following "[", for collection, and subsequently by the start of the first dictionary within the collection, indicated again by "{".
I can grab the first dictionary in the collection by index with:
json("results").item(1)
An inspection of the keys in this dictionary shows that one of the keys is what we are after i.e."formatted_address".
It's associated value is a primitive datatype; in this case a string. This means we can directly access it using the key (a further object is not returned).
formattedAddress = json("results").item(1)("formatted_address")
Now that we have the address string, we can parse it as we did before:
GetZipCode = Split(Trim$(Split(formattedAddress, Chr$(44))(2)), Chr$(32))(1)
End note:
You can use Postman, amongst other tools, to test API calls, and in this case inspect the JSON response. Indeed, to see what kind of response you are getting full stop.
Help:
It is very quick and easy to set up a project, generate an API key and get started. Maybe 10 minutes to read through and perform.
Instructions on setting up a project and getting an API key
Enabling the API
Understanding how to make API calls to the Geocoding API

Calling Multi-Variable Function in VBA - URL checker for redirection

The following code was posted by Justin Dearing here:
url checker VBA, when redirected, show redirected url
However, how on earth can I call this function to return T/F AND the target? I've tried various ways (Call, straight request etc) and continue to get compile errors, invalid use of object.
If I cut the function down to: Public Function GetResult(strUrl As String) As Boolean
This returns the T/F for a 301 or 302 however doesn't return the target.
What is the correct approach to calling this function to get the isRedirect True/False AND the target redirected too? What am I missing?
Any pointers appreciated. B
Public Function GetResult(ByVal strUrl As String, Optional ByRef isRedirect As Boolean, Optional ByRef target As String) As String
Dim oHttp As New WinHttp.WinHttpRequest
oHttp.Option(WinHttpRequestOption_EnableRedirects) = False
oHttp.Open "HEAD", strUrl, False
oHttp.send
GetResult = oHttp.Status & " " & oHttp.statusText
If oHttp.Status = 301 Or oHttp.Status = 302 Then
isRedirect = True
target = oHttp.getResponseHeader("Location")
Else
isRedirect = False
target = Nothing
End If
End Function
Try this, see if this works.
Option explicit
Public Function GetResult(ByVal strUrl As String, Optional ByRef isRedirect As Boolean, Optional ByRef target As String) As String
Dim oHttp As WinHttp.WinHttpRequest
Set oHttp = new winhttp.winhttprequest
With ohttp
.Option(WinHttpRequestOption_EnableRedirects) = False
.Open "HEAD", strUrl, False
.send
GetResult = .Status & " " & .statusText
If .Status = 301 Or .Status = 302 Then
isRedirect = True
target = .getResponseHeader("Location")
Else
isRedirect = False
target = vbnullstring
End If
End with
End Function
Untested and written on mobile, sorry for bad formatting.
Note that I don't use Dim ... As new syntax. Instead I use dim and set.
Hope it works. Let me know how you get on.
My understanding is that you want the function to return multiple values. Passing parameters ByRef is one workaround to achieve this. An alternative might be to return a single delimited string, which you then split() on the chosen delimiter. Or returning a variant.

Type Mismatch when setting an object

I am trying to query a web API using VBa.
The issue I am having is the return result throws this exception
Type Mismatch
This occurs when I exit the getJson function (shown below)
Function StartOfCode()
'...code
Dim jsonResult As Object
Set jsonResults = getJson(query) 'cannot get past this
'... more code
End Function
Function getJson(ByRef query As String) As Object
Dim MyRequest As Object
Set MyRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
With MyRequest
.Open "GET", query
.setRequestHeader "Content-Type", "application/json"
.setRequestHeader "Accept", "application/json"
.send
End With
Set getJson = DecodeJson(MyRequest.ResponseText) ' returns fine and I can see the object, of type Object/JScript/TypeInfo
Set MyRequest = Nothing
End Function
Function DecodeJson(JsonString As Variant) As Object
Set DecodeJson = m_ScriptEngine.Eval("(" + JsonString + ")")
End Function
I don't understand what I've done wrong here
The problem is with the object declaration:
Dim jsonResult As Object
Set jsonResults = getJson(query)
You have declared an object called jsonResult but in the next line you are using the plural form of the varibable name: jsonResults. So you need to change one of these variable names so they match.

How to send Pushover in VBA

What is the best approach to send a Pushover via VBA?
In Pushover website code example, I didn't find a VBA example.
He is the Function.
PS: I use an auxiliar function to format strings
TesteFunction:
Public Sub Test()
Debug.Print PushOverPost("myApplication", "mySubscriptionGroup", "Hello Stackoverflow!")
End Sub
PushoverFunction:
' Send a post via PushOver
' #param In application as String: The application's token
' #param In group as String: The user/group token
' #param In message as String: The message that you want to send
' #return as String(): The post response
Public Function PushOverPost(ByVal application As String, ByVal group As String, ByVal message As String) As String
Dim xhttp As Object, params As String, url As String
Set xhttp = CreateObject("MSXML2.ServerXMLHTTP")
params = StringFormat("token={0}&user={1}&message={2}", application, group, message)
url = "https://api.pushover.net/1/messages.json"
With xhttp
.Open "POST", url, False
.setRequestHeader "Content-type", "application/x-www-form-urlencoded"
.send params
PushOverPost = .responseText
End With
End Function
Auxiliar Function:
' Generate a string using .NET format, i.e. {0}, {1}, {2} ...
' #param In strValue as String: A composite format string that includes one or more format items
' #param In arrParames as Variant: Zero or more objects to format.
' #return as String: A copy of format in which the format items have been replaced by the string representations of the corresponding arguments.
' #example: Debug.Print StringFormat("My name is {0} {1}. Hey!", "Mauricio", "Arieira")
Public Function StringFormat(ByVal strValue As String, ParamArray arrParames() As Variant) As String
Dim i As Integer
For i = LBound(arrParames()) To UBound(arrParames())
strValue = Replace(strValue, "{" & CStr(i) & "}", CStr(arrParames(i)))
Next
StringFormat = strValue
End Function