PDA

Показать полную графическую версию : [решено] Скрипт парсера потребляет много памяти и не отрабатывает полностью


Uragan66
15-12-2019, 21:33
Добрый вечер всем!
Прошу помощи у знатоков тонкостей работы PoSh.
Написал парсер, создающий плейлист одного сайта.
Скрипт работает вроде неплохо, правда достаточно долго, но это думаю связано с длительными ответами от сервера.
Но при работе скрипта постоянно растёт память, потребляемая PoSh, доходит почти до 2 ГБ. И в какой-то момент процесс просто вырубается, не доработав до конца.
При запуске через консоль выскакивает какая-то ошибка, но не успеваю её прочитать, так как консоль тут же закрывается.
Если запустить скрипт в ISE, то появляется такая ошибка:
http://images.vfl.ru/ii/1576432844/9572b388/28919990_m.jpg (http://vfl.ru/fotos/9572b38828919990.html)
Сам скрипт:
$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
Прежде всего, надо поменьше пайпов и форычей.

Например, что-то в таком духе:
((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
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
При запуске через консоль выскакивает какая-то ошибка, но не успеваю её прочитать, так как консоль тут же закрывается. »
А Вы сначала запустите консоль PowerShell, а затем уже запускайте в ней скрипт. Тогда не закроется.

DJ Mogarych
16-12-2019, 08:56
Uragan66, вы можете предоставить примерное содержимое переменной $l?

Busla
16-12-2019, 11:17
Прежде всего, надо поменьше пайпов и форычей. »
в целом крайне сомнительный совет

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

DJ Mogarych
16-12-2019, 12:06
в целом крайне сомнительный совет »
как и всё, что я советую. успокойтесь.

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

Uragan66
16-12-2019, 13:16
Зачем сортировать в начале скрипта, это что важно для дальнейшей обработки? »
Charg, да, сортируются ссылки, необходимые для последующей обработки. Сервер отдаёт по две одинаковые ссылки, Sort-Object удаляет дубликаты.
Но здесь его медленность большой роли не играет.

Charg
16-12-2019, 13:58
Сервер отдаёт по две одинаковые ссылки, Sort-Object удаляет дубликаты. »
А Select-Object -Unique чем не устраивает?
В любом случае каждый блок можно скормить командлету Measure-Object и померять где что и сколько времени занимает.

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

Iska
16-12-2019, 17:29
Сервер на некоторые ссылки возвращает 404, наверное из-за частых запросов. »
Не может такого быть. 404 — не найден ресурс. Для «отфутболивания» — HTTP 5xx, обычно 503, 504.

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

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

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

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

Charg
16-12-2019, 19:58
Подскажите, пожалуйста, есть ли возможность на PoSh указать автоматическую вертикальную прокрутку listbox по мере его заполнения ? »
Ну, к павершелу это всё имеет отношение довольно формальное.
Быстрый гугл говорит что по-человечески это не сделать, но можно костылями, типа так (не пробовал):

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

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

Uragan66
16-12-2019, 20:19
но можно костылями, типа так (не пробовал):
$listBox1.SelectedIndex = listBox1.Items.Count - 1
$listBox1.SelectedIndex = -1 »
Не хочет так работать, ошибок нет (вернее была, пропущен знак переменной), но и прокрутка стоит на месте...

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

Uragan66
16-12-2019, 21:13
листбокс - неправильный выбор для задачи которую ты решаешь с помощью листбокса »
может быть и такое... у меня в листбоксе отображаются значения одной переменной в цикле, после каждой итерации значение этой переменной выводится в листбокс.

Iska
16-12-2019, 21:19
Не хочет так работать, ошибок нет (вернее была, пропущен знак переменной), но и прокрутка стоит на месте... »
Uragan66, как бы нужен минимально достаточный код для воспроизведения Вашей ситуации. А «ошибок нет» — это в PoSH'е типичная ситуация сплошь и рядом, это не ЯВУ со строгой типизацией и контролем границ индексов.

Uragan66
16-12-2019, 21:47
Код такой:
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
Правда немного подредактировал, неохота адрес сайта светить в паблике...

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




© OSzone.net 2001-2012