VBA script to pull values within specific HTML classes - html

I created a VBA script to pull prices from websites by getting the value within an HTML Class.
Please see VBA Script pull data from website for more context.
This works really well however there are some cases where there is only 1 price (no RRP & sale price) and therefore i need to somehow incorporate an if statement to look for the a class name, if that doesn't exist look for another.
For example I have the following spreadsheet:
| A | B | C |
| | Item | Price |
| | bfd/garden-structures/arbours/arbours-sunflower | |
| | bfd/garden-structures/arbours/tatton-corner-arbour-seat | |
| | bsd/garden-storage/wooden-storage/4-x-2-windsor-garden-storage-chest | |
In this example the first 2 work with the code below: (looking int class VariantPrice & NowValue) however the 3rd example doesn't work as the classes VariantPrice & NowValue do not exist, however it does have the class price & SinglePrice.
The code I have used is below:
Sub BuyDeckingDirect()
Dim ie As New InternetExplorer
Dim doc As HTMLDocument
Dim result As IHTMLElement
Dim result2 As IHTMLElement
Dim item As String
Dim lRow As Long
'ie.Visible = True'
lRow = 2
item = Worksheets("BuyDeckingDirect").Range("B" & lRow).Value
MsgBox "Price Dump Started"
Do Until item = ""
ie.navigate "http://www.buydeckingdirect.co.uk/" & item
Do
DoEvents
Loop Until ie.readyState = READYSTATE_COMPLETE
Set doc = ie.document
Set result = doc.querySelector(".VariantPrice")
Set result2 = result.querySelector(".NowValue")
Worksheets("BuyDeckingDirect").Range("C" & lRow).Value = result2.innerText
lRow = lRow + 1
item = Worksheets("BuyDeckingDirect").Range("B" & lRow).Value
Loop
MsgBox "BuyDeckingDirect Price Dump Complete"
End Sub
Any help would be really appreciated!
Thanks
Jess

Combine both classes in the call to querySelector and if result is Nothing, call it again with the alternative class.
Example:
' ...
Set result = doc.querySelector(".VariantPrice .NowValue")
If result Is Nothing Then
Set result = doc.querySelector(".VariantPrice .price")
End If
' You should consider the fact that you can have neither
If result Is Nothing Then
Worksheets(...etc...).Value = "N/A"
Else
Worksheets(...etc...).Value = result.innerText
End If
Of course you can also check for the existence of the NowValue class after setting result like so:
' ...
Set result = doc.querySelector(".VariantPrice")
If IsNull(result.querySelector(".NowValue")) Then
Set result2 = result.querySelector(".price")
Else
Set result2 = result.querySelector(".NowValue")
End If
Personally, I prefer the 1st option but it's up to your use case.

Related

Refer to OpenArgs to identify field name

I am having some issues trying to use OpenArgs to refer to a field name in a table. I'm fairly new to this so please bear with me.
So I have a Report(CourseCatalog) that has text boxes with course names that it pulls from a table(tblCourses). When you click on a course, it opens a Form which gives you the option to rate the course(frmRate). I use OpenArgs (from the report to the rating form) to make the caption for frmRate. Works fine.
Now i need to take the data (number of stars selected, (intNumStars)), which is defined previously in the code, and put it into a table. That table ("Allratings") has course names (the OpenArgs value) as the column names and I want to put the intNumStars(1-5) into the cells in those columns.
I seem to have some problems referring to the VarArgs to accomplish that. My syntax/logic may be (is probably) wrong, and if anyone knows a better way to accomplish this task, please let me know what you think! Thank you!
Private Sub btnSubmit_Click()
Dim varargs
Dim rst As dao.Recordset
Dim db As dao.Database
Dim fld As dao.Field
varargs = Me.OpenArgs
Set db = CurrentDb()
Set rst = db.OpenRecordset("Allratings")
For Each fld In rst.Fields
If fld.Name = "varargs" Then
rst.AddNew
rst!"varargs" = intNumStars
rst.Update
End If
Next
End Sub
You're doing weird things with strings, and you should get a descriptive compile error when you try to save/compile this.
varargs refers to the variable varargs
"varargs" is a literal string containing the letters v, a, r, etc.
rst!"varargs" should be rst.Fields("varargs") to avoid a compile error, but should actually be rst.Fields(varargs)
If you correct all incorrectly placed quotes, you get:
Private Sub btnSubmit_Click()
Dim varargs As String
Dim rst As dao.Recordset
Dim db As dao.Database
Dim fld As dao.Field
varargs = Me.OpenArgs
Set db = CurrentDb()
Set rst = db.OpenRecordset("Allratings")
For Each fld In rst.Fields
If fld.Name = varargs Then
rst.AddNew
rst.Fields(varargs) = intNumStars
rst.Update
End If
Next
End Sub
This seems valid, if Me.OpenArgs contains only a field name
Eric has shown how to make your code work, but your table design will prove impractical. Adding a new column for each new course is not how you work with databases.
I suggest
tblCourses
+----------+------------+
| CourseID | CourseName |
+----------+------------+
| 1 | foo |
| 2 | bar |
+----------+------------+
tblUsers
+--------+----------+
| UserID | UserName |
+--------+----------+
| 7 | John |
| 8 | Kate |
+--------+----------+
allRatings (this is a Junction table)
+----------+--------+----------+
| CourseID | UserID | NumStars |
+----------+--------+----------+
| 1 | 7 | 1 |
| 1 | 8 | 4 |
| 2 | 7 | 3 |
+----------+--------+----------+
So a new rating is a new record in allRatings, with fixed column names. You can get the CourseID from the passed Course Name with a DLookup call.
And a view with Courses and their ratings as columns is achieved with a Crosstab query.

inserting a HTML table cell into a excel sheet cell with no id

i was trying to extract a cell from a HTML table into a excel cell by using a function. the table is like this:
| 1Q | 2Q | 3Q |
income | 23 | 34 | 22 |
expenses | 11 | 19 | 10 |
.
.
.
i cannot get the elements by id, so i created two loops to look for element by element. the code can find the elements (in my case, the word "expenses" col1 row2) but i don't know how to get the cell value to the right (11 in this case)
Dim IE As Object
Dim doc As Object
Dim colTR As Object
Dim colTD As Object
Dim tr As Object
Dim td As Object
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
IE.Navigate "www.mywebpage.com"
Do Until IE.ReadyState = 4
DoEvents
Loop
Set doc = IE.Document
Set colTR = doc.GetElementsByTagName("TR")
For Each tr In colTR
Set colTD = tr.GetElementsByTagName("TD")
For Each td In colTD
If td.innertext = "expenses" Then
TheValueIWant = tr.item(1).innertext
End If
Next td
Next tr
IE.Quit
Set IE = Nothing
Set doc = Nothing
Set colTR = Nothing
Set colTD = Nothing
Set td = Nothing
Set tr = Nothing
thanks in advance
Scott is right there is no reason to loop through all the tds.
For Each tr In colTR
If tr.Cells(0).innerText = "expenses" Then
TheValueIWant = tr.Cells(1).innerText
MsgBox TheValueIWant
End If
Next

Issues with adding to a database

My program has a method for adding dictionaries through a text file to the database. Said text file consists of a word, what manner of word it is (i.e. noun, verb, etc.) and then the associated image's file name, all formatted in the form of:
word#type#filename
word2#type#filename2
and so on. To avoid repeats of word entries, I use a MySqlDataReader in conjunction with a query to run through all of the rows and then add any rows that are not yet added. The code looks like this:
Private Sub btnCreateBank_Click(sender As Object, e As EventArgs) Handles btnCreateBank.Click
Dim newOpenDialog As New OpenFileDialog
Dim newFileStream As FileStream = Nothing
newOpenDialog.InitialDirectory = GetFolderPath(SpecialFolder.MyDocuments)
newOpenDialog.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*"
newOpenDialog.FilterIndex = 1
newOpenDialog.ShowDialog()
Try
newFileStream = newOpenDialog.OpenFile()
If (newFileStream IsNot Nothing) Then
Dim sr As New StreamReader(newFileStream)
Dim fileContents As String = sr.ReadToEnd()
Dim fileContentsArray() As String = Split(fileContents, vbNewLine)
Dim fullSplitArray As New List(Of String)
For i As Integer = 0 To fileContentsArray.Length - 1
fullSplitArray.AddRange(Split(fileContentsArray(i).ToString, "#"))
Next
For j As Integer = 0 To fullSplitArray.Count - 1 Step 3
Dim connString As String = "server=localhost;user=root;database=jakub_project;port=3306;password=password;"
Try
Using conn As New MySqlConnection(connString)
conn.Open()
Dim command As New MySqlCommand("SELECT COUNT(word) FROM words WHERE word=#inputWord", conn)
command.Prepare()
command.Parameters.AddWithValue("#inputWord", fullSplitArray.Item(j))
Dim dataReader As MySqlDataReader = command.ExecuteReader()
While dataReader.Read()
If dataReader.Item(0) = 1 Then
MsgBox(fullSplitArray.Item(j) & " added to system.")
Else
Using conn2 As New MySqlConnection(connString)
conn2.Open()
Dim addCmd As New MySqlCommand("INSERT INTO words(word, wordType, wordFilename) VALUES(#inputWord, #inputType, #inputFilename);", conn2)
addCmd.Prepare()
addCmd.Parameters.AddWithValue("#inputWord", fullSplitArray.Item(j))
addCmd.Parameters.AddWithValue("#inputType", fullSplitArray.Item(j + 1))
addCmd.Parameters.AddWithValue("#inputFilename", fullSplitArray.Item(j + 2))
addCmd.ExecuteNonQuery()
MsgBox(fullSplitArray.Item(j) & " added to system.")
conn2.Close()
End Using
End If
End While
dataReader.Close()
conn.Close()
End Using
Catch ex As Exception
MsgBox("Error: " & ex.ToString())
End Try
Next
End If
Catch ex As Exception
MsgBox("Error: " & ex.ToString())
End Try
End Sub
A prior variant in which I did not include the second connection had given me the right result the first time, adding in eight rows. However, all successive attempts gave out errors that stem from what is supposedly a lack of that second connection. But now, all attempts with this subroutine output a table like this.
+--------+----------------------+----------------------+--------------------------+
| wordID | word | wordType | wordFilename |
+--------+----------------------+----------------------+--------------------------+
| 1 | acorn | noun | acorn.jpg |
| 2 | beach | noun | beach.jpg |
| 3 | chicken | noun | chicken.jpg |
| 4 | dance | verb | dance.jpg |
| 5 | elbow | noun | elbow.gif |
| 6 | fight | verb | fight.gif |
| 7 | grow | verb | grow.jpg |
| 8 | hat | noun | hat.jpg |
| 11 | acorn noun acorn.jpg | beach noun beach.jpg | chicken noun chicken.jpg |
| 12 | dance verb dance.jpg | elbow noun elbow.jpg | fight verb fight.gif |
+--------+----------------------+----------------------+--------------------------+
What I want is for the data to be loaded as seen in the first set of rows, rather than what I am now receiving in the bottom two. But I am unsure where the source of the problem lay.
The error messages don't really tell me what is going on, either. The first error message to appear tells me that the data is too long for the column it is attempting to insert it into. The second error tells me that the integer j in my For loop is out of bounds, which is occurring now because of the system seeming to read the whole string rather than three substrings.
If I am understanding you properly,
To me it looks like there was a possible flaw in your text file splitting sequence. Which is not noticeable in your posted code because you may have corrected the error after finding it?
As for the errors...
The first error is telling you that the data for the field in question (you did not mention which field it was) is too big to field into the allocated space. This too can be quantified as being caused by incorrect splitting
in your splitting sequence.
As for the second error it is unclear but I would assume do not get that error after you corrected the splitting issue?
Finally, it is recommended to move your Command.Prepare so it comes AFTER you have added all your command parameters.
Conclusion After Your Extra Info
After reading your comments below, I was still of the mind that the initial cause of the problems were in your splitting routine.
After use the watch tool to look at the splitting results, it was obvious what was happening. You can see the watch window below
Further more it seems to make more sense to have two separate routines for checking if the word exists and to add a new word if it doesn't exist.
By separating them out of your main processing block it makes it a lot easier to see what is going on...
So I have done that and tested it and it seems to work fine.
The main difference is that I skipped the step where you are reading in a data file and kind of simulated the contents of the in a string....
Private Sub btnCreateBank_Click(sender As Object, e As EventArgs) Handles btnCreateBank.Click
'
Dim counta As Integer = 0
Dim fullSplitArray As New List(Of String)
Dim fileContents As String = ""
Dim fileContentsArray() As String
Dim tmpWord As String = Nothing
Dim tmpType As String = Nothing
Dim tmpFile As String = Nothing
Dim Test As Boolean = False
'
Try
fileContents = "1#acorn#noun#acorn.jpg" & vbNewLine & "2#beach#noun#beach.jpg" & vbNewLine & "3#chicken#noun#chicken.jpg" & vbCrLf & "4#dance#verb#dance.jpg" & vbNewLine & "5#elbow#noun#elbow.gif" & vbNewLine & "6#fight#verb#fight.gif" & vbNewLine & "7#grow#verb#grow.jpg" & vbNewLine & "8#hat#noun#hat.jpg"
fileContentsArray = Split(fileContents, vbNewLine)
For i As Integer = 0 To fileContentsArray.Length - 1
fullSplitArray.AddRange(Split(fileContentsArray(i).ToString, "#"))
Next
For j As Integer = 0 To fullSplitArray.Count - 1 Step 4
Test = False
Try
Test = IsWordAlreadyListed(fullSplitArray(j + 1), tmpWord, tmpType, tmpFile)
If Test Then
MsgBox(fullSplitArray.Item(j + 1) & " already listed in system (" & Trim(tmpWord) & ", " & Trim(tmpType) & ", " & Trim(tmpFile) & ")")
Else
Test = AddNewRow(fullSplitArray(j + 1), fullSplitArray(j + 2), fullSplitArray(j + 3))
End If
Catch ex As Exception
MsgBox("Error: " & ex.ToString())
End Try
Next
Catch ex As Exception
MsgBox("Error: " & ex.ToString())
End Try
Test = Nothing
'
End Sub
Function IsWordAlreadyListed(ByVal ParamWord As String, ByRef pRtnWord As String, ByRef pRtnType As String, ByRef pRtnFile As String) As Boolean
'
'-> Locals
Dim iwalConn As Data.SqlClient.SqlConnection = Nothing
Dim iwalCmd As Data.SqlClient.SqlCommand = Nothing
Dim iwalAdapter As Data.SqlClient.SqlDataReader = Nothing
Dim iwalQuery As String = Nothing
'-> Init
IsWordAlreadyListed = False
iwalConn = New System.Data.SqlClient.SqlConnection()
iwalConn.ConnectionString = "YOUR-CONNECTION-STRING-HERE"
'-> Process Request
If Trim(paramword) <> "" Then
iwalQuery = "SELECT * FROM words WHERE word='" & Trim(ParamWord) & "'"
'-> Query Databanks
iwalCmd = New Data.SqlClient.SqlCommand(iwalQuery, iwalConn)
If iwalCmd.Connection.State = Data.ConnectionState.Closed Then iwalCmd.Connection.Open()
iwalAdapter = iwalCmd.ExecuteReader(Data.CommandBehavior.CloseConnection)
If iwalAdapter.HasRows Then
iwalAdapter.Read()
pRtnWord = iwalAdapter.GetValue(iwalAdapter.GetOrdinal("word"))
pRtnType = iwalAdapter.GetValue(iwalAdapter.GetOrdinal("wordType"))
pRtnFile = iwalAdapter.GetValue(iwalAdapter.GetOrdinal("wordFilename"))
IsWordAlreadyListed = True
End If
If iwalCmd.Connection.State = Data.ConnectionState.Open Then iwalCmd.Connection.Close()
Else
MsgBox("Error: Invalid or missing word parameter!")
End If
'-> tidy up
iwalCmd = Nothing
iwalAdapter = Nothing
iwalQuery = Nothing
iwalConn = Nothing
'
End Function
Function AddNewRow(ByVal ParamWord As String, ByVal ParamType As String, ParamFile As String) As Boolean
'
AddNewRow = False
Dim arnConn As System.Data.SqlClient.SqlConnection = Nothing
Dim arnConnString As String = "YOUR CONNECTION STRING HERE"
arnConn = New System.Data.SqlClient.SqlConnection(arnConnString)
arnConn.Open()
Try
Using arnConn
Dim addCmd As New System.Data.SqlClient.SqlCommand("INSERT INTO words(word, wordType, wordFilename) VALUES(#inputWord, #inputType, #inputFilename);", arnConn)
addCmd.Parameters.AddWithValue("#inputWord", ParamWord)
addCmd.Parameters.AddWithValue("#inputType", ParamType)
addCmd.Parameters.AddWithValue("#inputFilename", ParamFile)
'addCmd.Prepare()
addCmd.ExecuteNonQuery()
MsgBox(ParamWord & " added to system.")
AddNewRow = True
End Using
Catch ex As Exception
MsgBox("Error: " & ex.ToString())
Finally
arnConn.Close()
End Try
End Function

Find Lowest Value in Columns Access 2010

I have a table in Access 2010 that has 3 separate priority fields. I have a sub that looks through the columns, finds the smallest number, and puts it in an Overall Priority field.
Ex.
SubProjNo | GOPri | StrPri | SOPri
--------+-----------+----------+------------------
1234-12-01 | 100 | 7 | 61
1234-12-02 | | 18 | 2
1234-12-03 | 51 | |
ProjNo: 1234-12-00 Overall_Priority:2
I originally had the code under Private Sub Form_Current() but it slowed the program down way too much, so I moved it to an AfterUpdate for the subform that the table is in.
Private Sub MFWorkProjectssubform_AfterUpdate()
Dim MinGOPri As Variant
Dim MinStrPri As Variant
Dim MinSOPri As Variant
MinGOPri = DMin("[GOPri]", "[WorkProjects]", "WorkProjects.PROJNO = Activity.PROJNO")
MinStrPri = DMin("[StrPri]", "[WorkProjects]", "WorkProjects.PROJNO = Activity.PROJNO")
MinSOPri = DMin("[SOPri]", "[WorkProjects]", "WorkProjects.PROJNO = Activity.PROJNO")
Overall_Priority = IIf(((IIf([MinGOPri] < [MinStrPri], [MinGOPri], [MinStrPri])))
< [MinSOPri], ((IIf([MinGOPri] < [MinStrPri], [MinGOPri], [MinStrPri]))), [MinSOPri])
End Sub
The problem is, now, all the columns are cleared and only the largest value is left. Any suggestions for how to get this to work, or how to speed it up if I put it back in Form_Current would be really appreciated.
You don't need all this. For the Overall_Priority textbox use this expression as ControlSource:
=IIf(((IIf([GOPri]<[StrPri],[GOPri],[StrPri])))<[SOPri],((IIf([GOPri]<[StrPri],[GOPri],[StrPri]))),[SOPri])
Edit for Null and reduced:
=IIf(IIf(Nz([GOPri],9999)<Nz([StrPri],9999),Nz([GOPri],9999),Nz([StrPri],9999))<Nz([SOPri],9999),IIf(Nz([GOPri],9999)<Nz([StrPri],9999),Nz([GOPri],9999),Nz([StrPri],9999)),Nz([SOPri],9999))
Use this as a fourth column; name it, say, RowMin.
Then, in the footer, use =Min([RowMin]) as the controlsource for your totals box.
How about using recordsets, is this an option ?
Dim rst As Object
Dim minValue As Integer
Dim fieldCounter As Long
Dim minPriority as Long
minPriority = 9999
Set rst = Me.recordsetclone
With rst
.MoveFirst
While Not .EOF
For fieldCounter = 0 To .Fields.Count-1
if(.Fields(fieldcounter).name = "GOPri" or .Fields(fieldcounter).name = "StrPri" or .Fields(fieldcounter).name = "SOPri" ) then
Debug.print "Now you are checking : " &.Fields(fieldcounter).Name & " with value : " & .Fields(fieldcounter) & " Current minPriority = " & minPriority
If len(Nz(.Fields(fieldCounter),"")) > 0 Then
If .Fields(fieldCounter) < minPriority Then minPriority = .Fields(fieldCounter)
End If
End if
Next
.MoveNext
Wend
End With
Set rst = Nothing
Overall_Priority = minPriority
Maybe you need to adjust the fieldCounter to match your Table structure
The fields are counted from 0 -->.... according to your question.
Fields(0) = SubProjNo
Field(1) = GoPRi
If you want this column to always be a function of the other three fields, then you could create a calculated field to the table. The help documents can show you how; also this article gives directions:
https://support.office.com/en-us/article/Add-a-calculated-field-to-a-table-14a60733-2580-48c2-b402-6de54fafbde3
Generally you would define a new field on the table and set it's formula to the maximum of the other three fields. Then any time the calculated field is referenced it will always give you the maximum of them.
The other option is to just not worry about saving the field at all at the time of data entry and simply create a view that adds a field defined to be the maximum of the three values.

MS Access VBA - Recordset SQL Not returning correct amount

I have VBA code that queries a table. The Query view in MS Access returns the correct results, however the vba returns a different result.
Table called tbl_ADMIN_CLASS_INFO
Client_ID | POLICY_GROUP
12345a | 1
12345a | 2
12345a | 2
12345a | 2
12345a | 2
Column Definitions from Table:
CLIENT_ID = Text
POLICY_GROUP = Number
VBA
Public Sub NextPageControl()
Dim dbs As Database
Dim rst As Recordset
Dim CurrentTableName As String
Dim CurrentFormName As String
Dim NextPageSQL As String
Dim CurrentPage As Form
Dim LastRecord As Integer
Dim Nextpage As Integer
Dim TestPolicy As Long
Dim TestClient As String
TestPolicy = Forms!frm_ADMIN_CLASS_INFO.POLICY_GROUP 'Stepping Through Code shows 12345a
TestClient = Forms!frm_ADMIN_CLASS_INFO.CLIENT_ID 'Stepping Through Code shows 2
CurrentTableName = Screen.ActiveForm.RecordSource
CurrentFormName = Screen.ActiveForm.Name
Set CurrentPage = Screen.ActiveForm
Set dbs = CurrentDb
NextPageSQL = "SELECT * FROM " & CurrentTableName & " WHERE ((POLICY_GROUP = " & TestPolicy & ") AND (CLIENT_ID = '" & TestClient & "'))"
Debug.Print NextPageSQL
Set rst = CurrentDb.OpenRecordset(NextPageSQL, dbOpenDynaset)
MsgBox rst.RecordCount
...More Stuff
The problem is that this VBA returns 5 for rst.RecordCount when it should return 4....
Firstly, the following instruction is not optimal as it can fool you if you're in the case where your form's record source is a SQL query and not a table:
CurrentTableName = Screen.ActiveForm.RecordSource
So make sure that this instruction retruns the correct table name.
Secondly: using recordset.recordcount might not return the expected results depending on your cursor type and the datasource. From microsoft:
The cursor type of the Recordset object affects whether the number of
records can be determined. The RecordCount property will return -1 for
a forward-only cursor; the actual count for a static or keyset cursor;
and either -1 or the actual count for a dynamic cursor, depending on
the data source.
So to be sure you're always returning the correct amount of records, do a :
rst.movelast
prior to do the recordcount
Your code should work fine after those corrections.