背景
サイトを運営する上でそれぞれのページの検索順位を把握する必要がある. だが, 無料で検索するツールは有料版に比べて必要な機能が足りてない. 有料の検索順位ツールはいくつかあるけれども値段が高くなりがちだし, 自分に本当に必要か分からない.
そこで, pythonを用いて簡単なサイトを運営している人が用を足せる程度の検索順位チェッカーを作っておく.
幸いにも先駆者たちがいるので参考にさせてもらう.
無料検索順位チェックツールがないから作った【KW無制限】
【Python】Seleniumを使ってコマンドラインでGoogleの検索順位をチェックする方法
ここで紹介するプログラムを用いれば以下のような画像を作成することが出来る.


下準備
使うプログラムは以下の3つ.
(参考文献の後にcodeの全文を乗っけています. コピペして使用してください. )
getTitles.py : urlからサイトのタイトルを入手する用
search_ranking_checker.py : 定期的に回しサイトのキーワードごとの順位を取得する
make_pictures.py : 回収したデータから, 画像を作成する
さて, これらのプログラムを一つのディレクトリーに用意する.
同じディレクトリーに, url_keywords.xlsx という名前で以下の画像のようにデータを入力する.

1行目には, url , title, keyword と入れ, titleの行には何も入力しない.
urlとkeywordには調べたいurlとそれに対する単語を入力する.
ここに入力したurlとkeywordの検索順位を調べてデータを蓄積, そのデータをグラフ化していく.
また, さらに検索順位のデータを保存しておく用に data_file というフォルダーを作成しておく.
現段階でのフォルダ構成は以下のようになる.
- |- getTitles.py |- search_ranking_checker.py |- make_pictures.py |- url_keywords.xlsx |-data_file |- (空)
これで準備完了.
使い方
STEP 1.
getTitles.pyを用いて, urlからタイトルを引っ張ってくる.
このcodeを回すと url_keywords1.xlsx が出来てtitleのcolumnが全て埋まっている状態となる.
STEP 2.
search_ranking_checker.py を用いてその日の検索順位を入手する.
google さんにprogramで検索していると判断されないために1単語の検索順位を調べ終わったら, 70~100秒待機するようになっている.
このSTEP 2.は時間が掛かるし, 連続して使うとエラーが出るので注意.
STEP 3.
数日分のデータが集まった段階で make_pictures.py を回す. (一日分だけだと, matplotlib.plotのplotする点が一点だけだとグラフに表示されない都合上, グラフができない可能性があるので最低二日分は集めること. )
すると, サイトごとに検索ワードの順位の推移が写った画像が作成される. 各日にちに書かれている数字は順位で100は100位以上を表す.
STEP 3. まで上手く回ると以下のようなフォルダ構成になっている.
data_fileの日付のファイル(ex. 20190821)の中に作成された画像が入っている.
- |- getTitles.py |- search_ranking_checker.py |- make_pictures.py |- url_keywords.xlsx |- data_file │ ├── 190802.csv │ ├── 190803.csv │ ├── 190804.csv │ ├── 190805.csv │ ├── 190806.csv │ ├── 190807.csv │ ├── 190817.csv │ ├── 190819.csv │ ├── 190820.csv │ ├── 190821.csv │ └── 20190821 | |- ガウスの名言|I.png | |- pythonで検索順位.png | |- etc...
———–雑感(`・ω・´)———–
コードが汚くて申し訳ないと思ってるよ!
注意事項がいくつかあるよ!
・search_ranking_checker.py では検索ワードによっては上手く検索されない場合があるよ. その場合は自動的に作成されていく ~.xlsxのファイルの中にはデータとして入力されていないから気を付けて.
・make_pictures.pyの中では, matplotlibの画像の中に日本語を対応させる部分があって書体は’IPAPGothic’ を使っている. だからmatplotlibで日本語を対応させる準備をしていない人はその作業をしなければいけない. “matplotlib 日本語” と検索して準備してみてね.
・観察する検索ワードの選定に関しては, search consoleかubbersuggestを参考にするのが良かったよ.
・本格的に例えばブロガーとして食べていきたいとか考えているならば, 有料版の何かに手を出すべきだと思ったよ. でも, ブログ初心者が検索順位の推移見てみたいなぁという欲求を満たすにはこの程度で十分だったよ.
参考文献
(1) 【無料】検索順位チェックツール9選まとめ:2018年10月版, https://www.webtanguide.jp/column/free_seo_check_tool/
(2) 検索順位チェックツール11選!機能と料金をわかりやすく比較, https://seolaboratory.jp/74775/
・PythonとBeautiful Soupでスクレイピング, https://qiita.com/itkr/items/513318a9b5b92bd56185
・python3 文字列を辞書に変換, https://qiita.com/lamplus/items/b5d8872c76757b2c0dd9
・バックグラウンド実行で時間短縮しよう!! , https://qiita.com/alancodvo/items/15dc36d243e842448d33
・matplotlib.axes , https://matplotlib.org/3.1.1/api/axes_api.html#axis-limits-and-direction
・Pythonで例外を発生させる:raise , https://uxmilk.jp/39845
・matplotlib.datesで時系列データのグラフの軸目盛の設定をする , https://bunseki-train.com/setting_ticks_by_matplotlib_dates/
コード
getTitles.py
#!usr/bin/python
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup
import requests
import re
import random
import pandas as pd
import ast
def main():
df = pd.read_excel("urls_keywords.xlsx",sheet_name="Sheet1")
urls = df["url"].unique()
for url in urls:
# get title of the page
resBaseUrl = requests.get(url)
bs4BaseUrl = BeautifulSoup(resBaseUrl.content,"html.parser")
title = bs4BaseUrl.title.text
df.loc[df["url"] == url,"title"] = title
df.to_excel("urls_keywords1.xlsx",index=False)
if __name__=="__main__":
main()
search_ranking_checker.py
#!usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
import time
import datetime
def main():
today = datetime.date.today().strftime("%y%m%d")
savePath = "data_file/%s.csv" % today
if os.path.exists(savePath):
print("file already exists!")
return(0)
from bs4 import BeautifulSoup
import requests
import re
import random
import pandas as pd
import ast
def search_index(my_url,keyword):
search_url_keyword = keyword
search_url = 'https://www.google.co.jp/search?hl=ja&num=100&q=' + search_url_keyword
res_google = requests.get(search_url)
bs4_google = BeautifulSoup(res_google.content, 'html.parser')
preUrls = [ i.parent.get("href") for i in bs4_google.findAll('div', class_='BNeawe vvjwJb AP7Wnd')]
try:
preUrls = [ pre for pre in preUrls if "url" in pre]
urls = [re.sub(r'/url?q=|&sa.*', '', x) for x in preUrls]
except:
print(preUrls)
return("Error! urls can not extract properly.")
# check not blocked by google for illegal access
if len(urls) == 0:
print(bs4_google)
print("Error! finish running")
sys.exit()
today = datetime.date.today().strftime("%Y/%m/%d")
url_index = 0
flag = False
for url in urls:
if my_url not in url:
url_index += 1
else:
url_index += 1
flag = True
break
if not flag:
url_index = ">100"
return(url_index)
df = pd.read_excel("urls_keywords1.xlsx",sheet_name="Sheet1")
df["date"] = today
df["rank"] = ""
for url,key in zip(df["url"].values,df["keyword"].values):
rank = search_index(url,key)
cond = ( df["url"] == url ) & ( df["keyword"] == key )
df.loc[cond,"rank"] = rank
print(df.loc[cond])
randomsleep = random.randint(70,100)
time.sleep(randomsleep)
df.to_csv(savePath,header=True,index=False)
if __name__=="__main__":
main()
print("finish!")
make_pictures.py
#!usr/bin/python
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(context="paper" , style ="whitegrid",rc={"figure.facecolor":"white"})
import glob
import copy
from datetime import datetime
import matplotlib.dates as mdates
import os
import subprocess
def main():
paths = glob.glob("data_file/*.csv")
dataSet = {}
for path in paths:
df = pd.read_csv(path,header=0)
urlsMacro = df.url.unique()
# get a title for each url
for url in urlsMacro:
title = np.unique(df[df.url == url].title)
if len(title) == 1:
# check key exists or not
if url in dataSet:
# check title is same or not
if dataSet[url]["title"] != title[0]:
print("Error! There is two different titles for one url")
print(dataSet[url]["title"],title)
print("n")
#raise Exception("Error! There is two different titles for one url")
else:
dataSet[url] = {"title":title[0],"keywords":[]}
keywords = np.unique(df[df.url==url].keyword)
for keyword in keywords:
if not keyword in dataSet[url]["keywords"]:
dataSet[url]["keywords"].append(keyword)
# add index to url
for i,k in enumerate(dataSet.keys()):
dataSet[k]["index"] = i
NPrintTitle = 30
NPrintUrl = 50
rankMax = 100
today = datetime.now().strftime("%Y%m%d")
if not os.path.exists("data_file/%s" %today):
subprocess.check_call(["mkdir","data_file/%s" %today])
# list title, url and keywords that are searched.
for k, v in dataSet.items():
s = "%s -- index : %d -- %sn" % (v["title"][:NPrintTitle],v["index"],k[:NPrintUrl])
for i in range(len(v["keywords"])):
s += "|--- %sn"% v["keywords"][i]
print(s)
def getRank(url,keyword):
rank = {"date":[],"rank":[]}
for path in paths:
df = pd.read_csv(path,header=0)
cond = (df.url == url) & (df.keyword == keyword)
if len(df[cond]) >1:
raise Exception("There are more than 2 values for one url and keyword")
elif len(df[cond]) == 1:
date = datetime.strptime(str(df.loc[cond,"date"].values[0]),"%y%m%d")
r = df.loc[cond,"rank"].values[0]
if r == ">100":
r = rankMax
elif r == 'Error! urls can not extract properly.':
continue
else:
r = float(r)
rank["date"].append(date)
rank["rank"].append(r)
arg = np.argsort(rank["date"])
rank["date"] = np.array(rank["date"])[arg]
rank["rank"] = np.array(rank["rank"])[arg]
return(rank)
def pltRank(data_):
url = data_["url"]
title = data_["title"]
keywords = data_["keywords"]
fig = plt.figure(figsize=(5,3),dpi=150)
plt.rcParams['font.family'] = 'IPAPGothic' #全体のフォントを設定
ax = fig.add_subplot(111)
for keyword in keywords:
rank= getRank(url,keyword)
rankstr = [str(i) for i in rank["rank"]]
ax.plot(rank["date"],rank["rank"],label=keyword)
for i,r in enumerate(rank["rank"]):
ax.annotate(str(r),(rank["date"][i],rank["rank"][i]),fontsize=6)
ax.set_ylim(0,rankMax + 1)
ax.invert_yaxis()
# adjust x axis label
ax.xaxis.set_major_locator(mdates.DayLocator(bymonthday=None, interval=1, tz=None))
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
labels = ax.get_xticklabels()
plt.setp(labels, rotation=45, fontsize=6)
plt.rcParams["ytick.labelsize"] = 6.0
titlePrint = title[:NPrintTitle]+"n"+url[:NPrintUrl]
ax.set_title(titlePrint,fontsize = 6)
ax.legend(fontsize=6)
#ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0, fontsize=6)
plt.tight_layout()
plt.savefig("data_file/%s/%s.png" % (today,title[:10]) )
plt.clf()
def extractByIndex(ind = 0):
dataInd = {}
for k,v in dataSet.items():
if v["index"] == ind:
dataInd = copy.deepcopy(v)
dataInd["url"] = k
return(dataInd)
for index in range(len(dataSet.keys())):
dataInd = extractByIndex(index)
pltRank(dataInd)
if __name__=="__main__":
main()
print("finish!")

コメント
[…] 新しく投稿した記事の内容の方が使えます. こちらをどうぞ.pythonでサイト検索順位を調べるプログラム|ブログ初心者用 […]
[…] 検索順位の自動取得は、「pythonでサイト検索順位を調べるプログラム|ブログ初心者用」を参考にさせていただきました。 […]