Parsování HTML

Parsování HTML představuje slangový výraz pro syntaktickou analýzu obsahu webové stránky. Lidově řečeno porcujeme zdrojový kód stránky a vyzobáváme potřebný obsah. Webovým vývojářům není neznámý pojem HTML DOM (Document Object Model). Ten je popsán konsorciem W3C (http://www.w3schools.com/jsref/dom_obj_document.asp) a umožňuje spravovat obsah stránky s pomocí javascriptu. Ačkoliv je možné brát inspiraci přímo z něj, v případě VBA se odvoláváme na starší knihovnu Microsoft HTML Object Library, v níž jsou některé vlastnosti definovány odlišně (outerHTML, innerText aj.). Každopádně výhodu mají ti, kteří se již potkali s vytvářením stránek v jazyce HTML a ovládají práci s tagy (elementy), jako je např. <body>, <div>, <p>, <table>, jejich atributy (vlastnostmi) a stylováním (CSS).

Pro účely testování jsem vytvořil stránku parsovani.html.

Zdrojový kód:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
< !DOCTYPE html>
<html>
<head>
    <title>Moje stránka</title>
    <meta charset="UTF-8"/>
    <style>
    table {
            border-collapse: collapse;
        }
        table, td, th {
            border: 1px solid #CCCCCC;
        }
        .moje_trida {
            color: #0066CC;
        }
    </style>
</head>
<body>
    <p><input name="muj_nazev" type="text" value="Excel 2010"/></p>
    <p id="muj_identifikator" style="color: #FF0066">Element P s atributem
    id="muj_identifikator".</p>
    <div class="moje_trida">
        <p>Element P v prvním elementu DIV s atributem class="moje_trida"
        (index 0).</p>
    </div>
    <div class="moje_trida">
        <p>Element P ve druhém elementu DIV s atributem class="moje_trida"
        (index 1).</p>
    </div>
    <table>
        <tr>
            <td>Křížek</td>
            <td>123,45</td>
            <td>20.11.2015</td>
        </tr>
        <tr>
            <td>Bydžovský</td>
            <td>678,90</td>
            <td>1.6.2016</td>
        </tr>
    </table>
    <p><a href="http://www.ceskatelevize.cz/ct1/" target="_blank"><img alt=
   "Logo ČT1" height="83" src="ct1.jpg" width="155"/></a><br />
    <a href="http://www.ceskatelevize.cz/ct2/" target="_blank"><img alt=
   "Logo ČT2" height="83" src="ct2.jpg" width="155"/></a></p>
</body>
</html>

A nyní už si pojďme obsah stránky rozebrat programově. Kód je taktéž uveden v příloze a doporučuji jej krokovat a studovat v oknech Immediate a Locals.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
Sub ParsovaniHTML()

    'Tools / References / Microsoft HTML Object Library
   
    Dim objMSHTML As New HTMLDocument
    Dim objDocument As HTMLDocument
   
    Dim objImages As IHTMLElementCollection
    Dim objLinks As IHTMLElementCollection
    Dim objElements As IHTMLElementCollection
    Dim objTags As IHTMLElementCollection
    Dim objTagsTagName As IHTMLElementCollection
    Dim objTagsClass As IHTMLElementCollection
    Dim objTagsName As IHTMLElementCollection
   
    Dim objImage As IHTMLImgElement
    Dim objLink As IHTMLAnchorElement

    Dim objTagClass As IHTMLElement
    Dim objTagName As IHTMLElement
    Dim objTagId As IHTMLElement
   
    'přesnější typy vyplývající z testování
    'Dim objTagClass As IHTMLDivElement
    'Dim objTagName As IHTMLInputElement
    'Dim objTagId As IHTMLParaElement
   
    Dim objHTML As IHTMLHtmlElement
    Dim objHead As IHTMLHeadElement
    Dim objBody As IHTMLBodyElement

    Dim strURL As String
    Dim strTitulek As String
    Dim strHTML As String
    Dim strHead As String
    Dim strBody As String

    'adresa stránky
    strURL = "https://proexcel.cz/test/parsovani.html"

    'element ... tag

    'dokument
    Set objDocument = objMSHTML.createDocumentFromUrl(strURL, vbNullString)

    'čekání na stažení
    While objDocument.readyState <> "complete"
        DoEvents
    Wend

    'titulek stránky
    strTitulek = objDocument.title

    'objekt HTML (element html)
    Set objHTML = objDocument.documentElement
    strHTML = objHTML.outerHTML

    'hlavička (element head)
    Set objHead = objDocument.head
    strHead = objHead.outerHTML

    'obsah stránky (element body)
    Set objBody = objDocument.body
    strBody = objBody.outerHTML

    'kolekce obrázků (elementy img)
    Set objImages = objDocument.images

    For Each objImage In objImages
        Debug.Print objImage.outerHTML
        Debug.Print objImage.getAttribute("href")
    Next

    'kolekce hypertextových odkazů (elementy a)
    Set objLinks = objDocument.links

    For Each objLink In objLinks
        Debug.Print objLink.outerHTML
        Debug.Print objLink.innerHTML
        Debug.Print objLink.getAttribute("href")
    Next
   
    'varianta 1 pro tagy
   
    'kolekce elementů
    Set objElements = objDocument.all
   
    'kolekce elementů s požadovaným názvem (p)
    Set objTags = objElements.tags("p")

    'varianta 2

    'kolekce elementů s požadovaným názvem (p)
    Set objTagsTagName = objDocument.getElementsByTagName("p")

    'element s atributem id (id="muj_identifikator")
    'ID by mělo být v dokumentu jedinečné
    Set objTagId = objDocument.getElementById("muj_identifikator")

    'typ nalezeného elementu
    'P
    strElement = objTagId.tagName

    'získání barvy atributu style nalezeného elementu (style="color: ...")
    '#ff0066
    strColor = objTagId.style.Color

    'kolekce elementů s požadovaným atributem class (class="moje_trida")
    Set objTagsClass = objDocument.getElementsByClassName("moje_trida")

    For Each objTagClass In objTagsClass
        Debug.Print objTagClass.tagName
        Debug.Print objTagClass.outerHTML
        Debug.Print objTagClass.innerHTML
    Next

    'kolekce elementů (zpravidla elementy input)
    's požadovaným atributem name (name="hledaný řetězec")
    Set objTagsName = objDocument.getElementsByName("muj_nazev")

    For Each objTagName In objTagsName
        Debug.Print objTagName.tagName
        Debug.Print objTagName.outerHTML
        Debug.Print objTagName.getAttribute("value")
    Next

    'odstranění z paměti
    Set objDocument = Nothing
    Set objMSHTML = Nothing

End Sub

Řádky VBA jsem se snažil komentovat a na tomto místě jen upřesním pojmy innerHTML, innerText a outerHTML.

Příklad
<div><p>nějaký text</p></div>

<div><p>nějaký text</p></div> … vlastnost outerHTML pro element <div>
<p>nějaký text</p> … vlastnost innerHTML pro element <div>
nějaký text … vlastnost innerTEXT pro element <p>

Pozn. Pokud se chcete odkazovat na členy kolekcí indexem, pak vězte, že číslování začíná nulou.

Přirozeně se sluší na tomto místě ukázat způsob, jak z dané stránky převzít tabulku do listu Excelu (ačkoliv prosté HTML tabulky je lepší načítat prostřednictvím karty Data / Z webu).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
Sub ParsovaniTabulkyHTML()

    Dim objMSHTML As New HTMLDocument
    Dim objDocument As HTMLDocument
   
    Dim objTagsRow As IHTMLElementCollection
    Dim objTagsCell As IHTMLElementCollection
   
    Dim objTagRow As IHTMLTableRow
    Dim objTagCell As IHTMLTableCell
   
    Dim strURL As String

    'adresa stránky
    strURL = "https://proexcel.cz/test/parsovani.html"

    'dokument
    Set objDocument = objMSHTML.createDocumentFromUrl(strURL, vbNullString)

    'čekání na stažení
    While objDocument.readyState <> "complete"
        DoEvents
    Wend

    'všechny řádky tabulky
    Set objTagsRow = objDocument.getElementsByTagName("tr")
   
    'pro každý řádek
    For Each objTagRow In objTagsRow

        'všechny buňky řádku
        Set objTagsCell = objTagRow.getElementsByTagName("td")

        'čítač pro řádky
        i = i + 1

        For Each objTagCell In objTagsCell

            'čítač pro sloupce
            j = j + 1

            'zápis do buněk listu
            Select Case j
                Case 1
                    'text
                    Cells(i, j).Value = objTagCell.innerText
                Case 2
                    'desetinné číslo
                    Cells(i, j).Value = CDbl(objTagCell.innerText)
                Case 3
                    'datum
                    Cells(i, j).Value = CDate(objTagCell.innerText)
            End Select

        Next objTagCell

        'reset čítače pro sloupce
        j = 0

    Next objTagRow

    'odstranění z paměti
    Set objDocument = Nothing
    Set objMSHTML = Nothing

End Sub
Tabulka převedená z HTML stránky
Tabulka převedená z HTML stránky

HTML stránky by do jisté míry měly dodržovat hierarchii objektů a jejich vnořování do sebe. V praxi tomu tak často není a jejich obsah bývá uspořádán laxně, na rozdíl třeba od XML. Je to jeden z důvodů, proč i já jsem v daném tématu nevyužil skutečnosti, že tagy (elementy) představují jakési „nody“ ve stromové struktuře, kdy uvažujeme vazby rodič (parent) – dítě (child), případně děti (children).

Parsování HTML stránek nepatří k technikám, za které bychom se mohli plácat po ramenou. Pokud máme možnost, vždy sáhneme po přímém přístupu k datům do databáze. Klíčové je slovíčko „pokud“. Až příliš dobře se pamatuji na nutnost zpracovat 60 000 webových stránek z nejmenovaného webu státní správy jen proto, že webová aplikace padala pod deseti minutách nastavování parametrů (bez možnosti uložení). Poměrně solidně se s HTML kódem umí vypořádat i regulární výrazy.

Ke zpracování webových stránek a jejich obsahu se opět někdy vrátíme.

Příloha
excel_parsovani_html.zip