VBA Web Scraping using getElementsByClassName to names and addresses - html

I'm trying to extract the clinic name and corresponding address for all the clinics from the following web page: https://medimap.ca/Location/Calgary,%20AB,%20Canada
I'm having issues locating the exact area where I should be drilling down into. All the clinic names have the same class name of "_1FLG5" and the addresses are all "_1-Gov" . However, when I run through the below code nothing happens - no errors just nothing.
I'm also unsure if the reference after .getElementsByClassName is correct, as I want the inner text from the same row as where the "_1FLG5" is I referenced (0) and since I wanted the text from two rows below "_1-Gov" I referenced (2).
Option Explicit
Sub GetClinicData()
Dim objIE As InternetExplorer
Dim clinicEle As Object
Dim clinicAdd As Object
Dim clinicName As String
Dim address As String
Dim y As Integer
Dim x As Integer
Set objIE = New InternetExplorer
objIE.Visible = False
objIE.navigate "https://medimap.ca/Location/Calgary,%20AB,%20Canada"
Do While objIE.Busy = True Or objIE.readyState <> 4: DoEvents: Loop
y = 1
For Each clinicEle In objIE.document.getElementsByClassName("_1FLG5")
clinicName = clinicEle.getElementsByClassName("_1FLG5")(0).innerText
Sheets("Sheet1").Range("A" & y).Value = clinicName
y = y + 1
Next
x = 1
For Each clinicAdd In objIE.document.getElementsByClassName("_1-Gov")
clinicAdd = clinicAdd.getElementsByClassName("_1-Gov")(2).innerText
Sheets("Sheet1").Range("B" & x).Value = clinicAdd
x = x + 1
Next
End Sub

Content is dynamically loaded so you need a wait condition to ensure content loaded - otherwise your collections end up being of length 0. I use querySelectorAll to apply the class names which return nodeList you For Loop over the .Length of. Ideally you should add a timeout condition to the loop. I show a timed loop here.
Option Explicit
'VBE > Tools > References: Microsoft Internet Controls
Public Sub GetData()
Dim ie As Object
Set ie = CreateObject("InternetExplorer.Application")
With ie
.Visible = True
.Navigate2 "https://medimap.ca/Location/Calgary,%20AB,%20Canada"
While .Busy Or .readyState < 4: DoEvents: Wend
Dim clinics As Object, addresses As Object, i As Long
With .document
Do
Set clinics = .querySelectorAll("._1FLG5")
Set addresses = .querySelectorAll("._1-Gov")
Loop While clinics.Length = 0
For i = 0 To clinics.Length - 1
With ThisWorkbook.Worksheets("Sheet1")
.Cells(i + 1, 1) = Trim$(clinics.item(i).innerText)
.Cells(i + 1, 2) = Trim$(addresses.item(i).innerText)
End With
Next
End With
.Quit
End With
End Sub

Related

How to get data from class name inside a id name in excel VBA?

How can I get data from the classes date and number (all 3) data?
Sub Fii_dii()
Set IE = CreateObject("InternetExplorer.Application")
IE.Visible = True
IE.Navigate "https://www.nseindia.com/products/content/equities/equities/fii_dii_market_today.htm"
While IE.readyState <> 4
DoEvents
Wend
'Copy data
MsgBox IE.Document.all.Item("fiiTable").innerText
End Sub
Those values are retrieved by the browser dynamically from another URI you can find in the network tab of the browser when refreshing the page. I would simply issue an xmlhttp request (faster and no browser) and use css class selectors to match on the required nodes by class. Loop the returned nodeList and write out to Excel
Option Explicit
Public Sub ScrapeValues()
Dim html As HTMLDocument, values As Object, i As Long, ws As Worksheet
Set html = New HTMLDocument
Set ws = ThisWorkbook.Worksheets("Sheet1")
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://www.nseindia.com/products/dynaContent/equities/equities/htms/fiiEQ.htm", False
.send
html.body.innerHTML = .responseText
End With
Set values = html.querySelectorAll(".date, .number")
For i = 0 To values.Length - 1
With ws
.Cells(i + 1, 1) = values.Item(i).innerText
End With
Next
End Sub
If you want to use IE you need to have a test for those elements to be present and a timed loop e.g.
Option Explicit
Public Sub ScrapeValues()
Dim ie As InternetExplorer, values As Object, i As Long, ws As Worksheet, t As Date
Const MAX_WAIT_SEC As Long = 5 '<==adjust time here
Set ie = New InternetExplorer
Set ws = ThisWorkbook.Worksheets("Sheet1")
With ie
.Visible = True
.Navigate2 "https://www.nseindia.com/products/content/equities/equities/fii_dii_market_today.htm"
While .Busy Or .readyState <> 4: DoEvents: Wend
t = Timer
Do
Set values = .document.querySelectorAll(".date, .number")
If Timer - t > MAX_WAIT_SEC Then Exit Do
Loop While values.Length = 0
If values.Length > 0 Then
For i = 0 To values.Length - 1
With ws
.Cells(i + 1, 1) = values.Item(i).innerText
End With
Next
End If
.Quit
End With
End Sub
--
References (VBE>Tools>References):
Microsoft HTML Object Library
Microsoft Internet Controls

using excel VBA to grab data from a web page that runs scrpits to show the table data

Day two of researching this. I'm just not getting it. The web page is public:
https://register.fca.org.uk/ShPo_FirmDetailsPage?id=001b000000MfF1EAAV
Manually I pgdn x 2 to get to the button [+] Individuals, click it then pgdn x 1 to get to the "results per page" drop down and change it to 500. then copy and paste the results into excel
this is the code that I found on this site "Selecting a dropdown list when inserting data from web (VBA)" answered by QHarr which I tried to adapt and failed miserably. I put "HELP" where I think I should be making the changes but I'm just guessing
Public Sub MakeSelectiongGetData()
Dim IE As New InternetExplorer
Const URL = "https://register.fca.org.uk/ShPo_FirmDetailsPage?id=001b000000Mfe5TAAR#ShPo_FirmDetailsPage"
'Const optionText As String = "RDVT11"
Application.ScreenUpdating = False
With IE
.Visible = True
.navigate URL
While .Busy Or .readyState < 4: DoEvents: Wend
Dim a As Object
Set a = .document.getElementById("HELP")
Dim currentOption As Object
For Each currentOption In a.getElementsByTagName("HELP")
If InStr(currentOption.innerText, optionText) > 0 Then
currentOption.Selected = "HELP"
Exit For
End If
Next currentOption
.document.getElementById("HELP").Click
While .Busy Or .readyState < 4: DoEvents: Wend
Dim nTable As HTMLTable
Do: On Error Resume Next: Set nTable = .document.getElementById("HELP"): On Error GoTo 0: DoEvents: Loop While nTable Is Nothing
Dim nRow As Object, nCell As Object, r As Long, c As Long
With ActiveSheet
Dim nBody As Object
Set nBody = nTable.getElementsByTagName("tbody")(0).getElementsByTagName("tr")
.Cells(1, 1) = nBody(0).innerText
For r = 2 To nBody.Length - 1
Set nRow = nBody(r)
For Each nCell In nRow.Cells
c = c + 1: .Cells(r + 1, c) = nCell.innerText
Next nCell
c = 0
Next r
End With
.Quit
End With
Application.ScreenUpdating = True
End Sub
So I have included your changes and am here.
Public Sub MakeSelections()
Dim IE As New InternetExplorer
With IE
.Visible = True
.Navigate2 "https://register.fca.org.uk/ShPo_FirmDetailsPage?id=001b000000MfF1EAAV"
While .Busy Or .readyState < 4: DoEvents: Wend
.document.querySelector("[href*=FirmIndiv]").Click '<==click the + for indiv
.document.querySelector("#IndividualSearchResults_length[value='500']").Selected = True
End With
Dim nTable As HTMLTable
Do: On Error Resume Next: Set nTable =IE.document.getElementById("IndividualSearchResults"): On Error GoTo 0: DoEvents: Loop While nTable Is Nothing
Dim nRow As Object, nCell As Object, r As Long, c As Long
With ActiveSheet
Dim nBody As Object
Set nBody = nTable.getElementsByTagName("Name")(0) _
.getElementsByTagName("ShG1_IRN_c") _
.getElementsByTagName("ShGl_IndividualStatus__c") _
.getElementsByTagName("ShPo_Registerstatus__c") _
.getElementsByTagName("Id") _
.getElementsByTagName("RecordTypeId") _
.getElementsByTagName("CurrencyIsoCode") _
.Cells(1, 1) = nBody(0).innerText
For r = 2 To nBody.Length - 1
Set nRow = nBody(r)
For Each nCell In nRow.Cells
c = c + 1: .Cells(r + 1, c) = nCell.innerText
Next nCell
c = 0
Next r
End With
End Sub
You can use css attribute = value selectors to target the + for individuals and also to make the option selection for 500
Option Explicit
'VBE > Tools > References:
' Microsoft Internet Controls
Public Sub MakeSelections()
Dim IE As New InternetExplorer
With IE
.Visible = True
.Navigate2 "https://register.fca.org.uk/ShPo_FirmDetailsPage?id=001b000000MfF1EAAV"
While .Busy Or .readyState < 4: DoEvents: Wend
.document.querySelector("[href*=FirmIndiv]").Click '<==click the + for indiv
.document.querySelector("#IndividualSearchResults_length [value='500']").Selected = True
Dim event_onchange As Object
Set event_onchange = .document.createEvent("HTMLEvents")
event_onchange.initEvent "change", True, False
.document.querySelector("[name=IndividualSearchResults_length]").dispatchEvent event_onchange
Application.Wait Now + TimeSerial(0, 0, 5)
Dim clipboard As Object, ws As Worksheet
Set clipboard = GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
Set ws = ThisWorkbook.Worksheets("Sheet1")
clipboard.SetText .document.querySelector("#IndividualSearchResults").outerHTML
clipboard.PutInClipboard
ws.Cells(1, 1).PasteSpecial
.Quit
End With
End Sub
This selector, [href*=FirmIndiv], is an attribute = value selector with contains (*) modifier. It looks for the matches for href attributes that contain the substring FirmIndiv in the href value. querySelector all method of HTMLDocument *(ie.Document) will return the first match found.
You can see the match here:
The selector for the option tag element (the parent select tag for result counts contains child option tag elements):
#IndividualSearchResults_length [value='500']
It uses an id (#) selector to target the div parent, of the parent select element, by its id value IndividualSearchResults_length, then uses a descendant combinator (" ") followed by attribute = value selector to specify the option element with value = 500.
You can see that here:
Selenium basic version:
Option Explicit
Public Sub MakeChanges()
'VBE > Tools > References > Selenium Type Library
'Download: https://github.com/florentbr/SeleniumBasic/releases/tag/v2.0.9.0
Dim d As WebDriver
Set d = New ChromeDriver
Const url = "https://register.fca.org.uk/ShPo_FirmDetailsPage?id=001b000000MfF1EAAV"
With d
.Start "Chrome"
.get url
.FindElementByCss("[href*=FirmIndiv]").Click
.FindElementByCss("[name=IndividualSearchResults_length]").WaitDisplayed True, 10000
.FindElementByCss("[name=IndividualSearchResults_length]").AsSelect.SelectByValue "500"
Stop '<==delete me later
.Quit
End With
End Sub

How to scrape address information from Google Maps?

I'm trying to create a macro that pulls a list of addresses from Excel and inputs each one into Google Maps.
It then pulls the address line, city/zip and country from Google Maps back into Excel.
It works up to the point where it scrapes the information from Google Maps.
Sub AddressLookup()
Application.ScreenUpdating = False
For i = 1 To Sheet1.Cells(Rows.Count, 1).End(xlUp).Row
Dim IE As InternetExplorer
Dim itemELE As Object
Dim address As String
Dim city As String
Dim country As String
Set IE = New InternetExplorer
IE.Visible = True
IE.navigate "https://www.google.com/maps"
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
Dim Search As MSHTML.HTMLDocument
Set Search = IE.document
Search.all.q.Value = Cells(i, 1).Value
Dim ele As MSHTML.IHTMLElement
Dim eles As MSHTML.IHTMLElementCollection
Set eles = Search.getElementsByTagName("button")
For Each ele In eles
If ele.ID = "searchbox-searchbutton" Then
ele.click
Else
End If
Next ele
For Each itemELE In IE.document.getElementsByClassName("widget-pane widget-pane-visible")
address = itemELE.getElementsByClassName("section-hero-header-description")(0).getElementsByTagName("h1")(0).innerText
city = itemELE.getElementsByClassName("section-hero-header-description")(0).getElementsByTagName("h2")(0).innerText
country = itemELE.getElementsByClassName("section-hero-header-description")(0).getElementsByTagName("h2")(1).innerText
Next
Cells(i, 2).Value = Trim(address)
Cells(i, 3).Value = Trim(city)
Cells(i, 4).Value = Trim(country)
MsgBox country
Next
Application.ScreenUpdating = True
End Sub
This answer uses the OpenStreetMap Nominatim API with the VBA-Web WebRequest.
In opposite to scraping withInternet Explorerthis is designed for this purpose (faster, more reliable, more information). This can be done with the Geocode API too, but you need an API-Key and keep track of the cost.
If you use https://nominatim.openstreetmap.org/search respect their Usage Policy, but better have your own installation.
Public Function GeocodeRequestNominatim(ByVal sAddress As String) As Dictionary
Dim Client As New WebClient
Client.BaseUrl = "https://nominatim.openstreetmap.org/"
Dim Request As New WebRequest
Dim Response As WebResponse
Dim address As Dictionary
With Request
.Resource = "search/"
.AddQuerystringParam "q", sAddress
.AddQuerystringParam "format", "json"
.AddQuerystringParam "polygon", "1"
.AddQuerystringParam "addressdetails", "1"
End With
Set Response = Client.Execute(Request)
If Response.StatusCode = WebStatusCode.Ok Then
Set address = Response.Data(1)("address")
Set GeocodeRequestNominatim = address
'Dim Part As Variant
'For Each Part In address.Items
' Debug.Print Part
'Next Part
Else
Debug.Print "Error: " & Response.StatusCode & " - " & Response.Content
End If
End Function
Example (prints country, for other fields have a look at the returned JSON-String in example on Nomination Website):
Debug.Print GeocodeRequestNominatim("united nations headquarters,USA")("country")
The geocoding API is no longer "free" though I actually believe with a billing account set-up you can scrape for free if you remain within a certain threshold. As a new release (maps/APIs has been updated) I think the expectation is that these APIs are used in conjunction with actual maps (but don't quote me on that).
Please note the following:
1) Use a proper wait for page load and after .click
While ie.Busy Or ie.readyState < 4: DoEvents: Wend
2) Use .Navigate2 rather than .Navigate
3) Use ids as faster for selections. They generally are unique so no looping required
4) In this case additional time is needed to allow for url to update and map to zoom etc. I have added a timed loop for this. I show a single example as it is clear you know how to loop.
Option Explicit
Public Sub GetInfo()
Dim ie As New InternetExplorer, arr() As String, address As String, city As String, country As String
Dim addressElement As Object, t As Date, result As String
Const MAX_WAIT_SEC As Long = 10 '<==adjust time here
With ie
.Visible = True
.Navigate2 "https://www.google.com/maps"
While .Busy Or .readyState < 4: DoEvents: Wend
With .document
.querySelector("#searchboxinput").Value = "united nations headquarters,USA"
.querySelector("#searchbox-searchbutton").Click
End With
While .Busy Or .readyState < 4: DoEvents: Wend
t = Timer
Do
DoEvents
On Error Resume Next
Set addressElement = .document.querySelector(".section-info-line span.widget-pane-link")
result = addressElement.innerText
If Timer - t > MAX_WAIT_SEC Then Exit Do
On Error GoTo 0
Loop While addressElement Is Nothing
If InStr(result, ",") > 0 Then
arr = Split(result, ",")
address = arr(0)
city = arr(1)
country = arr(2)
With ActiveSheet
.Cells(1, 2).Value = Trim$(address)
.Cells(1, 3).Value = Trim$(city)
.Cells(1, 4).Value = Trim$(country)
End With
End If
Debug.Print .document.URL
.Quit
End With
End Sub
In terms of selectors -
Commercial addresses:
.section-info-line span.widget-pane-link
And feedback from OP re: residential:
.section-hero-header div.section-hero-header-description
After running your code and inspecting Google's address search result, I was able to retrieve the entire address block 'City, Province Postal_Code' by referencing the span tag inside the section-hero-header-subtitle class.
Without making any other changes to your code, add the following line above your For-Each loop (that loops through widget-pane widget-pane visible class) and step thru the code using F8.
Debug.Print IE.Document.getElementsByClassName("section-hero-header-subtitle")(0).getElementsByTagName("span")(0).innerText

Need help getting table from HTML

I had been successfully pulling mutual fund performance data from Marketwatch.com using the following code:
Dim A As Long
Dim B As Long
Dim C As Long
Dim Z As Long
For Z = 1 To 35
Range("A1").Select
ActiveCell.Offset((37 + (Z * 10)), 0).Select
If ActiveCell.Value = "" Then
Exit For
Else
End If
Dim oHTML As Object
Dim oTable As Object
Dim x As Long
Dim Y As Long
Dim vData As Variant
Set oHTML = CreateObject("HTMLFile")
With CreateObject("WinHTTP.WinHTTPRequest.5.1")
.Open "GET", "http://www.marketwatch.com/investing/fund/vfinx", False
.send
oHTML.body.innerhtml = .responsetext
End With
For Each oTable In oHTML.Getelementsbytagname("table")
If oTable.classname = "fundstable" Then
ReDim vData(1 To oTable.Rows.Length, 1 To oTable.Rows(1).Cells.Length)
For x = 1 To UBound(vData)
For Y = 1 To UBound(vData, 2)
vData(x, Y) = oTable.Rows(x - 1).Cells(Y - 1).innertext
Next Y
Next x
With ActiveCell.Offset(1, 0)
.Resize(UBound(vData), UBound(vData, 2)).Value = vData
End With
Exit For
End If
Next oTable
Next Z
Unfortunately, Marketwatch has added a Captcha to stop bots (i.e. me) from scraping their data. I don't know of anyway around this, so I figured I'd try another site.
I looked at Morningstar: http://performance.morningstar.com/fund/performance-return.action?t=VFINX&region=usa&culture=en_US
It appears that the table I want on that page would be: "table.r_table3 width955px print97" or just "r_table3 width955px print97", but neither one seems to work for me.
Any ideas?
Thanks!
The data is loaded by javascript and won't be available via XMLHTTP request as scripts won't have run to load content.
You can use that second link, for example, with IE and introduce a wait to ensure info is loaded. I show getting the table with that class name at index 1. You can change the index here:
ele.item(1).outerHTML
So, for the next table use clipboard.SetText ele.item(2).outerHTML .
You can also loop the .Length of ele to get each table but ensure you write out to a different cell when you paste:
Dim i As Long
For i = 0 To ele.Length-1
clipboard.SetText ele.item(i).outerHTML
'Etc
Next
VBA:
Option Explicit
Public Sub GetInfo()
Dim IE As New InternetExplorer, clipboard As Object
Dim ele As Object, ws As Worksheet, t As Date, tableCount As Long
Const MAX_WAIT_SEC As Long = 5
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set clipboard = GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
With IE
.Visible = True
.navigate "http://performance.morningstar.com/fund/performance-return.action?t=VFINX&region=usa&culture=en_US"
While .Busy Or .readyState < 4: DoEvents: Wend
With .document
t = Timer
Do
DoEvents
On Error Resume Next
Set ele = .querySelectorAll(".r_table3.print97")
tableCount = ele.Length
On Error GoTo 0
If Timer - t > MAX_WAIT_SEC Then Exit Do
Loop While tableCount < 3
If Not ele Is Nothing Then
clipboard.SetText ele.item(1).outerHTML
clipboard.PutInClipboard
ws.Cells(1, 1).PasteSpecial
End If
End With
.Quit
End With
End Sub

VBA Get HTML Element Info for Changing Id

I am trying to create an excel web scraper that logs into my companies ticket tracking system and logs certain information on the sheet (Lead assigned, Desired Date for the project, etc.). I was doing fine until I had to pull a field off the website that has a changing ID.
For example, on two pages the same field will have the IDs:
"cq_widget_CqFilteringSelect_32"
"cq_widget_CqFilteringSelect_9"
Can somebody provide guidance to how I should search and paste the "IT Lead" value into excel?
HTML snippet of div
Snippet of actual website
Setup in excel
Below is what I have so far
I get confused in this area:
lead = objCollection(i).Value
Sub CQscrub()
Dim i As Long
Dim objElement As Object
Dim objCollection As Object
Dim objCollection2 As Object
Dim ie As InternetExplorer
Dim html As HTMLDocument
Dim numbers() As String
Dim size As Integer
Dim row As Integer
Dim objLead As Object
Dim objLead2 As Object
Dim lead As String
Dim counter As Integer
size = WorksheetFunction.CountA(Worksheets(1).Columns(1)) - 4
ReDim numbers(size)
For row = 10 To (size + 10)
numbers(row - 10) = Cells(row, 1).Value
'Cells(row, 2) = numbers(row - 10)
Next row
Set ie = New InternetExplorer
ie.Height = 1000
ie.Width = 1000
ie.Visible = True
ie.navigate "http://clearquest/cqweb/"
Application.StatusBar = "Loading http://clearquest/cqweb"
Do While ie.Busy
Application.Wait DateAdd("s", 1, Now)
Loop
Application.StatusBar = "Searching form. Please wait..."
'Had these below as comment
Dim WRnumber1 As String
WRnumber1 = Range("A10").Value
'Range("A6").Value = WRnumber1
Dim iLastRow As Integer
Dim Rng As Range
iLastRow = Cells(Rows.Count, "a").End(xlUp).row 'last row of A
'Set objCollection = ie.document.getElementsByTagName("input") originally here
For counter = 0 To size - 1
Set objCollection = ie.document.getElementsByTagName("input")
i = 0
While i < objCollection.Length
If objCollection(i).Name = "cqFindRecordString" Then
objCollection(i).Value = numbers(counter)
End If
i = i + 1
Wend
'''''''''''''''''' Find Label ''''''''''''''''''''''''''''
Set objCollection = ie.document.getElementsByTagName("label")
i = 0
While i < objCollection.Length
If objCollection(i).innerText = "IT Lead/Assigned To" Then
lead = objCollection(i).Value
'Set objLead = objCollection(i)
End If
i = i + 1
Wend
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Range("B" & (iLastRow - (size - counter - 1))).Value = lead
Set objElement = ie.document.getElementById("cqFindRecordButton")
objElement.Click
Do While ie.Busy
Application.Wait DateAdd("s", 1, Now)
Loop
Application.Wait (Now + TimeValue("0:00:02"))
Next counter
ie.Quit
Set ie = Nothing
Set objElement = Nothing
Set objCollection = Nothing
Application.StatusBar = ""
MsgBox "Done!"
End Sub
Note: Website is internal only
Goal: Select Name under "IT Lead/Assigned To" field and paste to Excel
Thanks
Regarding the supplied code, tl;dr.
But if you are wanting the scratched out portion you supplied in your HTML snippet, the following may work (I can't test something that I don't have access to :D).
There are many different ways to grab an element, and this method you are grabbing the first instance of the class name dijitReset dijitInputField dijitInputContainer. Class names are not always a unique value, but due to the somewhat complexity of this class name, I feel somewhat safe that in your case it is.
You could have used one line to Set yourObj... but for demonstration purposes I decided to break it up. 1-liner method to Set your obj:
Set yourObj = doc.getElementsByClassName("dijitReset dijitInputField dijitInputContainer")(0).getElementsByTagName("input")(1)
Code Snippet:
Sub getElementFromIE()
Dim ie As InternetExplorer
' ... your above code pulls up webpage ...
'''''''''''''''''' Find Label ''''''''''''''''''''''''''''
Dim doc As HTMLDocument, yourObj As Object
Set doc = ie.document
' I assume the class name is unique? If so, just append (0) as I did below
Set yourObj = doc.getElementsByClassName("dijitReset dijitInputField dijitInputContainer")(0)
Set yourObj = yourObj.getElementsByTagName("input")(1)
lead = yourObj.Value
End Sub
The reason for the (1) on Set yourObj = yourObj.getElementsByTagName("input")(1) is because there are 2 input tags after your class dijitReset.... You are wanting the 2nd instance of this tag, which contains your value; and as you are probably already aware, you are using Base 0, meaning the 2nd instance is actually the number 1.