Web Scraping ETFs Daily Data VBA - PullRequest
       72

Web Scraping ETFs Daily Data VBA

0 голосов
/ 24 октября 2018

Я пытаюсь найти в сети некоторую ежедневную информацию о различных ETF.Я обнаружил, что https://www.marketwatch.com/ имеет точную информацию.Наиболее релевантная информация - это открытая цена, размещенные акции, NAV, суммарные активы ETF.Вот ссылка на IVV US Equity: https://www.marketwatch.com/investing/fund/ivv

Раньше я просматривал веб-страницы с помощью VBA, но HTML-код страниц, которые я использовал, отличается, я не знаю, так ли это, потому что некоторые значенияETF (такие как Price и Taded Volume) постоянно меняются.Идея состоит в том, чтобы создать код для извлечения соответствующей информации и создать базу данных для анализа макроэкономического фактора с использованием ETF в качестве рыночных индикаторов потоков между странами, регионами и т. Д. ...

Mi первый подход будет с VBAно после того, как я получу больше информации, я хотел бы попробовать с Python (после того, как я получу больше уверенности в этом), чтобы автоматизировать процесс webscraping ежедневно.

Я открыт для любого предложения или любого другого веб-сайтаэто может быть полезно (я пробовал с Yahoo Finance и Morningstar, и у меня та же проблема с кодом HTML).

Это мой плохой код:

Sub Get_Data()

    Dim ticker As String, enlace As String

    ticker = ThisWorkbook.Worksheets("ETFs").Cells(2, 2).Value 'IVV
    'link = "https://www.morningstar.com/etfs/arcx/" & ticker & "/quote.html"
    'link = "https://finance.yahoo.com/quote/" & ticker & "?p=" & ticker
    link = "https://www.marketwatch.com/investing/fund/" & ticker

    Application.ScreenUpdating = False

    Dim x As Integer
    x = ThisWorkbook.Worksheets("ETFs").Cells(Rows.Count, 1).End(xlUp).Row

    'Dim i As Integer
    'For i = 2 To x

    Dim total_net_assets As Variant, open_price As Variant, NAV As Variant, shares_out

    Set ie = CreateObject("InternetExplorer.application")
    With ie
        .Visible = False
        .navigate link
        While .Busy Or .readyState < 4: DoEvents: Wend
            Do
                DoEvents
                On Error Resume Next
                ' Here is where I get the problem of not knowing how to reference the values I need because the class name appears repeatedly
                total_net_assets = .document.getElementsByClassName("").Value
                open_price = .document.getElementByClassName("price").Value
                NAV = .document.getElementByClassName("").Value
                shares_out = .document.getElementByClassName("kv__value kv__primary ").Value
                On Error GoTo 0
            Loop
    End With
    ThisWorkbook.Worksheets("ETFs").Cells(2, 13).Value = total_net_assets
    ThisWorkbook.Worksheets("ETFs").Cells(2, 14).Value = NAV
    ThisWorkbook.Worksheets("ETFs").Cells(2, 15).Value = open_price
    ThisWorkbook.Worksheets("ETFs").Cells(2, 16).Value = shares_out
    ie.Quit
    'Next i
    Application.ScreenUpdating = True

End Sub

Ответы [ 2 ]

0 голосов
/ 24 октября 2018

Метод доступа:

Я использую XMLHTTP запросов намного быстрее, чем открытие IE.

Примечания к коду:

Следующее читает в коротких кодах фонда из столбца A листа 1, начиная с A2, в массив.Вы можете легко расширить это добавление дополнительных средств в столбец A.

Этот массив зацикливает выдачу запросов XMLHTTP, объединяя код фонда в переменную BASE_URL.

Я использую класс, clsHTTP, чтобы объект XMLHTTP был эффективным - не нужно продолжать создавать и уничтожать объект.

Я предоставляю этот класс двумя методами.Один для извлечения целевой страницы innerHTML (GetString), а другой для извлечения необходимой информации, если она доступна (GetInfo).Я использую словарь для проверки наличия искомых ярлыков.Если присутствует, я беру соответствующее значение.Если нет, у меня есть заполнитель vbNullString в словаре.

Я добавляю каждый полученный результат в коллекцию под названием results.В конце я записываю эту запись на лист.Сохраняя большую часть работы в памяти, это обеспечивает намного более быструю очистку.


Получение информации из HTML:

labels Например Openvalues идут парами.

Вы можете сгенерировать nodeList (представьте коллекцию как с getElementsByClassName), используя метод querySelectorAll, чтобы применить селектор CSS класса для сбораэлементы метки по имени класса kv__label."." является селектором класса.

Set labels = .querySelectorAll(".kv__label") '<== nodeList of labels

Вы делаете то же самое, чтобы получить связанные значения:

Set values = .querySelectorAll(".kv__value.kv__primary") '<== nodeList of associated values. Same length as labels nodeList so can use same index to retrieve associated label/value pairs from each nodeList.

Вы зацикливаете метки, используя словарь в методе clsHTTP.GetInfo чтобы увидеть, если вы искали метки, если они есть, соответствующее значение извлекается из значений с использованием того же индекса, что и метка, найденная в nodeList labels, и словаря vbNullStringзначение для этой метки обновляется до фактического извлеченного значения, в противном случае оно остается vbNullString.


Примеры результатов:

enter image description here

VBA:

Модуль класса clsHTTP:

Option Explicit
Private http As Object

Private Sub Class_Initialize()
    Set http = CreateObject("MSXML2.XMLHTTP")
End Sub

Public Function GetString(ByVal url As String) As String
    Dim sResponse As String
    With http
        .Open "GET", url, False
        .setRequestHeader "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"
        .send
        sResponse = StrConv(.responseBody, vbUnicode)
        GetString = sResponse
    End With
End Function

Public Function GetInfo(ByVal html As HTMLDocument) As Object
    Dim dict As Object, i As Long
    Set dict = CreateObject("Scripting.Dictionary")
    dict.Add "Open", vbNullString
    dict.Add "Shares Outstanding", vbNullString
    dict.Add "Total Net Assets", vbNullString
    dict.Add "NAV", vbNullString

    Dim values As Object, labels As Object

    With html
        Set values = .querySelectorAll(".kv__value.kv__primary")
        Set labels = .querySelectorAll(".kv__label")

        For i = 0 To labels.Length - 1
            If dict.Exists(labels.item(i).innerText) Then dict(labels.item(i).innerText) = values.item(i).innerText
        Next
    End With
    Set GetInfo = dict
End Function

Стандартный модуль 1:

Option Explicit   
Public Sub GetFundInfo()
    Dim sResponse As String, html As HTMLDocument, http As clsHTTP, i As Long
    Dim headers(), funds(), url As String, results As Collection, ws As Worksheet
    Const BASE_URL As String = "https://www.marketwatch.com/investing/fund/"

    Application.ScreenUpdating = False

    headers = Array("Open", "Shares Outstanding", "Total Net Assets", "NAV")
    Set results = New Collection
    Set http = New clsHTTP
    Set ws = ThisWorkbook.Worksheets("Sheet1")
    Set html = New HTMLDocument

    funds = Application.Transpose(ws.Range("A2:A3").Value) '<== Change the range here to the single column range containing your dotNums.

    For i = LBound(funds) To UBound(funds)
        If Not IsEmpty(funds(i)) Then
            url = BASE_URL & funds(i)
            html.body.innerHTML = http.GetString(url)
            results.Add http.GetInfo(html).Items
        End If
    Next

    If results.Count > 0 Then
        Dim item As Variant, r As Long, c As Long
        r = 2: c = 2
        With ws
            .Cells(1, c).Resize(1, UBound(headers) + 1) = headers
            For Each item In results
                .Cells(r, c).Resize(1, UBound(item) + 1) = item
                r = r + 1
            Next
        End With
    End If
    Application.ScreenUpdating = True
End Sub

Настройка:

enter image description here


Без использования класса:

Option Explicit

Public Sub GetFundInfo()
    Dim sResponse As String, html As HTMLDocument,  i As Long
    Dim headers(), funds(), url As String, results As Collection, ws As Worksheet
    Const BASE_URL As String = "https://www.marketwatch.com/investing/fund/"

    Application.ScreenUpdating = False

    headers = Array("Open", "Shares Outstanding", "Total Net Assets", "NAV")
    Set results = New Collection
    Set ws = ThisWorkbook.Worksheets("Sheet1")
    Set html = New HTMLDocument

    funds = Application.Transpose(ws.Range("A2:A3").Value) '<== Change the range here to the single column range containing your dotNums.

    For i = LBound(funds) To UBound(funds)
        If Not IsEmpty(funds(i)) Then
            url = BASE_URL & funds(i)
            html.body.innerHTML = GetString(url)
            results.Add GetInfo(html).Items
        End If
    Next

    If results.Count > 0 Then
        Dim item As Variant, r As Long, c As Long
        r = 2: c = 2
        With ws
            .Cells(1, c).Resize(1, UBound(headers) + 1) = headers
            For Each item In results
                .Cells(r, c).Resize(1, UBound(item) + 1) = item
                r = r + 1
            Next
        End With
    End If
    Application.ScreenUpdating = True
End Sub


Public Function GetString(ByVal url As String) As String
    Dim http As Object
    Set http = CreateObject("MSXML2.XMLHTTP")
    Dim sResponse As String
    With http
        .Open "GET", url, False
        .setRequestHeader "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"
        .send
        sResponse = StrConv(.responseBody, vbUnicode)
        GetString = sResponse
    End With
End Function

Public Function GetInfo(ByVal html As HTMLDocument) As Object
    Dim dict As Object, i As Long
    Set dict = CreateObject("Scripting.Dictionary")
    dict.Add "Open", vbNullString
    dict.Add "Shares Outstanding", vbNullString
    dict.Add "Total Net Assets", vbNullString
    dict.Add "NAV", vbNullString

    Dim values As Object, labels As Object

    With html
        Set values = .querySelectorAll(".kv__value.kv__primary")
        Set labels = .querySelectorAll(".kv__label")

        For i = 0 To labels.Length - 1
            If dict.Exists(labels.item(i).innerText) Then dict(labels.item(i).innerText) = values.item(i).innerText
        Next
    End With
    Set GetInfo = dict
End Function
0 голосов
/ 24 октября 2018

Хорошо, вам нужно создать две петли.Вы можете просто продолжать использовать переменные elem0, elem1 и elemColl(1) для каждой необходимой вам цены - просто убедитесь, что для каждой новой итерации bFoundIt установлено значение False, чтобы не выходить из цикла For Loops раньше.

Для вашего total_net_assets var вы сначала зациклите класс kv__item.Затем вам нужно будет зациклить каждую коллекцию классов kv__label в элементах kv__item и остановиться при совпадении с innerText: Всего чистых активов .Как только вы сопоставите это, вы будете использовать первый столбец obj elem0, чтобы получить для него имя класса kv__value kv__primary.

Dim IE As Object, elem0 As Object, elem1 As Object, i As Long, bFoundIt As Boolean

Set IE = CreateObject("InternetExplorer.application")
With IE
    .Visible = False
    .navigate link
    While .Busy Or .readyState < 4: DoEvents: Wend
        DoEvents
        bFoundIt = False
        For Each elem0 In .document.getElementsByClassName("kv__item")
            For Each elem1 In elem0.getElementsByClassName("kv__label")
                If elem1.innerText = "Total Net Assets" Then
                    bFoundIt = True
                    total_net_assets = elem0.getElementsByClassName("kv__value kv__primary ")(0).innerText
                    Exit For
                End If
            Next elem1
            If bFoundIt Then Exit For
        Next elem0
...