忍者ブログ

アパッチのWEBアクセスログの集計④

2020年01月14日
アパッチのWEBアクセスログをエクセルVBAで集計したい。
step1.フォルダ内の全ファイルを対象としたループを作る
step2.各ファイル内のデータを日付別にシート出力
step3.各シートで、ログデータを区切る
step4.区切られたデータを集計

前回step3を汎用性もたせて作り直した結果遅くなった。

今回はstep2を検討したら、step4のネタが思いつきました。

日付ごとにシートを分けるための処理。

まず、ログデータの日付情報は[の後の11文字が対象。
[の位置を検索して、次の文字から11文字をmidで取ればOKだね。

問題となるのは、新出のデータなら新しいシートに、
既出のデータなら既存のシートに出力する必要があること。

シート一覧を作ろうとすると、重複のしないリストを作る処理になるので、
検索してみた。

参考ページ(いつもお世話になってるオフィスOffice TANAKA)
http://officetanaka.net/excel/vba/tips/tips80.htm

重複しないリスト作りとして5つ紹介されてますが、
高速処理なVBAという観点で考えると(1)か(2)か。
(1)Dictionaryオブジェクト(連想配列)
(2)コレクション

前回作ったログデータをもとにIPアドレスやUAなどを集計したところ、
(2)のコレクションのほうが若干速い結果になった。

条件を変えて、全セルをユニークデータにしたところ、以下のとおり逆転。
(1)10万行を10秒
(2)10万行を50秒

高速に重複チェックをできるなら、カウントも入れればstep4で使えるじゃんってことで、
先にstep4をやることにした。

列のデータを配列に格納して、ユニークデータに対するカウント数を出力するマクロ。
ソースはこちら。

Sub 列集計_配列型()
'大文字小文字区別 空欄対応
Dim tictoc As Double
tictoc = Timer
Dim Col As Long: Col = Selection.Column
Dim Gyo As Long, GyoEnd As Long, TGyo As Long, ColName As String
If Cells(1, Col) <> "" Then
    TGyo = 1
Else
    TGyo = Cells(1, Col).End(xlDown).Row
End If
    ColName = Cells(TGyo, Col).Text
GyoEnd = Cells(Rows.Count, Col).End(xlUp).Row
If GyoEnd = 1 Then
    MsgBox "データがありません"
    Exit Sub
End If
    Dim Dic, i As Long, buf As String, Keys
    Set Dic = CreateObject("Scripting.Dictionary")
    For i = TGyo + 1 To GyoEnd
        buf = Cells(i, Col).Value
        If Not Dic.Exists(buf) Then
            Dic.Add buf, 1
        Else
            Dic(buf) = Dic(buf) + 1
        End If
    Next i
    
    ''出力
    Worksheets.Add
    Dim oArr() As Variant
    ReDim oArr(Dic.Count + 1, 2)
    
    oArr(0, 0) = "№"
    oArr(0, 1) = ColName
    oArr(0, 2) = "カウント"
    
    Keys = Dic.Keys
    For i = 0 To Dic.Count - 1
        oArr(i + 1, 0) = i + 1
        oArr(i + 1, 1) = Keys(i)
        oArr(i + 1, 2) = Dic(Keys(i))
    Next i
    Set Dic = Nothing
    
    Dim NoCell As Range
    Set NoCell = Cells(2, 1)
    
    Call ArrayToCell(NoCell, oArr)
    
    NoCell.AutoFilter
    With ActiveSheet.AutoFilter.Range
        .Sort Key1:=.Range("C2"), Order1:=xlDescending, Header:=xlYes
    End With
        
    Cells(1, 1) = Format(i, "#,##0種")
    Cells(1, 2) = "合計:"
    Cells(1, 3) = Format(GyoEnd - TGyo, "#,##0件")
    
    On Error Resume Next
    ActiveSheet.Name = ColName & "_内訳"
    Call ArrayToCell(NoCell, oArr, 1)     ' №の入った1列目だけ再出力
    
'Debug.Print "[" & Now & "] "; Format(GyoEnd - 1, "#,##0行を") & Format(Timer - tictoc, "0.00秒")
End Sub

Sub ArrayToCell(Target As Range, oArr, Optional ColCnt As Long = 0) '配列貼り付け便利モジュール
    Dim iRow        '// データ設定用の行カウンタ
    Dim iCol        '// データ設定用の列カウンタ
    Dim iRowMax     '// 二次元配列の最大行数
    Dim iColMax     '// 二次元配列の最大列数
       
    '// 1次元目の要素数を取得
    iRowMax = UBound(oArr, 1) - LBound(oArr, 1) + 1
    '// 2次元目の要素数を取得
    iColMax = UBound(oArr, 2) - LBound(oArr, 2) + 1
    
    '// Rangeオブジェクトで貼り付けるセル範囲を指定する場合
    'Range(Cells(1, 1), Cells(iRowMax, iColMax)).Value = oArr
    
    '// Rangeオブジェクトで開始セルから貼り付けるセル範囲を拡張する場合
    If ColCnt > 0 And ColCnt <= iColMax Then
        Target.Resize(iRowMax, ColCnt).Value = oArr
    Else
        Target.Resize(iRowMax, iColMax).Value = oArr
    End If
End Sub

コメント
  • 列データ集計は業務上使用頻度が高く、結構便利なモジュール。
  • 重複のないリストを作るだけであれば、Dic.Exists(buf)の判定処理は不要だが
    今回はカウント処理をするために残した。
  • Dictionaryオブジェクトでは、キーデータはユニークである必要
    試した結果、キーはブランクOK。大文字小文字の区別あり。
PR
Comment
  Vodafone絵文字 i-mode絵文字 Ezweb絵文字