All posts by admin

Lokaal HTML-bestand parseren zonder Internet Explorer

Je kunt de Microsoft HTML Object Library gebruiken om een ​​lokaal HTML-bestand te parseren zonder Internet Explorer te gebruiken. Wat betekent dat? Je kunt een lokaal HTML-bestand openen met een webbrowser om het weer te geven of een teksteditor (Kladblok) om de HTML-code te bekijken, aan te passen of om er specifieke data uit te halen. Dat wordt ook wel webscraping genoemd. Bijvoorbeeld prijzen ophalen.

Iedereen die wel eens webscraping heeft gedaan, deed dat door op de achtergrond Internet Explorer te starten om naar een webadres te navigeren. Zodra de pagina klaar is, begint het navigeren door de zogenaamde DOM. Met de term DOM (Document Object Model) HTML wordt bedoeld dat de HTML-code van een webpagina omgezet wordt in een boomstructuur van objecten met behulp van de ‘Microsoft HTML Object Library’ (MSHTML). Maar wat te doen als IE niet beschikbaar is?

Je hebt een lokaal bestand nodig om mee te werken en een verwijzing naar de ‘Microsoft HTML Object Library’ te bereiken via:
Alt+F11 | Tools | References
Alt+F11 | Extra | Verwijzingen

We noemen het lokaal bestand: The_Local_File.html. Dat staat al hard-coded in de code. Het wordt automatisch aangemaakt. Vervolgens navigeren we naar:
“https://www.theguardian.com/europe” en slaan de complete pagina op in The_Local_File.html. Tenslotte halen we alle koppen op en drukken die af in het immediate venster (Direct venster) in VBA IDE (te bereiken via Alt+F11)

Option Explicit
'* Tools -> References Microsoft HTML Object Library
'* MSDN - URLDownloadToFile function - https://msdn.microsoft.com/en-us/library/ms775123(v=vs.85).aspx
Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" _
        (ByVal pCaller As Long, ByVal szURL As String, ByVal szFileName As String, _
        ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long

Sub Test()

    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")

    Dim sLocalFilename As String
    'The local file. You don't need to create it. Its created on the fly.
    sLocalFilename = Environ$("TMP") & "\The_Local_File.html"
    
    Dim sURL As String
    'Navigate to
    sURL = "https://www.theguardian.com/europe"
    
    
    Dim bOk As Boolean
    bOk = (URLDownloadToFile(0, sURL, sLocalFilename, 0, 0) = 0)
    If bOk Then
        If fso.FileExists(sLocalFilename) Then
        
            '* Tools -> References Microsoft HTML Object Library
            Dim oHtml4 As MSHTML.IHTMLDocument4
            Set oHtml4 = New MSHTML.HTMLDocument
            
            Dim oHtml As MSHTML.HTMLDocument
            Set oHtml = Nothing
            
            '* IHTMLDocument4.createDocumentFromUrl
            '* MSDN - IHTMLDocument4 createDocumentFromUrl method
            '- https://msdn.microsoft.com/en-us/library/aa752523(v=vs.85).aspx
            Set oHtml = oHtml4.createDocumentFromUrl(sLocalFilename, "")
            
            '* need to wait a little whilst the document parses
            '* because it is multithreaded
            While oHtml.readyState <> "complete"
            '* do not comment this out it is required to break into the code if in infinite loop
                DoEvents
            Wend
            Debug.Assert oHtml.readyState = "complete"
            

            Dim sTest As String
            sTest = Left$(oHtml.body.outerHTML, 100)
            '* just testing we got a substantial block of text, feel free to delete
            Debug.Assert Len(Trim(sTest)) > 50
            
            '* this is where the page information goes
            Dim htmlAnswers As Object 'MSHTML.DispHTMLElementCollection
            Set htmlAnswers = oHtml.getElementsByClassName("show-underline")
    
            Dim lAnswerLoop As Long
            For lAnswerLoop = 0 To htmlAnswers.Length - 1
                Dim vAnswerLoop
                Set vAnswerLoop = htmlAnswers.Item(lAnswerLoop)
                Debug.Print vAnswerLoop.outerText
            
            Next
    
        End If
    End If
End Sub

Voorwaardelijk gemiddelde op niet-aaneengesloten bereiken met behulp van FREQUENCY()

Hoe kan de FREQUENCY() functie gebruikt kan worden? Deze post gaat over deze FREQUENCY() functie en komt van het MrExcel – forum. De techniek komt van twee heren en die krijgen dan ook de credits en alle lof namelijk Barry Houdini en Aladin Akyurek.

Het probleem

We hebben de prijzen van een aantal producten in kolom A en van drie winkels in kolommen B, D en F.

De producten hebben 2 categorieën namelijk Prijs en Aantal.  We gaan het prijs-gemiddelde uit de tabel halen. We weten dat het gemiddelde geen rekening houdt met 0-waarden in de cellen. Het juiste antwoord zou €24,48 moeten zijn waarbij de nullen genegeerd worden.

(14,99 9,89 34,79 42,49 13,99 34,09 18,65 43,19 13,49 7,89 17,99 42,29) / 12 = 24,48

In dit geval willen we een voorwaardelijk gemiddelde berekenen waarbij cellen met nul worden genegeerd. Normaal gesproken kunnen we dit doen met de AVERAGEIF()functie, maar dat is hier niet mogelijk omdat de Prijs-gegevens zich in drie afzonderlijke, niet-aaneengesloten, bereiken bevinden. Deze formule retourneert daarom een foutmelding:

=AVERAGEIF((B3:B7,D3:D7,F3:F7),”<>0″)

Om een ​​gemiddelde te berekenen van een niet-aaneengesloten verzameling bereiken kunnen we de werkbladfuncties SUM() en COUNT() gebruiken.

=SUM(B3:B7,D3:D7,F3:F7)/COUNT(B3:B7,D3:D7,F3:F7)

Echter de formule COUNT() telt ook de nullen waardoor de formule een resultaat van €19,58 retourneert, wat onjuist is. We kunnen proberen om de formule aan te passen om nullen te negeren door COUNTIF()in plaats van COUNT() te gebruiken, maar COUNTIF() kan ook niet worden gebruikt op niet-aaneengesloten bereiken, dus dit retourneert ook #VALUE!:

=SUM(B3:B7,D3:D7,F3:F7)/COUNTIF((B3:B7,D3:D7,F3:F7),”<>0″)

De vraag is dus hoe we het aantal niet-nulwaarden in deze cellen kunnen tellen en de nul-waarden negeren?

De oplossing

De oplossing is als volgt:
=SUM(B3:B7;D3:D7;F3:F7)/INDEX(FREQUENCY((B3:B7;D3:D7;F3:F7);0);2)
Formule invoeren met ENTER.

Dit SUM()deel van de formule is vrij eenvoudig.

FREQUENCY((B3:B7,D3:D7,F3:F7),0)

FREQUENCY() wordt hier gebruikt op niet-aaneengesloten bereiken.
De gegevens-matrix is (B3:B7,D3:D7,F3:F7). De bins_array is 0. FREQUENCY() retourneert een array met twee waarden: een telling van de prijzen met 0 die in de eerste bucket van 0 vallen, en een telling van de prijzen die groter zijn dan 0.

FREQUENCY((B3:B7,D3:D7,F3:F7),0)
wordt geëvalueerd tot {3;12}

We gaan er van uit dat er geen negatieve prijzen zijn. 3 is het aantal prijzen dat exact gelijk is aan 0 en 12 is het aantal prijzen dat groter is dan 0. 12 is het getal waarin we geïnteresseerd zijn. Vervolgens gebruiken we de INDEX()functie om dit uit de matrix te halen:

INDEX({3;12},2)
wordt geëvalueerd tot {12}

Vervolgens delen we de som van de waarden door 12 om het juiste antwoord te krijgen: €24,48.

SUM(B3:B7,D3:D7,F3:F7)/{12}
wordt geëvalueerd als: 293,74/{12}

resulteert in ons uiteindelijke antwoord: {€24,48} De formule werkt nog steeds, zelfs als er lege cellen zijn of als de niet-aaneengesloten bereiken een verschillende grootte hebben.