2016-05-12 16 views
4

Я пытаюсь сделать некоторые веб-скребки с помощью PowerShell, так как я недавно обнаружил, что это можно сделать без особых проблем.Использование querySelectorAll в объекте mshtml.HTMLDocumentClass в 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 селекторы или XPath, я обнаружил, что есть приватные методы доступны, так как мы только с помощью HTML объекта Document documented here:

querySelector() 
querySelectorAll() 

Так вместо того, чтобы делать:

$html.ParsedHtml.getElementsByTagName("img")[0] 

я могу сделать:

$html.ParsedHtml.querySelector("img") 

Так что я ожидал, чтобы быть в состоянии сделать:

$html.ParsedHtml.querySelectorAll("img") 

... для того, чтобы получить все элементы IMG. Вся документация, которую я нашел, и поиск в Google, я сделал, поддерживает это. Тем не менее, во всем моем тестировании эта функция выдает вызывающий процесс и сообщает код исключения кучи коррупции в журнале событий (0xc0000374).

Я использую PowerShell 5 на Windows 10 x64. Я пробовал это в Win10 x64 VM, которая является чистой сборкой и просто исправлена. Я также попробовал это в Win7 x64, обновленном до PowerShell 5. Я не пробовал его ни перед чем до PowerShell 5, поскольку все наши системы здесь обновлены, но я, вероятно, когда-нибудь успею создать новую виртуальную виртуальную машину для тестирования ,

Неужели кто-нибудь сталкивается с этим вопросом раньше? Все мои исследования до сих пор являются тупиком. Существуют ли альтернативы запросуSelectorAll? Мне нужно очистить страницы, которые будут иметь предсказуемые наборы тегов внутри непредсказуемых макетов, и, возможно, нет идентификаторов или классов, назначенных для тегов, поэтому я хочу иметь возможность использовать селектор, который допускает структуру/вложенность/подстановочные знаки.

P.S. Я также пробовал использовать объект InternetExplorer.Application COM в PowerShell, результат тот же, за исключением того, что 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 пытается перечислить HTML DOM NodeList object, возвращенный querySelectorAll(). Тот же объект возвращается 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 
} 

Edit .Length, кажется, работает заглавную или строчную. Я бы ожидал, что DOM будет чувствителен к регистру, так что есть некоторые вещи, которые помогут перевести, или я что-то не понимаю. Кроме того, селектор CSS захватывает исходные ссылки (self.PowerShell в основном), но что это моя логическая ошибка селектора CSS, а не проблема с querySelectorAll(). Обратите внимание, что результаты querySelectorAll() не являются живыми, поэтому их изменение не будет изменять исходный DOM. И я еще не пробовал модифицировать их или использовать их методы, но, очевидно, мы можем взять как минимум .InnerHtml.

Edit 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 Object Web Response, выход Invoke-WebReqest. Я изначально пытался пройти .ParsedHtml, но потом он сработал при назначении. Выполнение этого способа возвращает узлы в массиве Powershell.

+0

Спасибо за ваш ответ, это, безусловно, проницательно. Я смог следовать вашему предложению, и я могу получить доступ к элементам '$ NodeList' после того, как они заполнены массивом' $ PsNodeList'. Однако я заметил, что это работает только при использовании 'Invoke-WebRequest'. Если вы используете «New-Object -ComObject InternetExplorer.Application», он бросает меня «Исключение из HRESULT: 0x80020101' :( Я пытаюсь создать интерактивный скребок, поэтому, если возможно, я предпочел бы использовать IE ComObject. Продолжайте исследовать. На данный момент, по крайней мере, приятно знать, что есть способ обхода решения «Invoke-WebRequest». – TheKojukinator

+0

Хмм. Я не смог заставить код IE работать «работает», пока не использовал 32-разрядную версию Powershell Но мои самые лучшие усилия не могли заставить его вернуть результат '.item()'. oops hit enter ... still edit Я действительно получил атаку действительно умников и сделал что-то классное, но не смог верните его в Powershell до сих пор. Я сказал: «Вверните его, у нас есть DOM, давайте вставим JavaScript». И поэтому этот код Powershell вводит '

2

Решение @ 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, а также.