私のウェブサイトを訪問する各ユーザー(IPアドレス)が一連のページを見ている時間を比較して、ウェブサイト上の人ではなく活動を識別し、IPアドレスを分析から除外したいと思います。
私はawkを学び、改善したいので、できるだけ使いたいです(私はGAWKを使っています)。しかし、私はbashで他のツールを使用するためにオープンです。
次の形式の変更されたログファイル(output.csv)があります。
29/Oct/2020:07:41:42|111.111.111.111|200|/page-a/
29/Oct/2020:08:30:40|000.111.026.111|200|/page-a/
29/Oct/2020:08:30:44|000.111.026.111|200|/page-b/
29/Oct/2020:08:30:45|000.111.026.111|200|/page-c/
29/Oct/2020:08:30:47|000.111.026.111|200|/page-d/
29/Oct/2020:08:30:47|220.171.008.221|200|/page-h/
29/Oct/2020:08:30:48|000.111.026.111|200|/page-e/
29/Oct/2020:08:41:49|221.651.943.323|200|/page-a/
29/Oct/2020:08:41:52|060.121.125.144|200|/page-f/
29/Oct/2020:08:41:52|060.121.125.144|200|/page-g/
29/Oct/2020:08:41:54|000.111.026.111|200|/page-k/
29/Oct/2020:08:41:55|060.121.125.144|200|/page-l/
29/Oct/2020:08:41:57|060.121.125.144|200|/page-n/
29/Oct/2020:08:41:58|060.121.125.144|200|/page-s/
私は次のことをしたいと思います:
- すべての一意のIP検索
output.csv
- このIPのインスタンスが5つ以上の場合は、各行の最初の日付/時刻と5番目の日付/時刻の差(秒単位)を計算します。
- 15秒以内に5ページに接続したIPアドレスの分離
- この IP アドレスを次に追加します。
file.txt
私が試したこと
特定の数のIPアドレスインスタンス間の時間差(秒)を取得するには、次のコマンドセットを使用しました。
egrep "000.111.000.111" output.csv | awk 'BEGIN{FS="|"; ORS=" "} NR==1 || NR==5 {print $1,$2}' | sed -e 's/[\/:]/\ /g' -e 's/Jan/1/g' -e 's/Feb/2/g' -e 's/Mar/3/g' -e 's/Apr/4/g' -e 's/May/5/g' -e 's/Jun/6/g' -e 's/Jul/7/g' -e 's/Aug/8/g' -e 's/Sep/9/g' -e 's/Oct/10/g' -e 's/Nov/11/g' -e 's/Dec/12/g' | awk '{print $3,$2,$1,$4,$5,$6 "," $10,$9,$8,$11,$12,$13","$14}' | awk -F, '{d2=mktime($2);d1=mktime($1);print d2-d1, $3}' | awk '{if($1<15)print $2}' >> file.txt
特定のIPアドレスが15秒以内に5ページにアクセスすると、上記のコマンドはIPをファイルに追加します。
これが機能している間は、単一のコマンド/スクリプトですべての一意のIPでこれを行う方法を探しています。
私も面倒だと思うので、よりエレガントなアプローチで開いています。
希望する結果
望ましい結果は、14秒間に5ページ以上の速度でサーバーにアクセスしたIPアドレスのリストを含むファイルです(時間調整可能)。
例えば。file.txt
上記の例によると、内容は次のとおりです。
000.111.026.111
060.121.125.144
理想的には、あなたの方法を段階的に説明し、それがどのように機能するかを説明していただきありがとうございます。これは私が学ぶのに役立ちます。
答え1
GNU awkを使用してmktime()を実行します。
$ cat tst.awk
BEGIN { FS = "|" }
(++count[$2]) ~ /^[15]$/ {
split($1,t,"[/:]")
monthNr = (index("JanFebMarAprMayJunJulAugSepOctNovDec",t[2])+2)/3
currSecs = mktime(t[3] " " monthNr " " t[1] " " t[4] " " t[5] " " t[6])
if ( count[$2] == 1 ) {
firstSecs[$2] = currSecs
}
else if ( (currSecs - firstSecs[$2]) < 15 ) {
print $2
}
}
$ awk -f tst.awk file
000.111.026.111
060.121.125.144
私はこれが何をしているのかは非常に明確だと思うので、テキストの説明を追加する必要はありませんが、質問があればいつでもお問い合わせください。
ああ、特定の問題を解決するのに十分な包括的な例を投稿できるように、IPアドレスをダミー値に変換する方法を知りたいとコメントしています。
$ awk '
BEGIN { FS=OFS="|" }
!($2 in map) { ip=sprintf("%012d",++cnt); gsub(/.../,"&.",ip); sub(/.$/,"",ip); map[$2]=ip }
{ $2=map[$2]; print }
' file
29/Oct/2020:07:41:42|000.000.000.001|200|/page-a/
29/Oct/2020:08:30:40|000.000.000.002|200|/page-a/
29/Oct/2020:08:30:44|000.000.000.002|200|/page-b/
29/Oct/2020:08:30:45|000.000.000.002|200|/page-c/
29/Oct/2020:08:30:47|000.000.000.002|200|/page-d/
29/Oct/2020:08:30:47|000.000.000.003|200|/page-h/
29/Oct/2020:08:30:48|000.000.000.002|200|/page-e/
29/Oct/2020:07:41:49|000.000.000.004|200|/page-a/
29/Oct/2020:08:41:52|000.000.000.005|200|/page-f/
29/Oct/2020:08:41:52|000.000.000.005|200|/page-g/
29/Oct/2020:08:41:54|000.000.000.002|200|/page-k/
29/Oct/2020:08:41:55|000.000.000.005|200|/page-l/
29/Oct/2020:08:41:57|000.000.000.005|200|/page-n/
29/Oct/2020:08:41:58|000.000.000.005|200|/page-s/
編集:私のスクリプトで生成された出力と実行されたDavesスクリプトのバージョンで生成された出力の違いを調べることができます。
$ awk -f morton-botfilter.awk.txt output3test.csv > morton.out
$ awk -f dave-botfilter.awk.txt output3test.csv > dave.out
$ ip=$(comm -13 <(sort morton.out) <(sort dave.out) | head -1)
$ grep "$ip" output3test.csv | head -5
03/Nov/2020:07:52:55|000.000.000.007|200|/page-7/
03/Nov/2020:08:05:32|000.000.000.007|200|/page-11/
03/Nov/2020:11:28:56|000.000.000.007|200|/page-77/
03/Nov/2020:13:52:32|000.000.000.007|200|/page-143/
03/Nov/2020:13:52:33|000.000.000.007|200|/page-144/
上記の最初のタイムスタンプと最後のタイムスタンプの間の間隔が15秒をはるかに超えることに注意してください。これは、dave-botfilter.awk.txt のスクリプトが破損していることを示します。詳しくは下記のコメントをご覧ください。
答え2
あなたはawkを学びたいと確かに持っているので牛に似た一種の栄養awk -f script <logfile
をscript
含む awk(gawk)
BEGIN{ split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec",n2m);
for(i=1;i<=12;i++) m2n[n2m[i]]=i; FS="|"; }
function fixtime(str ,tmp){ split(str,tmp,"[:/]");
return mktime(tmp[3] OFS m2n[tmp[2]] OFS tmp[1] OFS tmp[4] OFS tmp[5] OFS tmp[6]) }
++count[$2]==1 { first[$2]=fixtime($1) }
count[$2]==5 && fixtime($1)-first[$2]<15 { print $2 }
最初の2行は、1月を1に、2月を2にマップする配列m2n(月を数字)を設定し、フィールド区切り記号をに設定します|
。 (などの作業を行う代わりに使用できますが、m2n["Jan"]=1; m2n["Feb"]=2;
より退屈です。
/
次の 2 行は all と区切り文字を使用して時間形式を分割し:
(最初に空白に変換せずに)月名を数値に変換し、必要に応じて並べ替えmktime()
(gawk のみ)する関数を定義します。 OFSの代わりにテキストを使用できますが(デフォルトは1つの空白で変更されていません)、" "
私はこれがもっと厄介だと思います。
5行目と6行目が見つかりました。最初発生し、そのタイムスタンプを記憶するすべてのIPaddr五同じIPaddrが発生したかどうかを検出し、そのタイムスタンプを記憶されたタイムスタンプと比較して、間隔が15秒未満であることを確認します。一部の人は、;next
5行目と6行目のスクリプト行が同じレコード(データ行など)で実行されないことを明らかにするために、5行目の操作に1つを追加しますが、私は気にしません。
良質の教育基金。
必要に応じて、'...'
スクリプトファイルを使用する代わりにスクリプト全体をコマンドラインに配置できますが、100文字を超える操作は好きではありません。
答え3
#!/bin/bash
awk -v mon=$(locale abmon) -v FS='[/:|]' '
BEGIN {for(n=split(mon, M, ";"); n; n--) Mn[M[n]]=n}
!A[$7]++ {IP[$7] = mktime($3" "Mn[$2]" "$1" "$4" "$5" "$6)}
A[$7]==5 && mktime($3" "Mn[$2]" "$1" "$4" "$5" "$6) - IP[$7] < 15 {print $7}
' file > bot_ip
-v mon=$(locale abmon)
- 変数にはmon
次の行が割り当てられます。Jan;Feb;Mar;Apr;May;Jun;Jul;Aug;Sep;Oct;Nov;Dec
for(n=split(mon, M, ";"); n; n--)
- この関数は、ループsplit
内でカウンタを開始する配列要素の数を返します。for
答え4
2番目のフィールドの最初と5番目の項目のみを表示するのではなく、5つの項目間隔のすべての項目を表示できます。スライドウィンドウ方法:
awk '
{
n = c[$7] = ++c[$7] % 4
m = index("..JanFebMarAprMayJunJulAugSepOctNovDec",$2)/3
s = mktime($3 " " m " " $1 " " $4 " " $5 " " $6)
if (s - t[$7,n] < 15 && !seen[$7]++) {
print
}
t[$7,n] = s
}
' FS='[/:|]' output.csv