我正在尝试通过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/