クイックノート

ちょっとした発見・アイデアから知識の発掘を

【R】SEO競合サイトの可視化

検索からのアクセスはブログの主な集客経路ですが、
当然、同じキーワードで検索される別のサイトと
アクセスを取り合うことになります。

自分のブログが外から見てどういうブログなのかは、 どういう記事を書いているかだけではなく、
競合するサイトの中でどういう位置付けにあるかによって
決まってきます。

ここでは、検索でどのようなサイトと競合しているのかを、
可視化してみたいと思います。

f:id:u874072e:20190906150320p:plain
競合の可視化例

利用するライブラリ

データの取得、処理、可視化に利用するライブラリを読み込みます。

library(searchConsoleR)
library(dplyr)
library(stringr)
library(rvest)
library(urltools)
library(plotly)
library(visNetwork)

データの取得

Google Search Consoleのデータ取得

どのようなクエリでどのようなサイトと競合しているかを調べるため、
クエリのデータをGoogle Search Consoleから取得してきます。

scr_auth()
url = "https://clean-copy-of-onenote.hatenablog.com/"
SA = search_analytics(url,dimensions = c("query","page"))
X = SA %>% filter(clicks > 30)

不必要にデータが多くなるのを防ぐため、
極端にアクセスの少ないクエリは除外しています。
具体的には、clicks>30で30回以上クリックされたものだけ
取り出しています。

検索結果の取得

上では自身のサイトにアクセスされたクエリ情報を取得しましたが、
そのクエリが他のサイトと競合しているかどうかに興味があります。

そこで、実際にクエリで検索をかけて、
上位に表示されるサイトを集計します。

search_list = function(query){
  url = str_c("https://www.google.com/search?q=",gsub(" ","+",query))
  s = html_session(url)
  p = s %>% read_html
  Sys.sleep(1)
  
  a = p %>% html_nodes("div > a") 
  
  a = a[a %>% html_attr("href") %>% grep(pattern = "^/url\\?q=")]
  
  titles = a %>% html_node("div") %>% html_text
  urls = a %>% html_attr("href") %>% gsub(pattern="^/url\\?q=",replacement = "")
  
  data.frame(titles,urls,stringsAsFactors = F)
}

search_data = function(qs){
  ans = NULL
  for(q in qs){
    print(q)
    tmp = search_list(q)
    tmp$query = q
    
    ans = rbind(ans,tmp)
  }
  return(ans)
}

data = search_data(X$query)
data$domain = data$urls %>% domain()

上位の競合サイト

多くのキーワードで、自分のサイトと一緒に表示されている回数で、
そのサイトの競合度合いを計ってみましょう。

competitors = data %>% group_by(domain) %>% summarise(count=n()) %>% arrange(-count) %>% 
  filter(!domain %in% c("accounts.google.com",domain(url)))
plot_ly(competitors %>% head(10),x=~domain,y=~count) %>% 
  layout(xaxis = list(categoryorder="array",categoryarray=~count))

f:id:u874072e:20190906153855p:plain
サイトの競合度合い

上のグラフは、競合度合いの高い順にサイトのドメインを並べたものです。

このブログでは、映画チケットに福利厚生サービスを利用する記事 がよくアクセスされているので、
福利厚生サービスの公式ページが競合サイトの最上位にきています。

また、問題解決の記事もいくらかアクセスを集めているので、
Qiitaやyahoo知恵袋などのサービスとも競合が強いことがわかります。

意外だったのは、YouTubeとの競合も強いということでしょうか。
あとで更に詳細をみますが、YouTubeと競合しているキーワードも、
やはり、問題解決系のもので、ノウハウを伝える動画と、
ノウハウを伝える記事の競合が生じていることがわかります。

調べ物をする時に、文字ではなく、
動画で調べるようなユーザーも増えてきているということが感じられますね。

ブログ記事と他サイトの競合関係を可視化

上では大まかに競合度合いの強いサイトをみましたが、
どのようなキーワードで、ブログのどの記事と競合しているのか
という詳細な情報は見えてきません。

そこで、どのキーワードでの検索結果を、
どのサイトと、どの記事が取り合っているのかの関係を、
ネットワーク状に可視化してみましょう。

下のコードはやや長いですが、
外部サイト、キーワード、内部ページをノードにして、
キーワードで表示されるサイト、ページにエッジを貼っています。

maxComp = 10
maxPage = 20
maxQ = 100
comp = competitors %>% head(maxComp)
Qs = X %>% distinct(query,.keep_all=T) %>% arrange(-impressions) %>% head(maxQ)
Ps = X %>% group_by(page) %>% summarise(clicks=sum(clicks)) %>% arrange(-clicks) %>% head(maxPage)
nodes = data.frame(id=sprintf("d%d",1:dim(comp)[1]),
                   title=comp$domain,
                   label=comp$domain,
                   group="domain",
                   value=comp$count,
                   key=comp$domain) %>% 
  merge(data.frame(id=sprintf("q%d",1:dim(Qs)[1]),
                   key=Qs$query,
                   label=Qs$query,
                   title=paste(Qs$page,Qs$query,sep="<br>"),
                   group="query",
                   value=(Qs$impressions  %>% scale %>% tanh + 1)/2*max(comp$count)),
        all=T) %>%
  merge(data.frame(id=sprintf("p%d",1:dim(Ps)[1]),
                   key=Ps$page,
                   title = Ps$page,
                   label=Ps$page %>% gsub(pattern = url,replacement = ""),
                   group="page",
                   value=(Ps$clicks %>% scale %>% tanh + 1)/2*max(comp$count)),
        all=T)

edges = NULL
for(i in 1:dim(data)[1]){
  q = data$query[i]
  d = data$d[i]
  from = nodes %>% filter(key==d) 
  to = nodes %>% filter(key==q)
  
  if(dim(to)[1]*dim(from[1]) != 0){
    edges = rbind(edges,
                  data.frame(
                    from = from$id,
                    to = to$id
                  ))
  }
}
for(i in 1:dim(X)[1]){
  q = X$query[i]
  p = X$page[i]
  from = nodes %>% filter(key==p)
  to = nodes %>% filter(key==q)
  
  if(dim(to)[1]*dim(from[1]) != 0){
    edges = rbind(edges,
                  data.frame(
                    from=from$id,
                    to=to$id
                  ))
  }
}

visNetwork(nodes,edges) %>%
  visGroups(groupname="domain",color="skyblue") %>%
  visGroups(groupname="query",color="orange") %>% 
  visGroups(groupname="page",color="tomato") %>% 
  visLegend()

結果は、下の図のようになります。
青い丸が外部サイトで、
黄色い丸がキーワード、
赤い丸が内部のページを表していて、
キーワード検索で表示されるものについてリンクが貼られています。

f:id:u874072e:20190906155449p:plain
ネットワーク状の可視化

このままでは、よくわかりませんが、
注目したいところがマウス操作でズームすることができるので、
より詳しくみていきましょう。

YouTubeとの競合

例えば、YouTubeとの競合関係に注目してみます。
下の図は、上のネットワークをズームしたものです。

f:id:u874072e:20190906155905p:plain
YouTubeとの競合を可視化

YouTube自体を話題にした記事と競合しているのはもちろんですが、
auアプリが更新できない」「ミラーリングの方法を知りたい」
など問題を解決するための検索でも競合していることがわかります。

文章で調べるよりも、
動画をみて解決したいというユーザーも増えてきているため、
問題解決型の記事は、
文字だけではなく動画とも勝負することになりそうですね。

まとめ

Google Search Consoleのデータと、
Webの検索結果を元に、
SEO競合サイトを可視化してみました。

競合サイトを並べるだけでも、
自分のサイトの方向性が強く反映されていました。

また、文章同士の競合だけではなく、
文章と動画の競合が生じているなどの興味深い発見もありました。

プライバシーポリシー