最後に
さて、ずいぶん放置してしまったこちらの話ですが、ここではスナップショットとしてのデータを参照する方法を述べてきました。
その後もデータを貯め続けていたのですが、昨年の9月から10月にかけて、一時フォルダを削除してしまったためにデータを取れなくなってしまっていました。その後、再開時にスクリプトを修正し、メモリの使用を減らすことにしました。結局のところ、JSONをオンラインで使用するのは諦めて、ファイルとして追記する形に変更したところ、実行時間も短く動作は大幅に安定することになりました。
こうして貯めたデータは、今はDBに取り込んで、時系列に沿った解析なども出来るようにしてみてあります。ですので、スナップショットを解析する、という形でのこの項は、最後に更新したスクリプトを掲載してクローズすることにします。
このデータを使って、過去データの解析をしているエッセイを、もし目にされることがありましたら、笑ってごらんになっていただけると幸いです。
以下、更新したスクリプト
-------
# なろうAPIのベースurl
$apiBase = "https://api.syosetu.com/novelapi/api/?out=json"
# 出力ディレクトリ
$outputBase = 'g:\narou'
# DateTimeOffsetをUnix epoch秒に変換する
function ToEpoch {
Param (
[DateTimeOffset] $dt
)
return $dt.ToUnixTimeSeconds()
}
# サンプル開始日時(JST)
$from = ToEpoch "2004/5/1"
#$from = ToEpoch "2022/7/1"
# 重複取得をチェックするためのハッシュ
$checker = @{}
# gzipの展開関数
# https://social.technet.microsoft.com/Forums/windowsserver/en-US/5aa53fef-5229-4313-a035-8b3a38ab93f5/unzip-gz-files-using-powershell?forum=winserverpowershell
Function DeGZip-File{
Param(
$infile
)
$input = New-Object System.IO.FileStream $inFile, ([IO.FileMode]::Open), ([IO.FileAccess]::Read), ([IO.FileShare]::Read)
$output = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress)
$buffer = New-Object byte[](1024)
while($true){
$read = $gzipstream.Read($buffer, 0, 1024)
if ($read -le 0){break}
$output.Write($buffer, 0, $read)
}
$gzipStream.Close()
$input.Close()
$dummy = $output.Seek(0, [System.IO.SeekOrigin]::Begin)
$reader = New-Object System.IO.StreamReader $output
return $reader.ReadToEnd()
}
# 一定範囲のデータ取得
function GetData(
[long] $from,
[long] $to,
[string] $base,
[System.Collections.ArrayList] $result,
[int] $count
){
$current = 1
while ($current -lt $count) {
try {
$res = Invoke-WebRequest ($apiBase + "&lim=500&gzip=5&st=" + $current + "&lastup=" + $from + "-" + $to) -OutFile "c:/temp/narou.tmp.gz"
$global:fileSize += (Get-Item c:\temp\narou.tmp.gz).Length
#Write-Output $global:fileSize
$content = DeGZip-File "c:/temp/narou.tmp.gz"
$json = ConvertFrom-Json $content
for ($i = 1; $i -lt $json.Count; $i++) {
$ncode = $json[$i].ncode
if ($checker[$ncode] -eq $Null) {
$dummy = $result.Add($json[$i])
$checker[$ncode] = $True
}
else {
Write-Output "Duplicate:" + ncode
}
}
if ($json.Count -eq 1) {
Write-Output "Short Data"
break
}
$current += $json.Count - 1
}
catch {
$r = $_.Exception.Response
$rs = $r.GetResponseStream()
$rs.Position = 0
$sr = [System.IO.StreamReader]::new($rs)
$res = $sr.ReadToEnd()
$sr.Close()
Write-Error $res
Start-Sleep 60
}
}
}
# 一定範囲のデータ件数取得
function GetCount(
[Long] $from,
[Long] $to,
[String] $base
){
$url = $base + "&lastup=" + $from + "-" + $to + "&lim=1"
while ($true) {
try {
$result = Invoke-WebRequest $url
$json = ConvertFrom-Json $result.Content
return $json[0].allcount
}
catch {
Write-Error $_
$r = $_.Exception.Response
$rs = $r.GetResponseStream()
$rs.Position = 0
$sr = [System.IO.StreamReader]::new($rs)
$res = $sr.ReadToEnd()
$sr.Close()
Write-Error $res
Start-Sleep 6000
}
}
}
# 取得件数が500-2000件となるように、ウィンドウを調整する
function GetLimit(
[Long] $start,
[String] $base,
[Long] $step
){
$count = GetCount $start ($start + $step) $base
while ($count -le 500) {
$step *= 2
$count = GetCount $start ($start + $step) $base
}
while ($count -ge 2000) {
$step /= 2
$count = GetCount $start ($start + $step) $base
}
return $count, $step
}
# 取得データを保存する変数
$result = New-Object System.Collections.ArrayList
# ダウンロードしたデータ長
$global:fileSize = 0
# ウィンドウ変数
$now = [DateTimeOffset]::Now
$limit = ToEpoch $now
$step = $limit - $from
# JSONの出力
# FilePathの引数がJSONの出力ファイル名
$targetJson = $outputBase + '\narou.json'
$targetZip = $outputBase + '\narou.' + (Get-Date -Format "yyyyMMdd") + ".zip"
Out-File -FilePath $targetJson -Force -InputObject "["
# サンプルを開始する
$first = $true
while ($from -lt $limit) {
$fromStr = ([DateTimeOffset]::FromUnixTimeSeconds($from).ToString())
Write-Output $fromStr
$temp = GetLimit $from $apiBase $step
$count = $temp[0]
$step = $temp[1]
GetData $from ($from + $step) $apiBase $result $count
$from += $step + 1
if ($first) {
$first = $false
$prefix = ""
}
else {
$prefix = ","
}
$str = ConvertTo-Json -Compress -InputObject $result
$str = $prefix + $str.Substring(1, $str.Length - 2)
OUt-File -FilePath $targetJson -Append -InputObject $str
$result = New-Object System.Collections.ArrayList
}
Out-File -FilePath $targetJson -Append -InputObject "]"
##ConvertTo-Json -Compress $result | Out-File -FilePath $targetJson -Force
# 以下は、日付別のzipに固めるためのもの
Compress-Archive -DestinationPath $targetZip -Path $targetJson
Write-Output ("All Done, Transfer Size:" + $global:fileSize)