我正在尝试从下面提到的网站复制数据,我需要各种尺寸,价格,设施,特价,储备金。我在下面的代码框架,但我能够正确复制元素。首先,只有三个要素可以解决重复问题,但设施和储备却没有得到结果。有人可以调查一下吗?
Sub text()
Dim ie As New InternetExplorer, ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Unit Data")
With ie
.Visible = True
.Navigate2 "https://www.safeandsecureselfstorage.com/self-storage-lake-villa-il-86955"
While .Busy Or .readyState < 4: DoEvents: Wend
Sheets("Unit Data").Select
Dim listings As Object, listing As Object, headers(), results()
Dim r As Long, list As Object, item As Object
headers = Array("size", "features", "Specials", "Price", "Reserve")
Set list = .document.getElementsByClassName("units_table")
'.unit_size medium, .features, .Specials, .price, .Reserve
Dim rowCount As Long
rowCount = .document.querySelectorAll(".tab_container li").Length
ReDim results(1 To rowCount, 1 To UBound(headers) + 1)
For Each listing In list
For Each item In listing.getElementsByClassName("unitinfo even")
r = r + 1
results(r, 1) = listing.getElementsByClassName("size secondary-color-text")(0).innerText
results(r, 2) = listing.getElementsByClassName("amenities")(0).innerText
results(r, 3) = listing.getElementsByClassName("offer1")(0).innerText
results(r, 4) = listing.getElementsByClassName("rate_text primary-color-text rate_text--clear")(0).innerText
results(r, 5) = listing.getElementsByClassName("reserve")(0).innerText
Next
Next
ws.Cells(1, 1).Resize(1, UBound(headers) + 1) = headers
ws.Cells(2, 1).Resize(UBound(results, 1), UBound(results, 2)) = results
.Quit
End With
Worksheets("Unit Data").Range("A:G").Columns.AutoFit
End Sub
答案 0 :(得分:2)
tl; dr;
提前(有些)道歉的长度,但我想我会考虑的 教学时机,详细说明正在发生的事情。
我使用的总体方法与您的代码相同:找到一个CSS选择器来隔离行(尽管在不同的选项卡中,小,中,大实际上仍然全部显示在页面上)
Set listings = html.querySelectorAll(".unitinfo")
上面生成了行。和以前一样,我们将其转储到新的HTMLDocument
中,以便可以利用querySelector/querySelectorAll
方法。
行:
让我们看一下我们正在检索的第一行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>
我们将要使用的每一行在html2
变量中都有相似的html。如果您有疑问,请查看上面显示的函数中的javascript:
$('.units_table tr.unitinfo').each(function(index, el)
它使用相同的选择器(但还指定了父表类和元素类型(tr
)。基本上,表中的每一行都会调用该函数。
大小:
由于某种原因,开头的td
标签被删除了,所以为了确定大小,而不是按类别抓取,我在寻找关闭标签的开始并将字符串提取到那里。为此,我将Instr(在字符串中找到<的位置)-1的返回值传递给Left$
(类型化)函数。
results(r, 1) = Left$(html2.body.innerHTML, InStr(html2.body.innerHTML, "<") - 1)
这将返回5x5
。
说明:
描述列由我们在上面看到的函数填充(应用于记住的每一行)
此位-$("#a5x5-1").tooltip
-告诉它目标位置,然后函数的return语句提供具有div
且类为description
的{{1}}的html,其中包含我们想。由于我们没有使用浏览器,并且我使用的是64位窗口,因此我无法评估此脚本,但是可以使用split
提取"description\">
到脚本开头之间的字符串(描述)。关闭关联的div
标签:
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
。
当您将鼠标悬停在这些页面上时,页面上会显示工具提示信息:
我们可以在实际页面的html中跟踪此工具提示的位置:
当您将鼠标悬停在其他便利设施上时,此内容也会更新。
长话短说(他说,在页面中途!),正在从服务器上的php文件更新此内容。我们可以请求文件并构建一个字典,映射每个便利设施的类名,例如amenity_icon icon_climate
(复合类在转换为.amenity_icon.icon_climate
的适当的CSS选择器时,需要用“。”替换为“”)以关联的描述。您可以浏览php文件here。
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
标签内返回文本。 详细描述 我编写了两个函数GetMatches
和GetAmenitiesDescriptions
,它们使用正则表达式为每个图标提取所有重复项,并返回一个以css选择器作为键的字典,以及短description : long description
作为值。
当我收集每一行中的所有图标时:
Set icons = html2.querySelectorAll(".amenity_icon")
我使用字典根据图标的类名返回工具提示说明
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
结合在一起,以确保输出在输出单元格中的不同行上。
您可以浏览正则表达式here。
正则表达式使用|
(或)语法,因此我将所有匹配的模式返回到一个列表中。
arr = GetMatches(re, s, "(\.amenity_icon\..*)""|<h4>(.*)<\/h4>|<p>(.*)<\/p>")
因为我需要不同的子匹配项(0、1或2个aka CSS类选择器,短desc,长desc),所以我使用带有计数器变量Select Case i mod 3
的{{1}}来提取适当的子匹配
与php文件中的映射匹配的示例:
特色:
我们回到了类选择器。 i
未填充,因此您可以删除。
Offer2
分别返回:
特价促销,空字符串
闭幕致:
因此,以上内容将引导您完成一行。只需冲洗,然后在所有行的循环中重复进行。为了提高效率,将数据添加到数组results(r, 6) = html2.querySelector(".offer1").innerText
results(r, 7) = html2.querySelector(".offer2").innerText
中;然后将其一次性写入results
。我可以看到一些小的改进,但这很快。
VBA:
Sheet1
输出:
参考(VBE>工具>参考):
答案 1 :(得分:1)
这里是一种方法:
Sub test()
Dim req As New WinHttpRequest
Dim doc As New HTMLDocument
Dim targetTable As HTMLTable
Dim tableRow As HTMLTableRow
Dim tableCell As HTMLTableCell
Dim element As HTMLDivElement
Dim sht As Worksheet
Dim amenitiesString As String
Dim i As Long
Dim j As Long
Set sht = ThisWorkbook.Worksheets("Sheet1")
With req
.Open "GET", "https://www.safeandsecureselfstorage.com/self-storage-lake-villa-il-86955", False
.send
doc.body.innerHTML = .responseText
End With
Set targetTable = doc.getElementById("units_small_units") 'You can use units_medium_units or units_large_units to get the info from the other tabs
i = 0
For Each tableRow In targetTable.Rows
i = i + 1
j = 0
For Each tableCell In tableRow.Cells
amenitiesString = ""
j = j + 1
If tableCell.className = "amenities" And tableCell.innerText <> "Amenities" Then
For Each element In tableCell.getElementsByTagName("div")
amenitiesString = amenitiesString & element.Title & ","
Next element
sht.Cells(i, j).Value = amenitiesString
ElseIf tableCell.className <> "features" Then
sht.Cells(i, j).Value = tableCell.innerText
End If
Next tableCell
Next tableRow
End Sub
我正在使用HTTP请求而不是Internet Explorer来获取HTML。除此之外,我认为您可以了解如何访问所需的元素。
这是结果的屏幕截图。
演示文稿有点原始,但是您了解了:-P
基本上是这样:
listing.getElementsByClassName("amenities")(0).innerText
将返回一个空白,因为这些元素中没有内部文本。该信息由脚本产生,但是也可以在title
元素的div
中找到。
使用的参考:
Microsoft HTML Object Library
和WinHTTP Services Version 5.1
答案 2 :(得分:0)
您可以尝试使用Jquery获取如下方法:
$。get('url',function(data){
// Loop through elements
$(data).find("ul").find("li").each( function(){
var text = $(this).text();
} )
});