2016-05-12 120 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 
} 

這一個工程,以及。