我试图通过PowerShell进行一些网络抓取,因为我最近发现可以毫不费力地这样做。
一个好的起点是只获取HTML,使用Get-Member,看看我能从那里做些什么,如下:
$html = Invoke-WebRequest "https://www.google.com"
$html.ParsedHtml | Get-Member
我可用于获取特定元素的方法如下所示:
getElementById()
getElementsByName()
getElementsByTagName()
例如,我可以在文档中获得第一个IMG标记:
$html.ParsedHtml.getElementsByTagName("img")[0]
然而,在对我是否可以使用CSS Selectors或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虚拟机中尝试过它,这是一个干净的构建并且只是修补了。我也尝试过将Win7 x64升级到PowerShell 5.在PowerShell 5之前我还没有尝试过,因为我们这里的所有系统都已经升级了,但我可能会有一次有时间来换新的vanilla 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版本。 v2-v4使用InternetExplorer.Application COM方法崩溃。 v3-4使用Invoke-WebRequest方法崩溃,v2不支持它。
答案 0 :(得分:3)
@ midnightfreddie的解决方案以前对我来说很好,但现在在调用Exception from HRESULT: 0x80020101
时它会抛出$NodeList.item($i)
。
我找到了以下解决方法:
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
}
这个适用于New-Object -ComObject InternetExplorer.Application
。
答案 1 :(得分:2)
我也遇到了这个问题,posted about it on reddit。我相信当Powershell尝试枚举querySelectorAll()
返回的HTML DOM NodeList object时会发生问题。 childNodes()
返回相同的对象,可以由PS枚举,因此我猜测为.ParsedHtml.childNodes
而不是.ParsedHtml.querySelectorAll()
编写了一些粘合代码。 Intellisense尝试获取对象的制表符完整帮助也可以触发崩溃。
.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数组中的节点。