在PowerShell的mshtml.HTMLDocumentClass对象上使用querySelectorAll会导致崩溃
我试图通过PowerShell进行一些网页抓取,因为我最近发现可以在没有太多麻烦的情况下这样做。在PowerShell的mshtml.HTMLDocumentClass对象上使用querySelectorAll会导致崩溃
一个很好的出发点是只取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文档对象documented here:
querySelector()
querySelectorAll()
所以不是这样做的:
$html.ParsedHtml.getElementsByTagName("img")[0]
我可以这样做:
$html.ParsedHtml.querySelector("img")
所以我期待能够做到:
$html.ParsedHtml.querySelectorAll("img")
...为了获得所有的IMG元素。我发现的所有文档和我已经完成的搜索结果都支持这一点。然而,在我所有的测试中,这个函数崩溃了调用进程,并在事件日志(0xc0000374)中报告堆损坏异常代码。
我在Windows 10 x64上使用PowerShell 5。我已经在Win10 x64虚拟机中试过了,它是一个干净的版本,只是补丁。我也在Win7 x64升级到PowerShell 5的时候尝试了它。在PowerShell 5之前,我还没有尝试过它,因为我们所有的系统都升级了,但是我可能会有一次有时间为一个新的vanilla虚拟机进行测试。
有没有人跑过这个问题呢?到目前为止,我所有的研究都是死路一条。是否有替代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()
希望我没有违反任何规则和这个职位是有道理的,是相关的,谢谢。
UPDATE
我测试了早期版本的PowerShell。 v2-v4使用InternetExplorer.Application COM方法崩溃。 v3-4使用Invoke-WebRequest方法崩溃,v2不支持它。
我也遇到了这个问题,posted about it on reddit。我相信当Powershell尝试枚举由querySelectorAll()
返回的HTML DOM NodeList object时会发生问题。 childNodes()
可以通过PS枚举返回相同的对象,所以我猜想有一些代码为.ParsedHtml.childNodes
写入,但不是.ParsedHtml.querySelectorAll()
。 Intellisense也试图为对象获取制表符完整帮助,从而触发崩溃。
虽然我找到了解决办法!只需直接访问本机DOM方法.item()
和.length
并将节点对象发送到PowerShell阵列中即可。以下代码从/ r/Powershell中提取帖子的最新页面,通过querySelectorAll()
获取帖子列表锚点,然后使用本地DOM方法手动枚举它们到Powershell本机数组中。
$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数组中的节点。
@ midnightfreddie的解决方案对我来说工作得很好,但现在调用时抛出Exception from HRESULT: 0x80020101
。
我发现了以下解决方法:为New-Object -ComObject InternetExplorer.Application
function Invoke-QuerySelectorAll($node, [string] $selector)
{
$nodeList = $node.querySelectorAll($selector)
$nodeListType = $nodeList.GetType()
$result = @()
for ($i = 0; $i -lt $nodeList.length; $i++)
{
$result += $nodeListType.InvokeMember("item", [System.Reflection.BindingFlags]::InvokeMethod, $null, $nodeList, $i)
}
return $result
}
这一个工程,以及。
感谢您的回应,这肯定是有见地的。我可以按照你的建议进行操作,我可以在'$ PsNodeList'数组中填入'$ NodeList'元素。但是,我注意到这只有在使用'Invoke-WebRequest'时才有效。如果使用'New-Object -ComObject InternetExplorer.Application',它会抛出'异常来自HRESULT:0x80020101' :( 我试图做一个交互式刮板,所以如果可能的话,我宁愿使用IE ComObject。我会继续研究,现在,至少很高兴知道有''Invoke-WebRequest'的结果有一个解决方法 – TheKojukinator
嗯,我无法得到OP IE“工作”代码,直到我使用32位Powershell但是我的最大努力无法让它返回'.item()'的结果。 哎呦命中输入...仍然编辑 我确实得到了真正的聪明人的攻击,做了一些很酷的事情,但没有回到Powershell到目前为止 我说:“拧它,我们有DOM,让我们插入一些JavaScript。”所以这个Powershell代码注入'
呃没有足够的代码空间,它不会让我再次回复。 K,这是一个代码的要点:https://gist.github.com/midnightfreddie/4b6622793846988f0edbac05f26bb645 – midnightfreddie