2016-05-12 117 views
4

我试图通过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不支持它。

回答

2

我也遇到了这个问题,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数组中的节点。

+0

感谢您的回应,这肯定是有见地的。我可以按照你的建议进行操作,我可以在'$ PsNodeList'数组中填入'$ NodeList'元素。但是,我注意到这只有在使用'Invoke-WebRequest'时才有效。如果使用'New-Object -ComObject InternetExplorer.Application',它会抛出'异常来自HRESULT:0x80020101' :( 我试图做一个交互式刮板,所以如果可能的话,我宁愿使用IE ComObject。我会继续研究,现在,至少很高兴知道有''Invoke-WebRequest'的结果有一个解决方法 – TheKojukinator

+0

嗯,我无法得到OP IE“工作”代码,直到我使用32位Powershell但是我的最大努力无法让它返回'.item()'的结果。 哎呦命中输入...仍然编辑 我确实得到了真正的聪明人的攻击,做了一些很酷的事情,但没有回到Powershell到目前为止 我说:“拧它,我们有DOM,让我们插入一些JavaScript。”所以这个Powershell代码注入'

2

@ 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 
} 

这一个工程,以及。