tl; dr;
Извиняюсь заранее (некоторым) за длину ответа, но я подумал, что воспользуюсь этим педагогическим моментом, чтобы подробно рассказать о происходящем.
Общий подход, который я использую, такой же, как и в вашем коде: найдите селектор css для изоляции строк (несмотря на то, что на разных вкладках маленькие, средние и большие все еще присутствуют на странице):
Set listings = html.querySelectorAll(".unitinfo")
Выше приведены строки.Как и прежде, мы помещаем это в новый HTMLDocument
, чтобы мы могли использовать querySelector/querySelectorAll
методы.
Rows:
Давайте посмотрим наПервая строка HTML мы получаем.В следующих разделах эта строка будет рассмотрена в качестве примера для обсуждения того, как извлекается информация:
5x5</TD> <TD class=features>
<DIV id=a5x5-1 class="icon a5x5">
<DIV class=img><IMG src="about:/core/resources/images/units/5x5_icon.png"></DIV>
<DIV class=display>
<P>More Information</P></DIV></DIV>
<SCRIPT type=text/javascript>
// Refine Search
//
$(function() {
$("#a5x5-1").tooltip({
track: false,
delay: 0,
showURL: false,
left: 5,
top: 5,
bodyHandler: function () {
return " <div class=\"tooltip\"> <div class=\"tooltop\"></div> <div class=\"toolmid clearfix\"> <div class=\"toolcontent\"> <div style=\"text-align:center;width:100%\"> <img alt=\"5 x 5 storage unit\" src=\"/core/resources/images/units/5x5.png\" /> </div> <div class=\"display\">5 x 5</div> <div class=\"description\">Think of it like a standard closet. Approximately 25 square feet, this space is perfect for about a dozen boxes, a desk and chair, and a bicycle.</div> </div> <div class=\"clearfix\"></div> </div> <div class=\"toolfoot\"></div> <div class=\"clearfix\"></div> </div> "}
});
});
</SCRIPT>
</TD><TD class=rates>
<DIV class="discount_price secondary-color-text standard_price--left">
<DIV class=price_text>Web Rate: </DIV>
<DIV class="rate_text primary-color-text rate_text--clear">$39.00 </DIV></DIV>
<SCRIPT>
$( document ).ready(function() {
$('.units_table tr.unitinfo').each(function(index, el) {
if ($(this).find('.standard_price').length != '' && $(this).find('.discount_price').length != '') {
$(this).parents('.units_table').addClass('both');
$(this).addClass('also-both');
$(this).find('.rates').addClass('rates_two_column');
}
});
});
</SCRIPT>
</TD><TD class=amenities>
<DIV title="Temperature Controlled" class="amenity_icon icon_climate"></DIV>
<DIV title="Interior Storage" class="amenity_icon icon_interior"></DIV>
<DIV title="Ground Floor" class="amenity_icon icon_ground_floor"></DIV></TD><TD class=offers>
<DIV class=offer1>Call for Specials </DIV>
<DIV class=offer2></DIV></TD><TD class=reserve><A id=5x5:39:00000000 class="facility_call_to_reserve cta_call primary-color primary-hover" href="about:blank#" rel=nofollow>Call </A></TD>
Каждая строка, с которой мы будем работать, будет иметь одинаковый html внутри переменной html2
.Если вы сомневались, посмотрите на javascript в функции, показанной выше:
$('.units_table tr.unitinfo').each(function(index, el)
, он использует тот же селектор (но также указывает класс родительской таблицы и тип элемента (tr
)).По сути, эта функция вызывается для каждой строки в таблице.
Размер:
По какой-то причине открывающий тег td
удаляется, поэтомупо размеру, а не по классам, я ищу начало закрывающего тега и извлекаю строку там.Я делаю это, передавая возвращаемое значение, заданное Instr (где <было найдено в строке) -1, в функцию <code>Left$ (напечатано).
![enter image description here](https://i.stack.imgur.com/yoczu.png)
results(r, 1) = Left$(html2.body.innerHTML, InStr(html2.body.innerHTML, "<") - 1)
Возвращает 5x5
.
Описание:
Столбец описания заполняется функцией, которую мывидел выше (который применяется к каждой запоминаемой строке)
Этот бит - $("#a5x5-1").tooltip
- сообщает ему, куда нацеливаться, а затем оператор return функции предоставляет html с div
классомdescription
, содержащий текст, который мы хотим.Поскольку мы не используем браузер, и я нахожусь на 64-битных окнах, я не могу оценить этот сценарий, но я могу использовать split
, чтобы извлечь строку (описание) между "description\">
и началом закрытия, связанного div
tag:
results(r, 2) = Split(Split(html2.querySelector("SCRIPT").innerHTML, """description\"">")(1), "</div>")(0)
Возвращает:
"Думайте об этом как о стандартном шкафу. Приблизительно 25 квадратных футов, это место идеально подходит для примерно дюжины ящиков, столаи стул, и велосипед. "
Тип ставки и цена:
Они просты и используют имя класса для цели:
results(r, 3) = Replace$(html2.querySelector(".price_text").innerText, ":", vbNullString)
results(r, 4) = Trim$(html2.querySelector(".rate_text").innerText)
Возврат (соответственно)
Интернет-тариф, £ 39,00
Удобства:
Здесь все немного сложнее.
Давайте еще раз рассмотрим приведенный выше HTML-код для этой строки, относящейся к удобствам:
<TD class=amenities>
<DIV title="Temperature Controlled" class="amenity_icon icon_climate"></DIV>
<DIV title="Interior Storage" class="amenity_icon icon_interior"></DIV>
<DIV title="Ground Floor" class="amenity_icon icon_ground_floor"></DIV></TD>
Мы можем видеть, что родительский td
имеет класс amenities
, который имеет дочерние div
элементы, которые имеют составные имена классов;последний из которых, в каждом случае, служит идентификатором для типа удобства, например icon_climate
.
Когда вы наводите курсор на них, на странице отображается информация всплывающей подсказки:
![enter image description here](https://i.stack.imgur.com/J6nrt.png)
Мы можем проследить местоположение этой подсказкив html текущей страницы:
![enter image description here](https://i.stack.imgur.com/HArkL.png)
При наведении указателя мыши на различные удобства этот контент обновляется.
Короче говоря, длинная история (он говорит, пока на полпути вниз по странице!) , этот контент обновляется из php-файла на сервере.Мы можем сделать запрос на файл и создать словарь, который отображает имя класса каждого объекта, например, amenity_icon icon_climate
(которые, как и составные классы, должны "" заменяться на "." При преобразовании в соответствующий селектор css из .amenity_icon.icon_climate
)к связанным описаниям.Вы можете изучить файл php здесь .
Файл php:
Давайте рассмотрим только начало файла, чтобы разобратьБазовая единица повторяющегося рисунка:
function LoadTooltips() {
$(".units_table .amenity_icon.icon_climate").tooltip({
track: false,
delay: 0,
showURL: false,
left: -126,
top: -100,
bodyHandler: function () {
return "<div class=\"sidebar_tooltip\"><h4>Temperature Controlled</h4><p>Units are heated and/or cooled. See manager for details.</p></div>"
}
});
Функция, отвечающая за обновление всплывающей подсказки, - LoadTooltips
.Селекторы классов CSS используются для нацеливания на каждый значок:
$(".units_table .amenity_icon.icon_climate").tooltip
И у нас есть bodyhandler, задающий текст возврата:
bodyHandler: function () {
return "<div class=\"sidebar_tooltip\"><h4>Temperature Controlled</h4><p>Units are heated and/or cooled. See manager for details.</p></div>"
У нас есть три бита полезной информации, которые появляются в повторяющихся группах.Селектор имени класса для элемента, краткое описание и полное описание, например,
.amenity_icon.icon_climate
: мы используем это для сопоставления описаний файлов php с именем класса значка удобства в нашей строке. Селектор CSS Temperature Controlled
;внутри h4
тега всплывающей подсказки, возвращающего текст. Краткое описание Units are heated and/or cooled. See manager for details.
;внутри p
тега всплывающей подсказки, возвращающего текст. Длинное описание
Я пишу 2 функции, GetMatches
и GetAmenitiesDescriptions
, которые используют регулярное выражение для извлечения всех повторяющихся элементов для каждого значкаи вернуть словарь, в котором ключом является селектор css, а в качестве значения - короткий description : long description
.
Когда я соберу все значки в каждой строке:
Set icons = html2.querySelectorAll(".amenity_icon")
Iиспользуйте словарь для возврата описания всплывающей подсказки на основе имени класса значка
For icon = 0 To icons.Length - 1 'use class name of amenity to look up description
amenitiesInfo(icon) = amenitiesDescriptions("." & Replace$(icons.item(icon).className, Chr$(32), "."))
Next
. Затем я объединяю описания с vbNewLine
, чтобы обеспечить вывод в разных строках в выходной ячейке.
Вы можете изучить регулярное выражение здесь .
Регулярное выражение использует синтаксис |
(Или), поэтому я возвращаю все сопоставленные шаблоны в одном списке.
arr = GetMatches(re, s, "(\.amenity_icon\..*)""|<h4>(.*)<\/h4>|<p>(.*)<\/p>")
Поскольку мне потребуются различные подсовпадения (селектор класса css 0,1 или 2, короткий дескриптор, длинный деск), я использую Select Case i mod 3
, с переменной счетчика i
, чтобы извлечь соответствующий подпункт-matches.
Пример соответствия для сопоставления в php-файле:
![enter image description here](https://i.stack.imgur.com/g87Zg.png)
Скидки:
Мы вернулись к селекторам классов.Offer2
не заполнено, поэтому вы можете удалить.
results(r, 6) = html2.querySelector(".offer1").innerText
results(r, 7) = html2.querySelector(".offer2").innerText
возвращает (соответственно):
Вызов Specials, пустая строка
Заключительные замечания:
Итак, вышеизложенное проведет вас через один ряд.Это просто промыть и повторить в цикле по всем рядам.Для удобства данные добавляются в массив results
;который затем записывается Sheet1
за один раз.Я вижу некоторые незначительные улучшения, но это быстро.
VBA:
Option Explicit
Public Sub GetInfo()
Dim ws As Worksheet, html As HTMLDocument, s As String, amenitiesDescriptions As Object
Const URL As String = "https://www.safeandsecureselfstorage.com/self-storage-lake-villa-il-86955"
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set html = New HTMLDocument
Set amenitiesDescriptions = GetAmenitiesDescriptions
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", URL, False
.setRequestHeader "User-Agent", "Mozilla/5.0"
.send
s = .responseText
html.body.innerHTML = s
Dim headers(), results(), listings As Object, amenities As String
headers = Array("Size", "Description", "RateType", "Price", "Amenities", "Offer1", "Offer2")
Set listings = html.querySelectorAll(".unitinfo")
Dim rowCount As Long, numColumns As Long, r As Long, c As Long
Dim icons As Object, icon As Long, amenitiesInfo(), i As Long, item As Long
rowCount = listings.Length
numColumns = UBound(headers) + 1
ReDim results(1 To rowCount, 1 To numColumns)
Dim html2 As HTMLDocument
Set html2 = New HTMLDocument
For item = 0 To listings.Length - 1
r = r + 1
html2.body.innerHTML = listings.item(item).innerHTML
results(r, 1) = Left$(html2.body.innerHTML, InStr(html2.body.innerHTML, "<") - 1)
results(r, 2) = Split(Split(html2.querySelector("SCRIPT").innerHTML, """description\"">")(1), "</div>")(0)
results(r, 3) = Replace$(html2.querySelector(".price_text").innerText, ":", vbNullString)
results(r, 4) = Trim$(html2.querySelector(".rate_text").innerText)
Set icons = html2.querySelectorAll(".amenity_icon")
ReDim amenitiesInfo(0 To icons.Length - 1)
For icon = 0 To icons.Length - 1 'use class name of amenity to look up description
amenitiesInfo(icon) = amenitiesDescriptions("." & Replace$(icons.item(icon).className, Chr$(32), "."))
Next
amenities = Join$(amenitiesInfo, vbNewLine) 'place each amenity description on a new line within cell when written out
results(r, 5) = amenities
results(r, 6) = html2.querySelector(".offer1").innerText
results(r, 7) = html2.querySelector(".offer2").innerText
Next
ws.Cells(1, 1).Resize(1, UBound(headers) + 1) = headers
ws.Cells(2, 1).Resize(UBound(results, 1), UBound(results, 2)) = results
End With
End Sub
Public Function GetAmenitiesDescriptions() As Object 'retrieve amenities descriptions from php file on server
Dim s As String, dict As Object, re As Object, i As Long, arr() 'keys based on classname, short desc, full desc
' view regex here: https://regex101.com/r/bII5AL/1
Set dict = CreateObject("Scripting.Dictionary")
Set re = CreateObject("vbscript.regexp")
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://www.safeandsecureselfstorage.com/core/resources/js/src/common.tooltip.php", False
.setRequestHeader "User-Agent", "Mozilla/5.0"
.send
s = .responseText
arr = GetMatches(re, s, "(\.amenity_icon\..*)""|<h4>(.*)<\/h4>|<p>(.*)<\/p>")
For i = LBound(arr) To UBound(arr) Step 3 'build up lookup dictionary for amenities descriptions
dict(arr(i)) = arr(i + 1) & ": " & arr(i + 2)
Next
End With
Set GetAmenitiesDescriptions = dict
End Function
Public Function GetMatches(ByVal re As Object, inputString As String, ByVal sPattern As String) As Variant
Dim matches As Object, iMatch As Object, s As String, arrMatches(), i As Long
With re
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = sPattern
If .test(inputString) Then
Set matches = .Execute(inputString)
ReDim arrMatches(0 To matches.Count - 1)
For Each iMatch In matches
Select Case i Mod 3
Case 0
arrMatches(i) = iMatch.SubMatches.item(0)
Case 1
arrMatches(i) = iMatch.SubMatches.item(1)
Case 2
arrMatches(i) = iMatch.SubMatches.item(2)
End Select
i = i + 1
Next iMatch
Else
ReDim arrMatches(0)
arrMatches(0) = vbNullString
End If
End With
GetMatches = arrMatches
End Function
Вывод:
![enter image description here](https://i.stack.imgur.com/CggiC.png)
Ссылки (VBE> Инструменты> Ссылки):
- Microsoft HTMLБиблиотека объектов