Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   Скриптовые языки администрирования Windows (http://forum.oszone.net/forumdisplay.php?f=102)
-   -   [решено] Скрипт парсера потребляет много памяти и не отрабатывает полностью (http://forum.oszone.net/showthread.php?t=343301)

Uragan66 15-12-2019 21:33 2900599

Скрипт парсера потребляет много памяти и не отрабатывает полностью
 
Добрый вечер всем!
Прошу помощи у знатоков тонкостей работы PoSh.
Написал парсер, создающий плейлист одного сайта.
Скрипт работает вроде неплохо, правда достаточно долго, но это думаю связано с длительными ответами от сервера.
Но при работе скрипта постоянно растёт память, потребляемая PoSh, доходит почти до 2 ГБ. И в какой-то момент процесс просто вырубается, не доработав до конца.
При запуске через консоль выскакивает какая-то ошибка, но не успеваю её прочитать, так как консоль тут же закрывается.
Если запустить скрипт в ISE, то появляется такая ошибка:

Сам скрипт:
Код:

$url = 'http://какой-то сайт'
$file = New-TemporaryFile
$d = '#EXTM3U'
(Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 20).Links.Href | Where {$_ -match "html"} | Sort-Object -Unique `
| ForEach {$_ -replace "(.*html)","http://этот сайт`$1"} | Out-File $file.FullName

Get-Content $file.FullName -Encoding utf8 | ForEach {
$l = Invoke-WebRequest -Uri $_ -TimeoutSec 20
if ($l.Content -notmatch "iframe src") {
$n = ($l.ParsedHtml.getElementsByTagName("title") | select IHTMLTitleElement_text).IHTMLTitleElement_text -replace "(.*) смотреть онлайн прямой эфир","`$1"
$ln = ($l.ParsedHtml.getElementsByTagName("iFrame") | select IHTMLFrameBase_src).IHTMLFrameBase_src
ForEach ($link in $ln) {
((Invoke-WebRequest -UseBasicParsing -URI $link -Headers @{"Referer"="$_"}).Content | Select-String -Pattern 'file:"([^"]+)"' -AllMatches).Matches `
| ForEach-Object {"$($_.Groups[1].Value)"}| ForEach {$_ -replace "(https?.*)","#EXTINF:-1,$n`n`$1"} | Out-File .\parser.txt -Append -Encoding utf8
}
}
else {
$n = ($l.ParsedHtml.getElementsByTagName("title") | select IHTMLTitleElement_text).IHTMLTitleElement_text -replace "(.*) смотреть онлайн прямой эфир","`$1"
$ln = ($l.ParsedHtml.getElementsByTagName("iFrame") | select IHTMLFrameBase_src).IHTMLFrameBase_src
ForEach ($link in $ln) {
((Invoke-WebRequest -UseBasicParsing -URI $link -Headers @{"Referer"="$_"}).Content | Select-String -Pattern 'var videoLink.*(https?[^"]+m3u8)' -AllMatches).Matches `
| ForEach-Object {"$($_.Groups[1].Value)"}| ForEach {$_ -replace "(https?.*)","#EXTINF:-1,$n`n`$1"} | Out-File .\parser.txt -Append -Encoding utf8
}
}
}
$c = Get-Content .\parser.txt -Encoding utf8
Set-Content .\parser.txt -Encoding utf8 -value $d,$c

Подскажите, пожалуйста, где ошибка в моём коде, почему такое большое потребление памяти и преждевременное закрытие процесса.
Заранее благодарен за помощь и советы.

DJ Mogarych 15-12-2019 22:03 2900604

Прежде всего, надо поменьше пайпов и форычей.

Например, что-то в таком духе:
Код:

((Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 20).Links.Href) -match "html" | Sort-Object -Unique) `
-replace "(.*html)","http://site`$1"

Содержимое реплейса не проверял, потому что не знаю, что этим делается.

Можно вообще вот так: :)
Код:

Sort-Object -Unique -InputObject ((Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 20).Links.Href -match "html" -replace "(.*html)","http://site`$1")

Uragan66 16-12-2019 00:03 2900620

DJ Mogarych, спасибо. Заменил первый блок с Invoke-WebRequest.
Но, к сожалению, проблему это не решило.
Хотя причину нашел. Это блок :
Код:

$n = ($l.ParsedHtml.getElementsByTagName("title") | select IHTMLTitleElement_text).IHTMLTitleElement_text -replace "(.*) смотреть онлайн прямой эфир","`$1"
$ln = ($l.ParsedHtml.getElementsByTagName("iFrame") | select IHTMLFrameBase_src).IHTMLFrameBase_src

При его замене на Select-String потребление памяти не растёт.
Но почему так происходит, не пойму.

Iska 16-12-2019 03:47 2900629

Цитата:

Цитата Uragan66
При запуске через консоль выскакивает какая-то ошибка, но не успеваю её прочитать, так как консоль тут же закрывается. »

А Вы сначала запустите консоль PowerShell, а затем уже запускайте в ней скрипт. Тогда не закроется.

DJ Mogarych 16-12-2019 08:56 2900652

Uragan66, вы можете предоставить примерное содержимое переменной $l?

Busla 16-12-2019 11:17 2900665

Цитата:

Цитата DJ Mogarych
Прежде всего, надо поменьше пайпов и форычей. »

в целом крайне сомнительный совет

а уж в контексте оптимизации по памяти, направление, куда вы послали даёт ровно противоположный эффект

DJ Mogarych 16-12-2019 12:06 2900672

Цитата:

Цитата Busla
в целом крайне сомнительный совет »

как и всё, что я советую. успокойтесь.

Charg 16-12-2019 12:46 2900679

Sort-Object очень медленный (ну, с поправкой на количество вводных данных, 50 строк то он быстро отсортирует).
Зачем сортировать в начале скрипта, это что важно для дальнейшей обработки?

Uragan66 16-12-2019 13:16 2900685

Цитата:

Цитата Charg
Зачем сортировать в начале скрипта, это что важно для дальнейшей обработки? »

Charg, да, сортируются ссылки, необходимые для последующей обработки. Сервер отдаёт по две одинаковые ссылки, Sort-Object удаляет дубликаты.
Но здесь его медленность большой роли не играет.

Charg 16-12-2019 13:58 2900692

Цитата:

Цитата Uragan66
Сервер отдаёт по две одинаковые ссылки, Sort-Object удаляет дубликаты. »

А Select-Object -Unique чем не устраивает?
В любом случае каждый блок можно скормить командлету Measure-Object и померять где что и сколько времени занимает.

Uragan66 16-12-2019 14:33 2900699

Charg, забыл я как-то за Select-Object -Unique.
Сейчас код полностью переписал, работает быстро и утечки памяти больше нет.
Но возникла немного другая проблема. Сервер на некоторые ссылки возвращает 404, наверное из-за частых запросов.
Добавил в цикл Start-Sleep -s 3, но только общее время работы увеличилось, а ошибки 404 встречаются всёравно.

Iska 16-12-2019 17:29 2900734

Цитата:

Цитата Uragan66
Сервер на некоторые ссылки возвращает 404, наверное из-за частых запросов. »

Не может такого быть. 404 — не найден ресурс. Для «отфутболивания» — HTTP 5xx, обычно 503, 504.

Скорее, ошибки в этих «некоторых» url'ах. Попробуйте сформировать их в файл и проверить «ручками».

Charg 16-12-2019 17:30 2900735

Я бы добавил проверку на код ответа и если он не 2** (двухсотые это когда всё ок) - писал в stdout содержимое каждой переменной.
Так же не забывай про автоматическую переменную $error и Start\Stop-Transcript

Uragan66 16-12-2019 19:19 2900750

Charg, спасибо!
Действительно некоторые url'ы для вторых запросов формировались неправильно, RegExr глючил... Сейчас исправил, вроде всё нормально.
Нелёгкое дело отладка скрипта :sclerosis

Остался один вопрос нерешенный по данному скрипту.
Подскажите, пожалуйста, есть ли возможность на PoSh указать автоматическую вертикальную прокрутку listbox по мере его заполнения ?
Пробовал добавлять AutoScrollOffset, консоль ругается.

Charg 16-12-2019 19:58 2900758

Цитата:

Цитата Uragan66
Подскажите, пожалуйста, есть ли возможность на PoSh указать автоматическую вертикальную прокрутку listbox по мере его заполнения ? »

Ну, к павершелу это всё имеет отношение довольно формальное.
Быстрый гугл говорит что по-человечески это не сделать, но можно костылями, типа так (не пробовал):

Код:

$listBox1.SelectedIndex = listBox1.Items.Count - 1
$listBox1.SelectedIndex = -1

Цитата:

Цитата Uragan66
Действительно некоторые url'ы для вторых запросов формировались неправильно, RegExr глючил... »

Это примерно как "it's always dns" в практически любой сисадминско-виндовой проблеме - если в коде есть регулярки и что-то не работает - почти всегда виноваты регулярки.

Uragan66 16-12-2019 20:19 2900761

Цитата:

Цитата Charg
но можно костылями, типа так (не пробовал):
Код:

$listBox1.SelectedIndex = listBox1.Items.Count - 1
$listBox1.SelectedIndex = -1

»

Не хочет так работать, ошибок нет (вернее была, пропущен знак переменной), но и прокрутка стоит на месте...

Charg 16-12-2019 20:43 2900765

Uragan66, сначала заполняешь листбокс данными, потом вот этот костыль, а уже потом рисуешь листбокс? Должно быть так, если я правильно понимаю.
Ну либо листбокс - неправильный выбор для задачи которую ты решаешь с помощью листбокса.

Uragan66 16-12-2019 21:13 2900770

Цитата:

Цитата Charg
листбокс - неправильный выбор для задачи которую ты решаешь с помощью листбокса »

может быть и такое... у меня в листбоксе отображаются значения одной переменной в цикле, после каждой итерации значение этой переменной выводится в листбокс.

Iska 16-12-2019 21:19 2900772

Цитата:

Цитата Uragan66
Не хочет так работать, ошибок нет (вернее была, пропущен знак переменной), но и прокрутка стоит на месте... »

Uragan66, как бы нужен минимально достаточный код для воспроизведения Вашей ситуации. А «ошибок нет» — это в PoSH'е типичная ситуация сплошь и рядом, это не ЯВУ со строгой типизацией и контролем границ индексов.

Uragan66 16-12-2019 21:47 2900778

Код такой:
Код:

function GenerateForm {

    [reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
    [reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null

    $form1 = New-Object System.Windows.Forms.Form
    $button1 = New-Object System.Windows.Forms.Button
    $listBox1 = New-Object System.Windows.Forms.ListBox
    $RadioButton = New-Object System.Windows.Forms.RadioButton
    $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState

    $b1= $false
    $b2= $false
    $b3= $false

#----------------------------------------------
#Generated Event Script Blocks
#----------------------------------------------

    $handler_button1_Click=
    {
        $listBox1.Items.Clear();   

        if ($RadioButton.Checked) {
            $url = 'http://какой-то сайт/'
            $file = New-TemporaryFile
            $d = '#EXTM3U'
            (Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 20).Links.Href | Where {$_ -match "html"} | Sort-Object -Unique `
            | ForEach {$_ -replace "(.*html)","http://какой-то сайт`$1"} | Out-File $file.FullName
            $ls = Get-Content $file.FullName -Encoding utf8
            ForEach ($link in $ls){
                $a = Invoke-WebRequest -Uri $link -TimeoutSec 20 -Method GET
                if ($a -notmatch "iframe src") {
                    $a.Content -match '<title>(.*)</title>' | Out-Null
                    $matches[1] | ForEach-Object {[Void]$listbox1.Items.Add($_)}
                    $listBox1.SelectedIndex = $listBox1.Items.Count -1
                    $listBox1.SelectedIndex = -1
                    $n = '#EXTINF:-1,'+$matches[1]
                    $a.Content -match 'iframe.*"(https?.+php)".*' | Out-Null
                    $Global:l = $matches[1]
                    if ($l -notmatch "free") {
                        (Invoke-WebRequest -UseBasicParsing -URI $l -Headers @{"Referer"=$link}).Content -match 'file:"([^"]+)"' | Out-Null
                        $m = $matches[1]
                        Add-Content .\only-tv.m3u -Encoding utf8 -Value $n,$m
                    }
                    else {
                        continue
                    }
                }
                else {
                    $a.Content -match '<title>(.*)</title>' | Out-Null
                    $matches[1] | ForEach-Object {[Void]$listbox1.Items.Add($_)}
                    $listBox1.SelectedIndex = $listBox1.Items.Count -1
                    $listBox1.SelectedIndex = -1
                    $n = '#EXTINF:-1,'+$matches[1]
                    $a.Content -match 'iframe.*"(https?.+html)".*' | Out-Null
                    $Global:l = $matches[1]
                    (Invoke-WebRequest -UseBasicParsing -URI $l -Headers @{"Referer"=$link}).Content -match 'videoLink.*(https?[^"]+m3u8)' | Out-Null
                    $m = $matches[1]
                    Add-Content .\tv.m3u -Encoding utf8 -Value $n,$m
                }
                sleep -s 2
            }
            $c = Get-Content .\tv.m3u -Encoding utf8
            Set-Content .\tv.m3u -Encoding utf8 -value $d,$c
            Remove-Item $file.FullName -errorAction silentlycontinue
           
            $listBox1.Items.Add("Лист создан!")
        }

        if ( !$RadioButton.Checked ) {  $listBox1.Items.Add("No CheckBox selected....")}


    }


    $OnLoadForm_StateCorrection=
    {#Correct the initial state of the form to prevent the .Net maximized form issue
        $form1.WindowState = $InitialFormWindowState
    }

#----------------------------------------------
#region Generated Form Code
    $form1.Text = "TV"
    $form1.Name = "form1"
    $form1.DataBindings.DefaultDataSourceUpdateMode = 0   
    $form1.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::Fixed3D
    $form1.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Width = 450
    $System_Drawing_Size.Height = 660
    $form1.BackColor = "0x7AC5E7"
    $form1.Opacity = 0.96
    $form1.ClientSize = $System_Drawing_Size

    $button1.TabIndex = 21
    $button1.Name = "button1"
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Width = 75
    $System_Drawing_Size.Height = 23
    $button1.Size = $System_Drawing_Size
    $button1.UseVisualStyleBackColor = $True

    $button1.Text = "Run Script"

    $System_Drawing_Point = New-Object System.Drawing.Point
    $System_Drawing_Point.X = 25
    $System_Drawing_Point.Y = 585
    $button1.Location = $System_Drawing_Point
    $button1.DataBindings.DefaultDataSourceUpdateMode = 0
    $button1.add_Click($handler_button1_Click)

    $form1.Controls.Add($button1)

    $CancelButton = New-Object System.Windows.Forms.Button
    $CancelButton.Location = New-Object System.Drawing.Point(25,615)
    $CancelButton.Size = New-Object System.Drawing.Size(75,23)
    $CancelButton.Text = "Cancel"
    $CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
    $CancelButton.UseVisualStyleBackColor = $True
    $button1.TabIndex = 20
    $form1.CancelButton = $CancelButton
    $form1.Controls.Add($CancelButton)

    $listBox1.FormattingEnabled = $True
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Width = 210
    $System_Drawing_Size.Height = 640
    $listBox1.Size = $System_Drawing_Size
    $listBox1.DataBindings.DefaultDataSourceUpdateMode = 0
    $listBox1.Name = "listBox1"
    $System_Drawing_Point = New-Object System.Drawing.Point
    $System_Drawing_Point.X = 220
    $System_Drawing_Point.Y = 13
    $listBox1.Location = $System_Drawing_Point
    $listBox1.TabIndex = 19

    $form1.Controls.Add($listBox1)

    $RadioButton.UseVisualStyleBackColor = $True
    $RadioButton.Checked = $false
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Width = 154
    $System_Drawing_Size.Height = 30
    $RadioButton.Size = $System_Drawing_Size
    $RadioButton.TabIndex = 0
    $RadioButton.Text = "TV"
    $System_Drawing_Point = New-Object System.Drawing.Point
    $System_Drawing_Point.X = 25
    $System_Drawing_Point.Y = 40
    $RadioButton.Location = $System_Drawing_Point
    $RadioButton.DataBindings.DefaultDataSourceUpdateMode = 0
    $RadioButton.Name = "RadioButton"
    $RadioButton.add_CheckedChanged({
        if ($RadioButton.Checked){
            $listBox1.Items.Add( "TV"  )
        }
        else {
            $listBox1.Items.Clear(); 
        }
    })

    $form1.Controls.Add($RadioButton)


#Save the initial state of the form
    $InitialFormWindowState = $form1.WindowState
#Init the OnLoad event to correct the initial state of the form
    $form1.add_Load($OnLoadForm_StateCorrection)
#Show the Form
    $form1.ShowDialog()| Out-Null

} #End Function

#Call the Function
GenerateForm

Правда немного подредактировал, неохота адрес сайта светить в паблике...

После последней правки, код что выше выложил, автоскроллинг заработал. Правда периодически пропадает содержимое листбокса (кроме последней строки) на несколько секунд.
Но это уже мелочи.
Большое спасибо всем за советы!


Время: 19:30.

Время: 19:30.
© OSzone.net 2001-