検索文字列・リファラ解析編 - さくらの「Webalizer」をPythonで解析したらデータ分析とWebマーケティングの基礎が詰まっていた話(4)
「宇宙打」のアクセスログ(Webalizer)を解析してみる企画の4回目です。
前回ホスト名を解析したところ、どうやらGoogleやYahoo!などの検索エンジンがクローリングしていることが分かりました。ということは、検索エンジンで検索すれば宇宙打のページに辿り着きそうです。
では、どのような文字列で検索されているのでしょうか?
ファイルを読み込んで検索文字列を表示しよう
まずはいつものようにusageファイルを確認します。今回はこのSearch Stringsを使いましょう。
以下のようにDataFrameに読み込みます。
import pandas as pd
filename='usage_200504.html'
# HTMLファイルの表を読み込んでDataFrame型に変換
dfs = pd.read_html(filename, encoding='EUC-JP')
df_org = dfs[10].copy()
df_org
結果は以下の通りとなりました。
これをいつも通り整形して全ファイルループを回して集計すれば良さそうですが、ここでいくつかのことに気付きます。
【気づき1】表記ゆれがある
1位は「宇宙打」、4位は「宇宙打ち」となっていますね。
これはそのまま別々に扱うべきでしょうか?
それとも合わせた方が良いでしょうか?
【気づき2】スペースが全角と半角の時がある
9位と10位、同じ文字列に見えますが、よく見ると「宇宙」と「タイピング」の間のスペースが全角と半角で違います。
これは区別した方が良いでしょうか?
【気づき3】順番が逆の時がある
「宇宙 タイピング」「タイピング 宇宙」のように、同じ用語なのに異なる順番で並んでいるパターンがあります。
これも区別した方が良いでしょうか?
【問題4】文字化けしている箇所がある
5位と6位、文字化けしてますね。
ここはどうしたらよいでしょうか?
このように、まずは一覧や集計を見て「気づき」を得る、ということは大変重要です。もしこれをやらずに集計してしまうと、誤った解釈をしてしまうこともあるかもしれません。
具体的にはDataFrameの一覧表を表示させて眺めてみるだけでよいです。
誤った解釈をしないようにデータを補正します。この処理はデータクレンジング*のひとつとなるのですが、ここで1点ポイントがあります。それは、処理をする必要があるかどうかを考えることです。何が何でも補正処理をする必要はなく、データ集計や分析の観点から考えて補正する必要があるかどうかを見極めてから作業を行う、ということです。次の章から具体的に考えていきましょう。
---
*データクレンジングに関しては以下の記事が大変分かりやすいです。以下の記事でいうところの「(3) データを名寄せ、統合する」が今回の処理にあたります。
【気づき1】表記ゆれに対応しよう?
まずは、「補正処理をする必要があるか?」について考えてみましょう。
そもそも「表記ゆれ」とは、同じ言葉を意味するものであっても違う書き方をすることを言います。
今回の場合、「宇宙打」を「宇宙打ち」という書き方をしていたことを例に挙げましたが、ほかにも
・ソラウチ
・そらうち
・宇宙打ち(ソラウチ)
なども表記ゆれですし、
・パソコン
・PC
・タイピングゲーム
・taipinguge-mu
・typinggame
なども表記ゆれの一種です。
企業のマーケティング施策におけるWebページの記事やブログ記事などは表記ゆれを極力なくして同一の表現をした方が良いと言われています。
ですが、今回検索文字列を入れてくるのは宇宙打のユーザであり、こちらで制御できるものではありません。
また、表記ゆれの種類は多種多様であり、それをすべて統一することには多大な労力がかかります。
さらに、実はその表記ゆれを見つけることこそがアクセス数を伸ばす上で大変重要となるケースがあります。
今回の場合ですと、通常「そらうち」と入力して変換した場合に「宇宙打」と変換されるPCはまれです。
というか、普通はそんな変換されません。初期状態で「そら」を「宇宙」と変換してくれるPCがあったとしたら、それは中二病です。
おそらく普通は「空うち」とか「そら打ち」とか変換されます。
とすれば、「空うち」とか「そら打ち」とか検索しても、「宇宙打」のページがヒットするようになっていれば、アクセスが増やせそうですね。そのようなキーワードを含めたSEO対策が有効となるかもしれません*。
そこで今回は、表記ゆれについてはひとまず気にしない(特に補正処理をせずに分析する)、という方策を取ることにしました。
---
*余談ですが、Googleで検索すると、「もしかして:○○」という表示がされることがあります。
一般的に、「もしかして機能」と言われているようですが、この機能が具体的にどのようなアルゴリズムで組まれているのかは謎で、昔はマーケティング界隈でこの機能について紹介する記事も多く出ていたのですが、最近では見かけなくなりました。
【気づき2】全角と半角のスペースに対応しよう
次はスペースの全角・半角問題です。
ここでも「補正処理をする必要があるか?」を考えてみましょう。
【気づき1】とは異なり、スペースの全角・半角の違いは特に意図されたものではないと考えられます*。つまり、「宇宙 タイピング」と「宇宙 タイピング」の違いはただの入力上の違いであり、意図されたものではないし、分けて考える必要もない、と考えられます。
とすれば、両者は同じものとして扱って良さそうです。
では実際に補正の方法です。実は簡単で、「全角スペース」を「半角スペース」にすべて置換すれば良いだけです。
その前にまずは整形しておきましょう。
# 不要な行と列を削除
df_tmp = df_org.drop([len(df_org)-1, len(df_org)-2])
df_tmp = df_tmp.drop([0,1,2,3,4])
df_tmp = df_tmp.drop(df_tmp.columns[[0,2]], axis=1)
# 列名を設定
features = ['Hits', 'SearchStrings']
df_tmp.columns = features
df_tmp
うまくいきました。
ここで合わせてやっておきたいことがあります。
それは、一番最初と一番最後のスペースを削除することです。
検索の際に最初と最後にスペースを入れることは実質的に意味は無いのですが、例えばWebサイトからコピペする際などに不意に入ってしまう場合があります。
以下の通りstrのstrip()メソッドで不要なスペースを削除します。
# 左右両方の文字列の空白を削除
df_tmp['SearchStrings'] = df_tmp['SearchStrings'].str.strip()
見た目上は変わりません。
では、全角スペースを半角スペースに置換します。
strのreplace()メソッドを使用します。
# 全角空白を半角空白に変換
df_tmp['SearchStrings'] = df_tmp['SearchStrings'].str.replace(' ',' ')
うまくいきました。
例えば13位と14位を見ると、同じ「宇宙 タイピング」という文字列になっていることが分かります。
では、同一文字列を合算して集計しましょう。
DataFrameのgroupby()メソッドでSearchStrings列について集計し、その値をsum()でとります。
# 同一キーワードでまとめて集計
df_tmp = df_tmp.groupby('SearchStrings').sum()
うまくいきました。
---
*これはおそらく癖のようなものですが、プログラマは全角スペースを避ける傾向にあるようです。プログラムを記載する際にスペースを全角で記載するとエラーになるためです。
【気づき3】順番が逆の時は対応しよう?
では次の気づきについてですが、これも実は「補正処理をする必要があるか?」を考える必要があります。
というのも、既にお気づきの方もいらっしゃるかと思いますが、ここまで、「宇宙 タイピング」と「宇宙」は別々のものとして扱ってきました。
両方とも「宇宙」という用語が含まれているため、「宇宙」という用語が含まれていたかどうかを算出するという観点で集計することは可能です。
ですが、「宇宙 タイピング」で検索した場合と、「宇宙」で検索した場合とでは、おそらく「宇宙」に関する意味合いが違うと考えられます。
・「宇宙 タイピング」→宇宙に関するタイピングゲーム的なものを探している
・「宇宙」で検索→宇宙的な何かを探している
そこで、「宇宙 タイピング」と「宇宙」は別ものとして扱いました。
順番が逆のケースについても実は似たような形で考えられます。
・「宇宙 タイピング」→宇宙に関するタイピングゲーム的なものを探している
・「タイピング 宇宙」→タイピングゲームの中で宇宙的なものを探している
これは、検索したユーザの思考のプロセスが異なるとみるべきです。
実際にGoogle検索では用語の順番が異なる場合に別の検索結果が表示されることが一般的に知られており、実際に「宇宙 タイピング」と「タイピング 宇宙」をそれぞれ検索すると、そもそもヒットする件数が異なることが分かります。
以上から、今回は順番が異なっている場合は同一のものと見なさず、別々のものとして集計することにしました。
【気づき4】文字化けに対応しよう?
最後に文字化けです。
結論を先に言うと、これは対応できませんでした。
文字コード表と照らし合わせながら変換すれば良かろうと考えていたのですが、そもそも化けた状態で文字列が記録されているため、容易に変換できませんでした。
ですので、ここではこのまま集計することにしました。
集計結果を表示しよう
ここまでをまとめて関数化しておきます。
import pandas as pd
import numpy as np
def readHtml(filename):
# HTMLファイルの表を読み込んでDataFrame型に変換
dfs = pd.read_html(filename, encoding='EUC-JP')
# 「Search Strings」のDataFrameのみ抜き出して、不要な行・列を削除
df_org = dfs[10].copy()
# もし「Search Strings」の表ではない場合は空のDataFrame返却
if not 'Search Strings' in df_org.iloc[1,0] :
return pd.DataFrame()
# もし最終行が文字列型だったら、2行削除
if isinstance(df_org.iloc[len(df_org)-1,0],str) :
df_tmp = df_org.drop([len(df_org)-1, len(df_org)-2])
# もし最終行が文字列型でなかったら、1行削除
else :
df_tmp = df_org.drop([len(df_org)-1])
df_tmp = df_tmp.drop([0,1,2,3,4])
df_tmp = df_tmp.drop(df_tmp.columns[[0,2]], axis=1)
# 列名を付与
features = ['Hits', 'SearchStrings']
df_tmp.columns = features
# 左右両方の文字列の空白を削除
df_tmp['SearchStrings'] = df_tmp['SearchStrings'].str.strip()
# 全角空白を半角空白に変換
df_tmp['SearchStrings'] = df_tmp['SearchStrings'].str.replace(' ',' ')
# 同一キーワードでまとめて集計
df_tmp = df_tmp.groupby('SearchStrings').sum()
# Referrerをset_indexする
# df_tmp.set_index('SearchStrings', inplace=True)
# int型にする
df_tmp = df_tmp.astype(int)
# デバッグ
print('--', filename, 'の読み込みを実施--')
print(df_tmp.head())
return df_tmp
これをループで回しましょう。
# 直下のhtmlファイル名をすべて取ってきてループを回す。
import glob
global df_final
df_final = pd.DataFrame()
for x in glob.glob('*.html'):
df_tmp = readHtml(x)
df_final = df_final.add(df_tmp, fill_value=0)
さらに、作業用にコピーした後Hitsの降順でソートしましょう。
# 念のため作業用のdfにコピー
df = df_final.copy()
# Hitsでソートする
sr_tmp = df['Hits'].sort_values(ascending=False)
df_new = pd.DataFrame(sr_tmp)
df_new.head(10)
うまくいきました。
これを見ると、3位~5位には「タイピングゲーム」関連の用語で検索してくれていることが分かります。
これは実は、とても良いことです。なぜなら、普段天文や宇宙にあまり関係していない人が、「タイピングゲーム」を媒介として「偶然」天文や宇宙の用語に触れる、という証左になるからです。
詳細は以下のnoteにも記載しましたので、併せてご参照ください。
また、「タイピング フリー」「タイピングゲーム フリー」がランクインしていることも見逃せません。フリーゲームというのは需要があるのですね。
なお、今回は行いませんでしたが、時系列で検索文字列がどのように変遷していっているか、を見ることも大変面白そうです。
リファラを見てみよう
ではここからは、リファラを見てみましょう。
リファラとは、宇宙打のページに訪問した人がどのページから参照してやってきたのかを表すものです。
いつものようにusageファイルの以下の情報を使います。
DataFrameの取得方法や整形方法は同様ですので、ソースだけ掲載しておきます。
import pandas as pd
def readHtml(filename):
# HTMLファイルの表を読み込んでDataFrame型に変換
dfs = pd.read_html(filename, encoding='EUC-JP')
# 「リファラー」のDataFrameのみ抜き出して、不要な行・列を削除
df_org = dfs[9].copy()
df_tmp = df_org.drop([len(df_org)-1, len(df_org)-2])
df_tmp = df_tmp.drop([0,1,2,3,4])
df_tmp = df_tmp.drop(df_tmp.columns[[0,2]], axis=1)
# 列名を付与
features = ['Hits', 'Referrer']
df_tmp.columns = features
# Referrerをset_indexする
df_tmp.set_index('Referrer', inplace=True)
# int型にする
df_tmp = df_tmp.astype(int)
# デバッグ
print('--', filename, 'の読み込みを実施--')
print(df_tmp.head())
return df_tmp
これをループで回します。
# 直下のhtmlファイル名をすべて取ってきてループを回す。
import glob
global df_final
df_final = pd.DataFrame()
for x in glob.glob('*.html'):
df_tmp = readHtml(x)
df_final = df_final.add(df_tmp, fill_value=0)
結果がこちらです。
うまくいったようです。
Hitsでソートして上位を見てみましょう。
# Hitsでソートする
df['Hits'].sort_values(ascending=False)
さてここで、あることに気付きます。
上位がほとんど「http://planetarium.halfmoon.jp/」から始まるページです。つまりこれは、自分のページ内で参照しあっている状態です。例えば、1位は「~/menu.html」というページですが、これは宇宙打トップページに組み込まれているメニューバーを指します。メニューバーなので様々なページにリンクが張られており、そこを参照してページを遷移していることは当たり前といえば当たり前です。
このような時はどうしたらよいのでしょうか?
いくつか方法はありそうですが、私の場合は「自分のドメインからのリファラのデータはカウントしない(一覧から削除する)」というようにしました。
具体的には「http://planetarium.halfmoon.jp/」という文字列から始まっているデータを取り除いた新たなDataFrameを作成しました。
df_new = df[~df.index.str.startswith('http://planetarium.halfmoon.jp/')]
df_new['Hits'].sort_values(ascending=False)
うまくいきました。
では、具体的に見ていきましょう。
1位の「Direct Request」は、文字通りダイレクトにページを表示した場合ですので、例えばブラウザの「お気に入り」や「ブックマーク」に登録してクリックした場合などを指します。
2位~4位はどうやらYahoo!やGoogleの検索エンジンのようです。
5位~9位はゲーム紹介系のWebページでした。すでに現存しないページが多数のようです。
そして10位です。
このURLはもしや・・・。
ろじっくぱらだいす!!!
そう、実は宇宙打はろじぱらのワタナベさんに紹介して頂いたことがあったのです(今でいうところのインフルエンサーです)。それがきっかけで爆発的にユーザーが増えたのでした。
15年経った今でもこの名前をお見掛け出来るとは、涙がちょちょ切れそうです。
Naverまとめでも紹介いただいていました。
他にも11位以降を見ると、「なるほど、このページから来ていたのか」とか、「こんなページでも紹介されていたのか」ということがよくわかります。ぜひ参考にしてみてください。
まとめと今後に向けて
以上、本日は検索文字列とリファラについて取り上げてみました。
この方法を知っているだけで、「自分のWebサイトをどのように見つけてくれているのか」を知ることが出来、今後のサイト運営に役立つと思います。
高価で複雑なアクセス解析ツールを使わなくても、WebalizerのデータとPythonプログラムの知識とちょっとした意気込みがあれば、アクセス解析は十分可能です。
みなさんもぜひTryしてみてください。
この記事が気に入ったらサポートをしてみませんか?