Ранее в этой серии постов:
...28.
PowerShell и ЖЖ: функция «syncitems» и данные от нее, ч.4 (тесты)29.
PowerShell и ЖЖ: функция «getevents», скачивание постов (интерфейс «flat»), ч.130.
PowerShell и ЖЖ: функция «getevents», скачивание постов (интерфейс «flat»), ч.2 Окружение: операционная система «Windows 10», программа-оболочка «PowerShell» версии 7.
В предыдущих постах я получил от сервера ЖЖ («Живого Журнала») все записи об обновлениях журнала «
vbgtut» с помощью удалённой функции «
syncitems» (сервер ЖЖ выдает эти записи порциями не более 500 за раз, но для журнала «vbgtut» таких записей всего 193, поэтому их удалось получить одной порцией). Также я получил первую порцию постов в количестве 100 штук с помощью удалённой функции «
getevents» при типе отбора постов (selecttype) «syncitems» (в журнале «vbgtut» на данный момент всего 147 постов, но сервер ЖЖ выдает посты в количестве не более 100 штук за раз). В этом посте я постараюсь показать, как загрузить к себе на компьютер с сервера ЖЖ следующую порцию постов своего журнала (для журнала «vbgtut» посты можно загрузить всего двумя порциями, но для журнала с бо́льшим количеством постов порций может понадобится больше).
Подготовка вспомогательных функций
В ходе описываемых далее действий мне понадобятся четыре вспомогательных функции, которые я предварительно загружаю в текущий сеанс работы программы-оболочки. Код функций toHashTable (преобразование многострочной строки в хеш-таблицу) и getHash (получение хеш-суммы заданной строки по заданному алгоритму) можно найти в
части 1. Код функции toEventsTable (получение таблицы с постами из хеш-таблицы с параметрами) можно найти в
части 2.
Код функции toActionsTable (получение таблицы с записями об обновлении журнала из хеш-таблицы с параметрами) я описывал в
части 2 про удалённую функцию «syncitems», но теперь я внес в исходный вариант некоторые изменения, поэтому новую редакцию этой функции приведу тут:
function toActionsTable($hashT) {
$arrL = [System.Collections.ArrayList]::new()
foreach ($key in $hashT.Keys) {
if ($key -match '^.+_(.+)_(.+)$') {
$num = [int]$Matches[1]
$colName = $Matches[2]
$val = $hashT[$key]
$searchRes = $arrL | Where-Object { $_.num -eq $num }
$row = if ($searchRes) {
$searchRes[0]
} else {
$i = $arrL.Add(("" | Select-Object "num","itemT","itemN","time","downloaded"))
$arrL[$i].num = $num
$arrL[$i]
}
if ($colName -eq "item") {
$res = $val -match '^(.+)-(.+)$'
$row.itemT = $Matches[1]
$row.itemN = [int]$Matches[2]
} elseif ($colName -eq "action") {
# отбрасываю это поле за ненадобностью
} else {
$row.$colName = $val
}
}
}
return $arrL
}
Изменения я внес следующие. Во-первых, я отбросил колонку «item», так как она представлена двумя другими колонками: «itemT» и «itemN». Во-вторых, я отбросил колонку «action», так как она мне пока не нужна (может и вообще не понадобится, в документации сказано, что эта колонка имеет декоративное значение). Взамен двух отброшенных колонок я добавил одну новую - «downloaded», в соответствии с универсальным алгоритмом загрузки всех постов журнала,
описанным в документации. Колонка «downloaded» изначально по умолчанию содержит значение $null, этим я показываю, что данный пост пока не загружен. В коде приведенной выше функции все строки в поле «downloaded» содержат значение $null. Заполнение этого поля я продемонстрирую далее.
Загрузка полученных в предыдущих постах данных из кэша
PS C:\> $fromCacheS = Get-Content -Path "C:\Users\Илья\Desktop\syncItems vbgtut.txt" -Raw
PS C:\> $fromCacheS.Length
14441
PS C:\> $fromCacheE = Get-Content -Path "C:\Users\Илья\Desktop\getEvents vbgtut.txt" -Raw
PS C:\> $fromCacheE.Length
473540
Из первого текстового файла я загрузил в переменную $fromCacheS тело HTTP(S)-ответа, полученного в предыдущих постах от удалённой функции «syncitems» (там содержатся данные 193 записей об обновлении журнала «vbgtut»).
Из второго текстового файла я загрузил в переменную $fromCacheE тело HTTP(S)-ответа, полученного в предыдущих постах от удалённой функции «getevents» (там содержатся данные 100 постов журнала «vbgtut»).
Преобразование полученных данных в хеш-таблицу с параметрами
PS C:\> $paramsS = toHashTable $fromCacheS
PS C:\> $paramsS.Count
582
PS C:\> $paramsE = toHashTable $fromCacheE
PS C:\> $paramsE.Count
1434
Получение таблиц
PS C:\> $tableS = toActionsTable $paramsS
PS C:\> $tableS.Length
193
PS C:\> $tableSL = $tableS | Where-Object { $_.itemT -eq "L" }
PS C:\> $tableSL.Length
147
PS C:\> $tableE = toEventsTable $paramsE
PS C:\> $tableE.Length
100
Сначала мы получаем таблицу $tableS с записями об обновлении журнала «vbgtut». В этой таблице 193 записи (строки). Затем мы вычленяем из таблицы $tableS только записи об обновлении журнала, касающиеся постов, и помещаем эти записи в таблицу $tableSL. В этой таблице 147 записей (строк), так как в журнале «vbgtut» на данный момент содержится 147 постов. В конце мы получаем таблицу $tableE с постами журнала «vbgtut». В этой таблице содержится 100 записей (строк), так как эта таблица содержит только первую порцию постов, полученную ранее.
Пометим полученные ранее посты в таблице с обновлениями журнала
$tableE | ForEach-Object {
$itemid = $_.itemid
$searchRes = $tableSL | Where-Object { $_.itemN -eq $itemid }
$searchRes[0].downloaded = 1
}
В блоке кода выше командлет «
ForEach-Object» перебирает в цикле строки таблицы полученных ранее постов $tableE. Поле «itemid» каждой строки таблицы полученных постов $tableE сверяется с полем «itemN» таблицы с обновлениями журнала $tableSL (она содержит перечень всех постов журнала «vbgtut», в ней есть информация об идентификаторах постов, но нет текста самих постов). В таблице $tableSL все загруженные первой порцией посты помечаем, вписывая значение-число 1 в поле «downloaded».
Находим значение для входного параметра «lastsync»
Бо́льшая часть выполненных выше манипуляций нужна для нахождения значения для входного параметра «lastsync», который потребуется для следующего запуска удалённой функции «getevents», которая возвратит следующую порцию постов журнала «vbgtut». Значение этого параметра служит точкой отсчета, начиная с которой программа-сервер ЖЖ возвратит следующие 100 постов. Передвигая эту точку отсчета от самых старых дат к самым новым, мы преодолеваем ограничение программы-сервера ЖЖ на возврат не более 100 постов журнала за один раз.
В
описанном в документации алгоритме скачивания всех постов журнала предлагается просмотреть список неполученных еще постов, найти дату-время самого старого поста, вычесть 1 секунду и полученное время использовать для параметра «lastsync». Но мне кажется, что лучше будет просмотреть список уже полученных постов, найти дату-время самого нового поста и использовать эту дату-время для параметра «lastsync» (в этом случае не придется вычитать 1 секунду из даты-времени). Я проверю оба варианта.
1. Рекомендуемый в документации способ
Получим таблицу $tableSL1 с обновлениями журнала, касающимися постов, которые не были еще загружены в первой порции:
PS C:\> $tableSL1 = $tableSL | Where-Object { $null -eq $_.downloaded }
PS C:\> $tableSL1.Length
47
Найдем пост с самой старой датой-временем и получим эту дату-время в переменную $oldestTime:
PS C:\> $oldestTime = ($tableSL1.time | Measure-Object -Minimum).Minimum
PS C:\> $oldestTime
2013-03-02 11:44:39
Вычтем из полученной даты-времени 1 секунду:
PS C:\> $d = Get-Date $oldestTime
PS C:\> $oldestTime = $d.AddSeconds(-1).ToString("yyyy-MM-dd HH:mm:ss")
PS C:\> $oldestTime
2013-03-02 11:44:38
Вычитание 1 секунды из даты-времени на первый взгляд кажется простой задачей, но это не так. Следует учитывать, что такое вычитание может изменить не только количество секунд, но и количество минут, количество часов, дату, месяц и год. Поэтому я не стал изобретать велосипед, а использовал возможности структуры «
System.DateTime» платформы «.NET». Сначала я преобразовал имеющуюся строку в структуру типа «System.DateTime», использовал для вычитания 1 секунды метод «
AddSeconds» этой структуры, а затем преобразовал полученное значение обратно в строку. Обратите внимание, что в
строке формата, передаваемой в метод «ToString», регистр букв имеет значение (MM не то же самое, что mm, а HH не то же самое, что hh и так далее).
2. Альтернативный способ
Получим таблицу $tableSL2 с обновлениями журнала, касающимися постов, уже загруженных в первой порции:
PS C:\> $tableSL2 = $tableSL | Where-Object { $true -eq $_.downloaded }
PS C:\> $tableSL2.Length
100
Найдем пост с самой новой датой-временем и получим эту дату-время в переменную $newestTime:
PS C:\> $newestTime = ($tableSL2.time | Measure-Object -Maximum).Maximum
PS C:\> $newestTime
2012-06-25 20:09:40
Как видно из блоков кода выше, при втором способе действий меньше. Неожиданно большой разброс между значениями переменных $oldestTime и $newestTime объясняется тем, что в этом месте в журнале «vbgtut» как раз есть довольно большой перерыв по времени между соседними постами.
Получение второй порции постов
Для этого выполним соответствующий HTTP(S)-запрос для вызова на выполнение удалённой функции «getevents». Я приведу код только для входного параметра «lastsync» со значением $oldestTime. Код для входного параметра «lastsync» со значением $newestTime абсолютно такой же, за исключением, собственно, этого значения.
$body = @{
mode = "getchallenge"
}
$Response = Invoke-WebRequest -URI "
https://www.livejournal.com/interface/flat" `
-Body $body -Method "POST"
$params = toHashTable($Response.Content)
$hPass = getHash "пароль" "MD5"
$hResp = getHash ($params["challenge"] + $hPass) "MD5"
$body = @{
mode = "getevents"
user = "vbgtut"
auth_method = "challenge"
auth_challenge = $params["challenge"]
auth_response = $hResp
selecttype = "syncitems"
lastsync = $oldestTime # или $newestTime
ver = "1"
}
$Response = Invoke-WebRequest -URI "
https://www.livejournal.com/interface/flat" `
-Body $body -Method "POST"
$params = toHashTable($Response.Content)
$params["success"]
OK
Первичная проверка полученных данных, сохранение в текстовый файл (кэш)
PS C:\> ($Response.Content).Length
167005
PS C:\> $params.Count
708
PS C:\> $params["events_count"]
47
PS C:\> $params["prop_count"]
141
Посчитаем распределение параметров:
708 параметров = (47 постов * 6) + (141 свойств * 3) + 3 общих = 282 + 423 + 3 = 708
Сохраним полученные данные в текстовый файл:
$Response.Content | Out-File -FilePath "C:\Users\Илья\Desktop\getEvents vbgtut2.txt" `
-NoNewline -NoClobber
Сначала я хотел добавить новую порцию данных в текстовый файл с первой порцией данных. Однако, после этого данные будет неудобно преобразовывать в хеш-таблицу с помощью вспомогательной функции toHashTable. Дело в том, что в возвращаемых порциях данных программа-сервер ЖЖ каждый раз начинает нумерацию параметров с единицы. В первой порции постов они пронумерованы номерами с 1 по 100, во второй порции постов - с 1 по 47. Если параметры этих постов хранить в одном файле, то при использовании вспомогательной функции toHashTable 47 постов из второй порции данных затрут первые 47 постов из первой порции данных (из-за совпадающих ключей в разных парах ключ-значение). Чтобы этого избежать, далее я продолжу работать с порциями данных отдельно, а объединю их в одну таблицу уже после загрузки в сессию программы-оболочки.
Загрузка второй порции данных из кэша, объединение всех постов в одну таблицу
Загрузка второй порции данных из кэша и их проверка:
PS C:\> $fromCacheE2 = Get-Content -Path "C:\Users\Илья\Desktop\getEvents vbgtut2.txt" -Raw
PS C:\> $fromCacheE2.Length
167005
PS C:\> $paramsE2 = toHashTable $fromCacheE2
PS C:\> $paramsE2.Count
708
PS C:\> $tableE2 = toEventsTable $paramsE2
PS C:\> $tableE2.Length
47
Объединение всех постов в одну таблицу:
PS C:\> $table = $tableE + $tableE2
PS C:\> $table.Length
147
Для демонстрации полученной таблицы со всеми постами я сначала отсортирую эту таблицу в порядке возрастания идентификаторов постов. Затем выведу первые 5 строк таблицы и последние 5 строк таблицы:
$table = $table | Sort-Object -Property "itemid"
$table | Select-Object -First 5 |
Format-Table @{e="num";w=3}, @{e="itemid";w=6}, @{e="anum";w=4},
@{e="eventtime";w=19}, @{e="subject";w=16},
@{e="url";w=10}, @{e="event";w=16}
num itemid anum eventtime subject url event
--- ------ ---- --------- ------- --- -----
1 2 140 2010-10-16 19:53:00 Осень на улицах 652.html %D0%9B%D0%B5%D0…
2 3 240 2010-10-17 15:05:00 Траншея на авто… 1008.html %D0%96%D0%B5%D0…
3 4 108 2010-10-18 22:37:00 Режим работы сб… 1132.html %D0%9B%D0%B5%D0…
4 5 131 2010-10-20 00:21:00 Квартирный вопр… 1411.html %D0%9C%D0%B5%D0…
5 6 166 2010-10-22 00:29:00 Налоговая инспе… 1702.html %D0%A3%D0%BB%D0…
$table | Select-Object -Last 5 |
Format-Table @{e="num";w=3}, @{e="itemid";w=6}, @{e="anum";w=4},
@{e="eventtime";w=19}, @{e="subject";w=16},
@{e="url";w=10}, @{e="event";w=16}
num itemid anum eventtime subject url event
--- ------ ---- --------- ------- --- -----
43 145 46 2014-04-19 16:22:00 Строительство х… 37166.html %D0%A1%D0%BD%D0…
44 146 245 2014-06-15 01:03:00 Мемориал в памя… 37621.html %D0%A1%D0%BD%D0…
45 147 121 2014-08-09 20:05:00 Автомобили, дев… 37753.html %D0%A1%D0%BD%D0…
46 148 64 2015-04-22 01:44:00 Закат 37952.html %D0%A1%D0%BD%D0…
47 149 131 2015-04-29 00:41:00 Замки на огражд… 38275.html %D0%A1%D0%BD%D0…
Обратите внимание, что в колонке «num» сначала идут номера от 1 до 100, а затем опять от 1 до 47. Причину этого я описал выше.