无法让我的脚本使用 IE 继续单击“加载更多”按钮
Unable to let my script keep clicking on Load more button using IE
我在 vba 中创建了一个脚本,使用 IE 继续点击 Load more hits
按钮位于网页底部,直到没有这样的按钮为止。
Here is how my script can populate that button: In the site's landing page there is a dropdown named Type
. The script can click on that Type
to unfold the dropdown
then it clicks on some corporate bond
checkbox among the options. Finally, it clicks on the apply
button to populate the data. However, that load more hits
button can be visible at the bottom now.
我的脚本几乎可以完全遵循我上面描述的所有步骤。我唯一难以解决的问题是,在单击该按钮 3/4 次后,脚本似乎卡住了。
我怎样才能纠正我的脚本,让我一直点击那个 Load more hits
按钮,直到没有这样的按钮为止?
到目前为止我已经尝试过:
Sub ExhaustLoadMore()
Dim IE As New InternetExplorer, I As Long
Dim Html As HTMLDocument, post As Object, elem As Object
Dim CheckBox As Object, btnSelect As Object
With IE
.Visible = True
.navigate "https://www.boerse-stuttgart.de/en/tools/product-search/bonds"
While .Busy Or .readyState < 4: DoEvents: Wend
Set Html = .document
Do: Loop Until Html.querySelectorAll(".bsg-loader-ring__item").Length = 0
Html.querySelector("#bsg-filters-btn-bgs-filter-3").Click
Do: Set CheckBox = Html.querySelector("#bsg-checkbox-3053"): DoEvents: Loop While CheckBox Is Nothing
CheckBox.Click
Set btnSelect = Html.querySelector("#bsg-filters-menu-bgs-filter-3 .bsg-btn__label")
Do: Loop While btnSelect.innerText = "Close"
btnSelect.Click
Do: Loop Until Html.querySelectorAll(".bsg-loader-ring__item").Length = 0
Do: Set elem = Html.querySelector(".bsg-table__tr td"): DoEvents: Loop While elem Is Nothing
Do
Set post = Html.querySelector(".bsg-searchlist__load-more button.bsg-btn--juna")
If Not post Is Nothing Then
post.ScrollIntoView
post.Click
Application.Wait Now + TimeValue("00:00:05")
Else: Exit Do
End If
Loop
End With
End Sub
我尝试过使用 selenium,但似乎速度较慢。但是,即使其中没有硬编码等待,它也会在长时间等待之后继续单击加载更多按钮。在硒的情况下:我希望有任何可能有助于减少其执行时间的解决方案。
Sub ExhaustLoadMore()
Const Url$ = "https://www.boerse-stuttgart.de/en/tools/product-search/bonds"
Dim driver As New ChromeDriver, elem As Object, post As Object
With driver
.get Url
Do: Loop Until .FindElementsByCss(".bsg-loader-ring__item").count = 0
.FindElementByCss("#bsg-filters-btn-bgs-filter-3", timeOut:=10000).Click
.FindElementByXPath("//label[contains(.,'Corporate Bond')]", timeOut:=10000).Click
.FindElementByXPath("//*[@id='bsg-filters-menu-bgs-filter-3']//button", timeOut:=10000).Click
Do: Loop Until .FindElementsByCss(".bsg-loader-ring__item").count = 0
Set elem = .FindElementByCss(".bsg-table__tr td", timeOut:=10000)
Do
Set post = .FindElementByCss(".bsg-searchlist__load-more button.bsg-btn--juna", timeOut:=10000)
If Not post Is Nothing Then
post.ScrollIntoView
.ExecuteScript "arguments[0].click();", post
Do: Loop Until .FindElementsByCss("p.bsg-searchlist__info--load-more").count = 0
Else: Exit Do
End If
Loop
Stop
End With
End Sub
你能不能试试改一下
Do
Set post = Html.querySelector(".bsg-searchlist__load-more button.bsg-btn--juna")
If Not post Is Nothing Then
post.ScrollIntoView
post.Click
Application.Wait Now + TimeValue("00:00:05")
Else: Exit Do
End If
Loop
至:
Set post = Html.querySelector(".bsg-searchlist__load-more button.bsg-btn--juna")
If Not post Is Nothing Then
post.ScrollIntoView
While Not post Is Nothing
Debug.Print "Clicking"
post.Click
Application.Wait Now + TimeValue("00:00:05")
Wend
Debug.Print "Exited Click"
End If
(未经测试)
我研究了你的网站,由于我无法将所有这些都说成一条评论,我决定 post 一个答案(即使它没有提供具体的解决方案,但只是 "answer" 和一些提示)。
您问题的答案
How can I rectify my script to keep clicking on that Load more hits button until there is no such button is left?
不幸的是,这不是你的错。您定位的网站正在通过 Web 客户端(您的浏览器)和提供您试图抓取的价格的 Web 服务器之间的 WebSocket 通信工作。可以看到如下:
想象一下:
- 当你第一次加载你的网页时,网络套接字被初始化并发送第一个请求(网络客户端:"Hey server, give me the first X results",网络服务器:"Sure, here you go").
- 每次单击 "Load more results" 按钮时,Web 客户端(重要:重新使用相同的 WS 连接)不断要求 X 个新结果以网络服务器。
所以,通信持续了一段时间。在某些时候,超出您的控制,网络套接字恰好死了。在单击 "Load more results" 按钮的同时查看 JavaScript 控制台就足够了:您将看到请求正在处理,直到在某个时候您不只是看到 NullPointerException
引发:
如果你点击异常前堆栈的最后一行,你会看到这是因为网络套接字:
错误说得很清楚:cannot read .send() on null
,意思是_ws
(网络套接字)不见了。
从现在开始,您可以忘记您的网站。当你点击按钮 "Load more results" 时,web 客户端会要求 web socket 将新的请求传递给 web 服务器,但是 web socket 已经消失了所以再见了两者之间的通信,所以(不幸的)再见了您的其余数据。
您可以通过在堆栈中往上移一点来验证这一点:
正如您在上面看到的,我们有:
- 控制台中记录的消息说 "performSearch params ...") 就在 post 新数据请求
之前
- 新数据请求
post
- 控制台中记录的消息说 "performed search with result ...") 就在 post 新数据请求
之后
虽然 web 套接字仍然存在,但每次单击 "Load more results" 时,您都会在控制台中看到这两条消息(中间的其他消息打印在其余代码上):
然而,在网络套接字第一次崩溃后,无论您尝试点击按钮多少次,您都只会收到第一条消息(网络客户端发送请求)而永远不会收到第二条消息(请求在空白中丢失):
请注意,这与您在 VBA 中观察到的行为相对应:
the script seems to get stuck after clicking on that button 3/4 times.
它没有卡住,实际上你的脚本一直在正确执行。是网站超时了。
我试图找出网络套接字崩溃的原因,但没有成功。这似乎是一个超时(我在调试他们的 JavaScript 时遇到了很多这样的问题,所以我的断点导致了超时)但我不能让你确定这是唯一的原因。由于您无法控制 Web 客户端和 Web 服务器之间的进程,您所能做的就是希望它不会超时。
此外,我相信使用 Selenium 会自动设置一些更长的超时(因为执行时间长),这在某种程度上允许您使 Web 套接字对超时更具容忍度。
我发现在网络套接字崩溃后恢复连接的唯一方法是完全重新加载网页并从头开始重新启动进程。
我的建议
我认为您可能会构建一个 XHR 请求并通过 JavaScript 发送,因为它们的 API(网络 client/web 套接字通过它向网络服务器发送请求)在他们的前端代码中非常暴露。
如果你打开他们的文件 FinderAPI.js
,你会看到他们留下了端点和 API 配置硬编码:
var FinderAPI = {
store: null,
state: null,
finderEndpoint: '/api/v1/bsg/etp/finder/list',
bidAskEndpoint: '/api/v1/prices/bidAsk/get',
instrumentNameEndpoint: '/api/products/ProductTypeMapping/InstrumentNames',
nameMappingEndpoint: '/api/v1/bsg/general/namemapping/list',
apiConfig: false,
initialize: function initialize(store, finderEndpoint) {
var apiConfig = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
this.store = store;
this.state = store.getState();
this.apiConfig = apiConfig;
this.finderEndpoint = finderEndpoint;
},
这意味着您知道应该将 POST
请求发送到的 URL。
请求还需要服务器验证 Bearer Token。幸运的是,他们也忘记了保护他们的代币,提供 (GORSH) GET
获取代币的终点:
终点:https://www.boerse-stuttgart.de/api/products
回复:
{"AuthenticationToken":"JgACxn2DfHceHL33uJhNj34qSnlTZu4+hAUACGc49UcjUhmLutN6sqcktr/T634vaPVcNzJ8sHBvKvWz","Host":"frontgate.mdgms.com"}
您只需要稍微浏览一下网站,弄清楚您的 POST 请求的主体是什么,然后创建一个新的 XmlHttpRequest
并将这些值发送到其中直接在 VBA 中检索价格,无需打开网页和机器人抓取。
我建议你从文件 FinderAPI.js
的第 66 行开始(代码行是 this.post(this.finderEndpoint, params)
,params
应该会引导你到请求的主体 -我记得您可以使用 JSON.stringify(params)
).
将对象打印为字符串
此外,请注意他们每次都使用 50
个结果的分页,即使他们的 API 最多支持 500
个结果。换句话说,如果您将值 500(而不是 50)扫入他们的分页 属性 发送到请求的 API:
...然后您每次将获得 500 个结果而不是 50 个,因此如果您决定不深入研究 XHR 解决方案,您的代码抓取网页的时间将减少 10 个。
我在 vba 中创建了一个脚本,使用 IE 继续点击 Load more hits
按钮位于网页底部,直到没有这样的按钮为止。
Here is how my script can populate that button: In the site's landing page there is a dropdown named
Type
. The script can click on thatType
to unfold thedropdown
then it clicks on somecorporate bond
checkbox among the options. Finally, it clicks on theapply
button to populate the data. However, thatload more hits
button can be visible at the bottom now.
我的脚本几乎可以完全遵循我上面描述的所有步骤。我唯一难以解决的问题是,在单击该按钮 3/4 次后,脚本似乎卡住了。
我怎样才能纠正我的脚本,让我一直点击那个 Load more hits
按钮,直到没有这样的按钮为止?
到目前为止我已经尝试过:
Sub ExhaustLoadMore()
Dim IE As New InternetExplorer, I As Long
Dim Html As HTMLDocument, post As Object, elem As Object
Dim CheckBox As Object, btnSelect As Object
With IE
.Visible = True
.navigate "https://www.boerse-stuttgart.de/en/tools/product-search/bonds"
While .Busy Or .readyState < 4: DoEvents: Wend
Set Html = .document
Do: Loop Until Html.querySelectorAll(".bsg-loader-ring__item").Length = 0
Html.querySelector("#bsg-filters-btn-bgs-filter-3").Click
Do: Set CheckBox = Html.querySelector("#bsg-checkbox-3053"): DoEvents: Loop While CheckBox Is Nothing
CheckBox.Click
Set btnSelect = Html.querySelector("#bsg-filters-menu-bgs-filter-3 .bsg-btn__label")
Do: Loop While btnSelect.innerText = "Close"
btnSelect.Click
Do: Loop Until Html.querySelectorAll(".bsg-loader-ring__item").Length = 0
Do: Set elem = Html.querySelector(".bsg-table__tr td"): DoEvents: Loop While elem Is Nothing
Do
Set post = Html.querySelector(".bsg-searchlist__load-more button.bsg-btn--juna")
If Not post Is Nothing Then
post.ScrollIntoView
post.Click
Application.Wait Now + TimeValue("00:00:05")
Else: Exit Do
End If
Loop
End With
End Sub
我尝试过使用 selenium,但似乎速度较慢。但是,即使其中没有硬编码等待,它也会在长时间等待之后继续单击加载更多按钮。在硒的情况下:我希望有任何可能有助于减少其执行时间的解决方案。
Sub ExhaustLoadMore()
Const Url$ = "https://www.boerse-stuttgart.de/en/tools/product-search/bonds"
Dim driver As New ChromeDriver, elem As Object, post As Object
With driver
.get Url
Do: Loop Until .FindElementsByCss(".bsg-loader-ring__item").count = 0
.FindElementByCss("#bsg-filters-btn-bgs-filter-3", timeOut:=10000).Click
.FindElementByXPath("//label[contains(.,'Corporate Bond')]", timeOut:=10000).Click
.FindElementByXPath("//*[@id='bsg-filters-menu-bgs-filter-3']//button", timeOut:=10000).Click
Do: Loop Until .FindElementsByCss(".bsg-loader-ring__item").count = 0
Set elem = .FindElementByCss(".bsg-table__tr td", timeOut:=10000)
Do
Set post = .FindElementByCss(".bsg-searchlist__load-more button.bsg-btn--juna", timeOut:=10000)
If Not post Is Nothing Then
post.ScrollIntoView
.ExecuteScript "arguments[0].click();", post
Do: Loop Until .FindElementsByCss("p.bsg-searchlist__info--load-more").count = 0
Else: Exit Do
End If
Loop
Stop
End With
End Sub
你能不能试试改一下
Do
Set post = Html.querySelector(".bsg-searchlist__load-more button.bsg-btn--juna")
If Not post Is Nothing Then
post.ScrollIntoView
post.Click
Application.Wait Now + TimeValue("00:00:05")
Else: Exit Do
End If
Loop
至:
Set post = Html.querySelector(".bsg-searchlist__load-more button.bsg-btn--juna")
If Not post Is Nothing Then
post.ScrollIntoView
While Not post Is Nothing
Debug.Print "Clicking"
post.Click
Application.Wait Now + TimeValue("00:00:05")
Wend
Debug.Print "Exited Click"
End If
(未经测试)
我研究了你的网站,由于我无法将所有这些都说成一条评论,我决定 post 一个答案(即使它没有提供具体的解决方案,但只是 "answer" 和一些提示)。
您问题的答案
How can I rectify my script to keep clicking on that Load more hits button until there is no such button is left?
不幸的是,这不是你的错。您定位的网站正在通过 Web 客户端(您的浏览器)和提供您试图抓取的价格的 Web 服务器之间的 WebSocket 通信工作。可以看到如下:
想象一下:
- 当你第一次加载你的网页时,网络套接字被初始化并发送第一个请求(网络客户端:"Hey server, give me the first X results",网络服务器:"Sure, here you go").
- 每次单击 "Load more results" 按钮时,Web 客户端(重要:重新使用相同的 WS 连接)不断要求 X 个新结果以网络服务器。
所以,通信持续了一段时间。在某些时候,超出您的控制,网络套接字恰好死了。在单击 "Load more results" 按钮的同时查看 JavaScript 控制台就足够了:您将看到请求正在处理,直到在某个时候您不只是看到 NullPointerException
引发:
如果你点击异常前堆栈的最后一行,你会看到这是因为网络套接字:
错误说得很清楚:cannot read .send() on null
,意思是_ws
(网络套接字)不见了。
从现在开始,您可以忘记您的网站。当你点击按钮 "Load more results" 时,web 客户端会要求 web socket 将新的请求传递给 web 服务器,但是 web socket 已经消失了所以再见了两者之间的通信,所以(不幸的)再见了您的其余数据。
您可以通过在堆栈中往上移一点来验证这一点:
正如您在上面看到的,我们有:
- 控制台中记录的消息说 "performSearch params ...") 就在 post 新数据请求 之前
- 新数据请求
post
- 控制台中记录的消息说 "performed search with result ...") 就在 post 新数据请求 之后
虽然 web 套接字仍然存在,但每次单击 "Load more results" 时,您都会在控制台中看到这两条消息(中间的其他消息打印在其余代码上):
然而,在网络套接字第一次崩溃后,无论您尝试点击按钮多少次,您都只会收到第一条消息(网络客户端发送请求)而永远不会收到第二条消息(请求在空白中丢失):
请注意,这与您在 VBA 中观察到的行为相对应:
the script seems to get stuck after clicking on that button 3/4 times.
它没有卡住,实际上你的脚本一直在正确执行。是网站超时了。
我试图找出网络套接字崩溃的原因,但没有成功。这似乎是一个超时(我在调试他们的 JavaScript 时遇到了很多这样的问题,所以我的断点导致了超时)但我不能让你确定这是唯一的原因。由于您无法控制 Web 客户端和 Web 服务器之间的进程,您所能做的就是希望它不会超时。
此外,我相信使用 Selenium 会自动设置一些更长的超时(因为执行时间长),这在某种程度上允许您使 Web 套接字对超时更具容忍度。
我发现在网络套接字崩溃后恢复连接的唯一方法是完全重新加载网页并从头开始重新启动进程。
我的建议
我认为您可能会构建一个 XHR 请求并通过 JavaScript 发送,因为它们的 API(网络 client/web 套接字通过它向网络服务器发送请求)在他们的前端代码中非常暴露。
如果你打开他们的文件 FinderAPI.js
,你会看到他们留下了端点和 API 配置硬编码:
var FinderAPI = {
store: null,
state: null,
finderEndpoint: '/api/v1/bsg/etp/finder/list',
bidAskEndpoint: '/api/v1/prices/bidAsk/get',
instrumentNameEndpoint: '/api/products/ProductTypeMapping/InstrumentNames',
nameMappingEndpoint: '/api/v1/bsg/general/namemapping/list',
apiConfig: false,
initialize: function initialize(store, finderEndpoint) {
var apiConfig = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
this.store = store;
this.state = store.getState();
this.apiConfig = apiConfig;
this.finderEndpoint = finderEndpoint;
},
这意味着您知道应该将 POST
请求发送到的 URL。
请求还需要服务器验证 Bearer Token。幸运的是,他们也忘记了保护他们的代币,提供 (GORSH) GET
获取代币的终点:
终点:https://www.boerse-stuttgart.de/api/products
回复: {"AuthenticationToken":"JgACxn2DfHceHL33uJhNj34qSnlTZu4+hAUACGc49UcjUhmLutN6sqcktr/T634vaPVcNzJ8sHBvKvWz","Host":"frontgate.mdgms.com"}
您只需要稍微浏览一下网站,弄清楚您的 POST 请求的主体是什么,然后创建一个新的 XmlHttpRequest
并将这些值发送到其中直接在 VBA 中检索价格,无需打开网页和机器人抓取。
我建议你从文件 FinderAPI.js
的第 66 行开始(代码行是 this.post(this.finderEndpoint, params)
,params
应该会引导你到请求的主体 -我记得您可以使用 JSON.stringify(params)
).
此外,请注意他们每次都使用 50
个结果的分页,即使他们的 API 最多支持 500
个结果。换句话说,如果您将值 500(而不是 50)扫入他们的分页 属性 发送到请求的 API:
...然后您每次将获得 500 个结果而不是 50 个,因此如果您决定不深入研究 XHR 解决方案,您的代码抓取网页的时间将减少 10 个。