【python】unittestを使用して、関数などのテストを行う

標準モジュールのunittestを使うことで、クラスや関数が正しく機能しているかどうかをテストする事ができる。

準備

unittestのインポートをする。

import unittest

テストを行う関数の作成

URLからドメイン部分だけを抜き出す簡単な関数を作成。
引数がURLではない場合(httpで始まらない文字列の場合)は、UrlErrorにする。

class UrlError(Exception):
    """入力がURLじゃない場合のエラー"""
    
def get_domain(url):

    if not url.startswith('http'):
        raise UrlError
        
    domain = url.replace('http://','').replace('https://','')
    if '/' in domain:
        domain = domain.split('/')[0]
    
    return domain

上記の関数のユニットテスト

引数にunittest.TestCaseを指定して、テスト用のclassを作成する。

class TestGetDomain(unittest.TestCase):

    #引数がURLの場合    
    def test__get_domain(self):
        test_domain =  get_domain('http://example.com/')
        self.assertEqual(test_domain, 'example.com')
        
    #引数がURL以外の文字列の場合
    def test__not_url(self):
        with self.assertRaises(UrlError):
            test_domain =  get_domain('tp://example.com/')

テストの実行

テストはunittest.main()で実行する。

if __name__ == '__main__':
    unittest.main()

#テスト結果
..
----------------------------------------------------------------------
Ran 1 tests in 0.005s

OK

ただし、IPythonやJupyter notebookでunittestを行おうとすると下記のエラーが発生する。

AttributeError: module '__main__' has no attribute ~~~~~~~~~~~

jupyter notebookでunittestを行う場合は、下記の記述に変更が必要。

if __name__ == '__main__':
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

まとめ

今までテストをする時は適当な引数を渡して、ちまちまとテストしてたが
unittestなんていう便利なものがあるらしい・・。

本番環境で動かしてみると予想外のエラーが起こるということは頻繁にあるので、
日頃からテストを書く習慣をつけておくようにしたい。

【python】OAuth経由でSearch ConsoleのAPIから検索アナリティクスデータをCSVに保存する

Search ConsoleのAPIから、OAuth経由で検索アナリティクスのデータを抜き出してくる方法。
検索アナリティクスは90日分のデータしか保存されないので、定期的にこのプログラムを動かして、検索アナリティクスのデータを蓄積していきたい。

準備

諸々の必要なモジュールをインポートする。
今回は事前にSQlite3に保存しておいた、OAuth認証トークンを使用する。
CSVの生成用にpandasもインポート。

#OAuth認証のデータを扱う時に使う
import json
import sqlite3

#API経由でデータ取得する時に使う
import httplib2
from apiclient.discovery import build
from oauth2client.client import OAuth2WebServerFlow
from oauth2client import client

#CSVに保存する時に使う
import pandas as pd
from pandas import DataFrame, Series

検索アナリティクスのデータ取得

サイトURLや取得対象日の入力

複数サイトを管理してる場合でも扱いやすくするために、今回はインプットでサイトURLを指定する方式にする。

#取得対象のURL、開始日、終了日をインプットで入力する。
property_uri = input('取得対象のURL: ')
s_date = input('開始日の入力(YYYY-MM-DD):')
e_date = input('終了日の入力(YYYY-MM-DD):')

#CSVのファイル名用にURLの変換
domain = property_uri.replace('://','_').replace('/','_')

OAuthトークンを、SQlite3から抜き出すための関数

SQlite3で作成したテーブルから、取得対象URLに一致するサイトのOAuthトークンを取り出す。
※前もってOAuth認証を済ませておき、OAuthトークンをSQlite3に保存しておく必要がある。

def get_oauth_json():
    #OAuth認証情報のデータベースに接続
    #oauth_database.dbは同じディレクトリに保存しておく
    connecter = sqlite3.connect('oauth_database.db')
    c = connecter.cursor()

    #データベースから、取得対象URLに一致するデータを抜き出し
    c.execute("select * from oauth_list where site_name = '" + property_uri +"' ")
    for row in c:
        oauth_json = row[1]
    
 #データベースを閉じる
    connecter.commit()
    connecter.close()
    
    #取得対象URLのOAuth認証データを返す
    return oauth_json

リクエストを処理するための関数

これは参考にした記事の内容をそのまま使用させて頂いている。

def execute_request(webmasters_service, property_uri, request):
    return webmasters_service.searchanalytics().query(
        siteUrl=property_uri, body=request).execute()

取得したデータをCSVを保存するための関数

今回は取得したデータをCSVにして保存する。

def print_table(response):
    if 'rows' not in response:
        print ('Empty response')
        return
    rows = response['rows']
    df = DataFrame(rows)
    
    #取得したデータのままだと検索クエリがリストになっているので、キーワードを抜き出す。
    keys = Series(df['keys'].apply(lambda x : x[0]))
    df['keys'] = keys
    
    #各カラムの名前を日本語に変換。
    shaped_df = df.rename(columns={'keys': 'キーワード', 'clicks': 'クリック数', 'impressions':'表示回数','position':'平均掲載順位','ctr':'CTR'})
    
    #カラムの並び替え。Search Consoleの画面と同じ並びにする。
    shaped_df = shaped_df.loc[:,['キーワード','クリック数','表示回数','CTR','平均掲載順位']]
    
    output_name ='【' + domain +'】SearchAnalytics_' +s_date + '_ ' + e_date + '.csv'
    
 #CSV形式でアウトプット
    df2.to_csv(output_name)

メインの処理

sqliteからOAtuhトークンを取り出し、API経由で取り出した検索アナリティクスのデータをCSVに保存する。

def main():
    oauth_json_data = get_oauth_json()
    credentials = client.OAuth2Credentials.from_json(oauth_json_data)    
    http_auth = credentials.authorize(httplib2.Http())

    webmasters_service = build('webmasters', 'v3', http=http_auth)
        
    # Get the queries for the date range, sorted by click count, descending.
    request = {
        'startDate': s_date,
        'endDate': e_date,
        'dimensions': ['query'],
        'rowLimit': 2000
    }
    response = execute_request(webmasters_service, property_uri, request)
    print_table(response)

実行

if __name__ == '__main__':
    main()

下記の形式で、CSVで保存される。
f:id:otoha-googl:20171119023518p:plain

まとめ

OAuth認証の仕方すら知らない状態からだったので、すごく苦戦してしまった…。
でも何とか形に出来たので、あとはスケジュール通りに実行できる仕組みさえ整えてしまえば、かなり便利になりそう!


今回のプログラムの作成にあたって下記の記事を参考にさせて頂きました。
xxbxxqxx.com

【python】複数サイト分のGRC順位データを比較表に整形する

f:id:otoha-googl:20171104180245p:plain

GRCからダウンロードした順位データは上記の形式になっているが、このままではサイト毎の順位比較がしづらいので、下記の形式に整形したい。


f:id:otoha-googl:20171104180529p:plain

準備

pandasを使用して整形する。

import pandas as pd
from pandas import Series, DataFrame

GRCの順位データの整形

GRCデータの読み込み

pandasのread_csv()を使用して、GRCの順位データをDataFrameに読み込む。
GRCからダウンロードできるCSVは、1~2行目に必要ないデータが入っているので、skiprows=2を指定する。

dframe = pd.read_csv('grc_data.csv', encoding='cp932', skiprows=2)

DataFrameのデータを、ピボットに変換

下記の例では、'検索後'を行名、'サイト名'を列名、'Google順位'を値として埋める。

grc_pivot = dframe.pivot('検索語', 'サイト名' , 'Google順位')

grc_pivot

#出力結果
サイト名	サイト1	サイト2	サイト3	サイト4	サイト5	サイト6
検索語						
キーワードA	1	6	11	11	11	11
キーワードB	2	7	12	12	12	12
キーワードC	3	8	13	14	15	16
キーワードD	4	9	14	14	14	14
キーワードE	5	10	15	15	15	15

整形後のデータをCSVで保存

grc_pivot.to_csv('grc_shaped.csv')

まとめ

Excelだと関数が重たくなりがちだが、pythonならすぐに整形出来た。
20000行とかでも一瞬で処理できたので、役立ちそう。

【python】HTMLからテーブルを読み込む(pd.io.html.read_html)

準備

pandasのインポート

import pandas as pd

pd.io.html.read_html()で、HTMLのからテーブルを読み込む

pd.io.html.read_html()を使用すると、指定したURLからテーブルのデータだけを抜き出してくれる。

url = 'https://stocks.finance.yahoo.co.jp/stocks/history/?code=2282.T'
dframe = pd.io.html.read_html(url)

dframe[1][0:5]

#出力結果
        0	1	2	3	4	5	6
0	日付	始値	高値	安値	終値	出来高	調整後終値*
1	20171122944	2949	2880	2910	7045000	2910
2	20171113245	3280	3230	3270	915000	3270
3	201710313250	3260	3230	3260	1082000	3260
4	201710303315	3315	3240	3275	1956000	3275

CSVで保存

to_csv()を使う。

dframe[1].to_csv('table_data.csv')

まとめ

いちいちDOMを指定しなくても、テーブルだけを取得してくれるのですごく便利そう。
スクレイピングする時に役立つ気がする。

【python】DataFrame, Seriesの欠損値の補完,削除

pandasのDataFrameやSeriesで、欠損値が含まれていると色々と面倒な事もある。
事前に欠損値の補完や、欠損データの削除をしておく方法。

準備

欠損値のデータを作るために、numpyのnanを使う。

import pandas as pd
from pandas import Series , DataFrame
import numpy as np
from numpy import nan

Seriesの場合

#テスト用のシリーズの作成。
data = Series([1,2,nan,4])

data

#出力結果
0    1.0
1    2.0
2    NaN
3    4.0
dtype: float64

欠損値が含まれるかを調べる

シリーズに欠損値が含まれているかどうかは、isnull()を使用する。
データがNaNの場合は、Trueが返ってくる。

data.isnull()

#出力結果
0    False
1    False
2     True
3    False
dtype: bool

欠損データの削除

欠損値を削除するには、dropna()を使用する。

0    1.0
1    2.0
3    4.0
dtype: float64

欠損データの補完

欠損データを補完するには、fillna()を使用する

data.fillna(10)

#出力結果
0     1.0
1     2.0
2    10.0
3     4.0
dtype: float64

DataFrameの場合

#テスト用のDataFrameの作成
dframe = DataFrame([[1,2,3,5],[4,nan,5,6],[nan,7,nan,8],[nan,nan,nan,nan]])

dframe

#出力結果
	0	1	2	3
0	1.0	2.0	3.0	5.0
1	4.0	NaN	5.0	6.0
2	NaN	7.0	NaN	8.0
3	NaN	NaN	NaN	NaN

欠損データの削除

Series同様に、dropna()で欠損データを削除する事ができる。
行の中に1つでもNaNが含まれている場合は、行単位で削除される

dframe.dropna()

#出力結果
	0	1	2	3
0	1.0	2.0	3.0	5.0


行の全てがNaNの場合に削除するには、how='all'を指定する

dframe.dropna(how='all')

#出力結果
        0	1	2	3
0	1.0	2.0	3.0	5.0
1	4.0	NaN	5.0	6.0
2	NaN	7.0	NaN	8.0


欠損値がある列を削除するには、axis=1を指定する。

dframe.dropna(axis=1)

#出力結果
0
1
2
3


NaNじゃない数位がn個以上のある行だけを抜き出すには、thresh=nを指定する。

dframe.dropna(thresh=3)

#出力結果
    0	1	2	3
0	1.0	2.0	3.0	5.0
1	4.0	NaN	5.0	6.0

欠損データの補完

欠損データを補完するには、fillna()を使用する

dframe.fillna(100)

#出力結果
    0	1	2	3
0	1.0	2.0	3.0	5.0
1	4.0	100.0	5.0	6.0
2	100.0	7.0	100.0	8.0
3	100.0	100.0	100.0	100.0


fillna()に辞書型の値を渡すと、特定の列だけにfillna()を適用する事ができる。
◯列目は△~の様に、列毎に補完する値の指定も出来る。

dframe.fillna({3:3})

#出力結果
	0	1	2	3
0	1.0	2.0	3.0	5.0
1	4.0	NaN	5.0	6.0
2	NaN	7.0	NaN	8.0
3	NaN	NaN	NaN	3.0

まとめ

dropna(), fillna()を使う事で、欠損データを補完・削除する事が出来る。
データを処理する前に、前もって欠損データを精査する時に役立ちそう。

【python】キーワードプランナーのデータから重複を削除して、CSVで出力

AdWordsのキーワードプランナーからダウンロードしたデータは、スペース前後の入れ替えの違いだけなどが多く含まれているデータになっている。

キーワード調査をする際に1つずつ目視していくのは大変なので、ある程度のノイズデータを一括で除去したい。

準備

#pandasのインポート
import pandas as pd
from pandas import Series , DataFrame

#キーワードを整形するための関数
def shape_kw(keyword):
    split_kw = keyword.split(' ')
    
    #スペースなしのキーワード作成
    join_kw = ''.join(split_kw)

    #並び替えをして、スペースなしのキーワード作成
    sorted_kw = sorted(split_kw)
    shape_kw = ''.join(sorted_kw)
    
    #配列にして値を返す
    return [shape_kw, join_kw]

キーワードデータを整形して、重複行を削除

#CSVの読み込み。落としてきたままのCSVだとエラーになるので、一回保存し直す必要がある?
dframe = pd.read_csv('keyword.csv',encoding="cp932")

#検索ボリュームで降順に並び替え
sorted_dframe = dframe.sort_values(by='Avg. Monthly Searches (exact match only)', ascending=False)

#整形したキーワードを代入
shaped_kws = list(map(lambda keyword:shape_kw(keyword),sorted_dframe['Keyword']))

#整形したキーワードをDataFrameに変換
dframe2 = DataFrame(shaped_kws, columns=['shaped_kw', 'join_kw'])

#整形したキーワードを、元のDataFrameに追加
sorted_dframe['shaped_kw'] = dframe2['shaped_kw']
sorted_dframe['join_kw'] = dframe2['join_kw']

#重複データのある行を削除
droped = sorted_dframe.drop_duplicates(['shaped_kw'])
droped = droped.drop_duplicates(['join_kw'])

CSVで出力

#アウトプットのために列名を変換
renamed_dframe = droped.rename(columns = {'Avg. Monthly Searches (exact match only)':'検索vol.', 'Keyword':'キーワード'})

#アウトプットされる形式
    キーワード   	検索vol.
0	ハローワーク  	1220000
2	バイト     	246000
1	転職      	246000
3	ハローワーク 求人	201000
4	求人      	135000


#検索vol.が空欄のデータを補完
filled_dframe = renamed_dframe['検索vol.'].fillna(0)

#キーワードと検索volのみをCSVで出力
filled_dframe[['キーワード','検索vol.']].to_csv('results_shaping_kw.csv')

まとめ

Excelだとスペース前後違いなどの整形はするのが手間だったが、Pythonなら簡単にできた!
もうちょっと仕組みを考えれば、キーワードのジャンル分けも一度にできそう。

※追記:検索vol.が空欄の場合に、値を0にする処理を追加

【python】pandas_datareaderでYahooファイナンスの株価を取得する

pandas_datareaderを使うことで、Yahooファイナンスから過去の株価データを取得する事ができる。

pandas_datareaderを使用するには別でインストールが必要。

準備

Anaconda Promptから、pandas-datareaderをインストール。

#_ではなく、-になっている事に注意する
pip install pandas-datareader

データの取得~表示

#pandas_datareader.data のインポート
import pandas_datareader.data as pdd
import datetime

start = datetime.datetime(2013,1,1)
end = datetime.datetime(2013,1,31)

#Yahooファイナンスから、SONY, SEGASammy, Nintendoの株価の取得
#https://finance.yahoo.com/lookup のSymbolから指定する
data = pdd.get_data_yahoo(['SNE','SGAMY','NTDOY'], start, end)['Adj Close']



data [0:5]

#出力結果
     	NTDOY  	SGAMY  	SNE
Date			
2013-01-31	11.644242	4.213631	14.94
2013-01-30	11.701698	4.270572	14.99
2013-01-29	12.640132	4.223121	15.14
2013-01-28	12.822073	4.232611	15.12
2013-01-25	12.333705	4.014338	14.41

グラフ化

data.plot()

f:id:otoha-googl:20171103012139p:plain
任天堂セガサミーソニーの株価の推移




変化量の計算

変化量の計算にはpct_change()を使用する

pct = data.pct_change()

pct[0:5]

#出力結果
    	NTDOY  	SGAMY  	SNE
Date			
2013-01-31	NaN	NaN	NaN
2013-01-30	0.004934	0.013514	0.003347
2013-01-29	0.080196	-0.011111	0.010007
2013-01-28	0.014394	0.002247	-0.001321
2013-01-25	-0.038088	-0.051569	-0.046958

変化量の相関関係を調べる

pct.corr()

#出力結果
        NTDOY   	SGAMY   	SNE
NTDOY	1.000000	0.275519	0.345960
SGAMY	0.275519	1.000000	0.144161
SNE	0.345960	0.144161	1.000000

各社の株価の相関関係はあまりなさそう…。


相関関係の可視化

#seaborn , matplotlibのインポート
import seaborn as sb
import matplotlib.pyplot as plt

#ヒートマップの表示
%matplotlib inline
sb.heatmap(pct.corr())

#出力結果

f:id:otoha-googl:20171103011650p:plain
任天堂セガサミーソニーの株価の相関関係

まとめ

データ量が増えてきた時に、相関関係を調べたり、変化量を出せるのはすごく役立ちそう。
自然検索の順位変動の計算にも使えるかも。