powershell - 在PowerShell中的mshtml.HTMLDocumentClass对象上使用querySelectorAll会导致崩溃

标签 powershell com mshtml powershell-5.0 selectors-api

我正在尝试通过PowerShell进行一些Web爬网,因为我最近发现这样做是没有太大麻烦的。

一个很好的起点是只获取HTML,使用Get-Member,然后看我可以从那里做什么,就像这样:

$html = Invoke-WebRequest "https://www.google.com"
$html.ParsedHtml | Get-Member

我可以用来获取特定元素的方法如下:
getElementById()
getElementsByName()
getElementsByTagName()

例如,我可以像这样获得文档中的第一个IMG标签:
$html.ParsedHtml.getElementsByTagName("img")[0]

但是,在对我可以使用CSS选择器还是XPath进行了更多研究之后,我发现有可用的未列出方法,因为我们仅使用HTML Document对象documented here:
querySelector()
querySelectorAll()

因此,不要这样做:
$html.ParsedHtml.getElementsByTagName("img")[0]

我可以:
$html.ParsedHtml.querySelector("img")

所以我期望能够做到:
$html.ParsedHtml.querySelectorAll("img")

...以获取所有IMG元素。我找到的所有文档以及完成的Google搜索都支持此操作。但是,在我所有的测试中,此函数使调用过程崩溃,并在事件日志(0xc0000374)中报告堆损坏异常代码。

我在Windows 10 x64上使用PowerShell 5。我已经在Win10 x64虚拟机中尝试过了,它是一个干净的版本,并且刚刚进行了修补。我还在升级到PowerShell 5的Win7 x64中进行了尝试,因为在此将所有系统升级后,在PowerShell 5之前的任何版本上都没有尝试过,但是一旦有时间我可以假脱机测试新的普通VM进行测试。

以前有人遇到过这个问题吗?到目前为止,我所有的研究都是死胡同。有没有querySelectorAll的替代品?我需要抓取一些页面,这些页面在不可预测的布局内将具有可预测的标记集,并且可能没有分配给标记的ID或类,因此我希望能够使用允许结构/嵌套/通配符的选择器。

P.S.我还尝试在PowerShell中使用InternetExplorer.Application COM对象,结果相同,只是PowerShell崩溃而不是Internet Explorer崩溃。这实际上是我最初的方法,下面是代码:
# create browser object
$ie = New-Object -ComObject InternetExplorer.Application

# make browser visible for debugging, otherwise this isn't necessary for function
$ie.Visible = $true

# browse to page
$ie.Navigate("https://www.google.com")
# wait till browser is not busy
Do { Start-Sleep -m 100 } Until (!$ie.Busy)

# this works
$ie.document.getElementsByTagName("img")[0]

# this works as well
$ie.document.querySelector("img")

# blow it up
$ie.document.querySelectorAll("img")

# we wanna quit the process, but since we blew it up we don't really make it here
$ie.Quit()

希望我没有违反任何规则,这篇文章有意义并且相关,谢谢。

更新

我测试了早期的PowerShell版本。使用InternetExplorer.Application COM方法使v2-v4崩溃。 v3-4使用Invoke-WebRequest方法崩溃,v2不支持它。

最佳答案

我也遇到了这个问题,还有posted about it on reddit。我相信,当Powershell尝试枚举querySelectorAll()返回的HTML DOM NodeList object时,就会发生此问题。 childNodes()返回相同的对象,而PS可以枚举该对象,因此我猜有一些为.ParsedHtml.childNodes写的胶水代码,但没有为.ParsedHtml.querySelectorAll()写的胶水代码。崩溃也可以由Intellisense尝试获取对象的制表符完整帮助来触发。

不过,我找到了解决方法!只需直接访问 native DOM方法.item().length,然后将节点对象发送到PowerShell数组中即可。以下代码从/r/Powershell中获取最新的帖子页面,通过querySelectorAll()获取帖子列表 anchor ,然后使用 native DOM方法手动将它们枚举到Powershell-native数组中。

$Result = Invoke-WebRequest -Uri "https://www.reddit.com/r/PowerShell/new/"

$NodeList = $Result.ParsedHtml.querySelectorAll("#siteTable div div p.title a")

$PsNodeList = @()
for ($i = 0; $i -lt $NodeList.Length; $i++) { 
    $PsNodeList += $NodeList.item($i)
}

$PsNodeList | ForEach-Object {
    $_.InnerHtml
}

编辑.Length似乎可以大写或小写。我本来希望DOM区分大小写,所以要么正在进行某些事情来帮助翻译,要么我误会了某些东西。另外,CSS选择器正在获取源链接​​(主要是self.PowerShell),但这是我的CSS选择器逻辑错误,而不是querySelectorAll()的问题。注意querySelectorAll()的结果不是实时的,因此修改它们不会修改原始DOM。而且我还没有尝试修改它们或使用它们的方法,但是很明显,我们至少可以捕获.InnerHtml

编辑2:这是一个更通用的包装函数:
function Get-FixedQuerySelectorAll {
    param (
        $HtmlWro,
        $CssSelector
    )
    # After assignment, $NodeList will crash powershell if enumerated in any way including Intellisense-completion while coding!
    $NodeList = $HtmlWro.ParsedHtml.querySelectorAll($CssSelector)

    for ($i = 0; $i -lt $NodeList.length; $i++) {
        Write-Output $NodeList.item($i)
    }
}
$HtmlWro是HTML Web响应对象,是Invoke-WebReqest的输出。我最初尝试传递.ParsedHtml,但随后在分配时会崩溃。以这种方式执行此操作将返回Powershell阵列中的节点。

关于powershell - 在PowerShell中的mshtml.HTMLDocumentClass对象上使用querySelectorAll会导致崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37196558/

相关文章:

c# - 如何允许 IErrorHandlers 处理 WCF 中的 AccessViolationExceptions?

c# - 切换到其他框架(.Net WebBrowser、MsHTML)时拒绝访问跨域异常

delphi - 如何触发 HTML 表单的 onsubmit 事件?

powershell - 如何仅从PowerShell中的哈希表获取值?

Powershell DSC xChrome 示例在 AzureVM 上失败

bash - 获取每个用户名的计数,作为它们的最后一个修饰符的文件数量

javascript - 如何使用 WPF WebBrowser 覆盖网页上的 btnSubmit_onclick javascript 函数?

c# - 如何在 PowerShell 或 C# 中获取进程的命令行信息

c++ - 如何在 C/C++ 代码中使用 HTMLElement 类

c# - 如何在管理控制台中打开所选对象的属性对话框?