前々回の記事では、requests と BeautifulSoupを使ったスクレイピングについて紹介し、前回の記事 では今後使えそうな機能をクラスしてみました。
今回は、request の代わりに selenium を使ってスクレイピングをしましたので、その方法について解説したいと思います。
selenium で抜き出したHTMLは前回紹介した共通クラスを使って情報を抽出していますので、共通クラスの使い方をもう少し知りたいも、是非ご一読ください。
今回の背景
実は、このブログは Cocoon というWordPress用のフリーのテーマを使っており、管理者画面では記事毎の統計情報(日毎、週毎、月毎、及び記事公開から今日までのアクセス件数)が見れるようになっています。
これを分析すれば、どの記事が興味をもたれているかが分かるので、スクレイピングで収集しようと試みたところ、そもそも管理者画面に入るにはIDとパスワードが必要となり、前回までの方法では出来ないことが分かりました。
requests を使って、URLの要求時にIDとパスワードを送って認証させる方法もあるのですが、それで対応できる方法はベーシック認証などごく一般的なもので、残念ながらWordPressの場合はこの方法が使えません。
それなら、selenium を使って画面からIDとパスワードを入力するのが手っ取り早そうだと判断し、今回の記事に至ります。
selenium とは
スクレイピングを行う上でよく使われるライブラリとして、Requests、BeautifulSoup、Seleniumがあることは前々回の記事でも説明いたしました。
Reauests はブラウザを表示することなく、直接WebサイトとHTTP通信が行えるライブラリです。
それに対して、Selenium は Chromeなどの既存ブラウザをPythonから操作するためのライブラリです。

旅行に例えて言うなら、自分が直接宿泊施設に電話して宿を押さえてもらうか、旅行会社(この場合はSelenium)に頼んで宿を押さえてもらうかの違いです。
もともとCeleniumはWebアプリのデバッグ用として開発されており、あたかも人が画面操作するのと同じことを自動で行わせることで、テスト作業を効率化するものです。
しかし、これを利用することで、あたかも人が操作しているようにスクレイピングが可能になります。
つまり、スクレイプされる側のWebサイトは、人がやっているのか自動でやっているのか全く区別がつきません。
selenium の使い方
Selenium は ブラウザごとに用意されたドライバープログラム(Chrome の場合は chromedriver.exe)をダウンロードし、このドライバープログラムを通じてPython 側からブラウザを制御します。
Pythonにimport したSeleniumライブラリは、あくまでも chromedriver に対して命令を出すだけで、実際のブラウザ制御は chromedriver.exe が担当します。

Seleniumを使うための準備
使うための準備は3つです。
1つ目は、ドライバープログラムをダウンロードして所定のフォルダに置くことです。
2つ目は、Pythonにライブラリをインストールすること。
3つ目は、インストールしたライブラリをインポートすること。
では、順を追って説明していきます。
ドライバをダウンロードして所定のフォルダに置く
今回は chrome を使うことを前提にしていますが、他のブラウザを seleniumuで制御したい場合も同様の手順になります。
あらかじめ、お使いのchromeのバージョンを確認しておきます。

ちなみに、2021年12月時点で、私の環境では バージョン: 96.0.4664.110(Official Build) (64 ビット)でした。
次に、こちらのサイトから お使いのchrome のバージョンに合ったchromedirver.exe をダウンロードして下さい。
画面をスクロールしていくと、バージョンと一致するドライバープログラムが見つかると思います。

クリックするとダウンロードできますので、どこか好きな場所に保存して下さい。
PythonでSeleniumuを使う段階で、このドライバプログラムのフルパスが必要になりますので、場所は控えておいてください。
インストールする
インストール方法ですが、PythonをAnaconda経由でインストールされた方は conda を使って、Python公式サイトからPythonをインストールされた方は pip を使ってインストールします。
1 2 3 4 5 |
#Anaconda経由でPythonをインストールされた方 conda install -c conda-forge selenium #Python公式サイトからPythonをインストールされた方 pip install selenium |
インポートする
次に、プログラムをインポートします。
これは次の様に記述します。
1 2 |
from selenium import webdriver from selenium.webdriver.common.keys import Keys |
1行目は必須ですが、2行目は seleniumu 経由でブラウザにキー入力したい場合に役立つものなので、単純にスクレイピングするだけなら必要ありません。
Seleniumu を使ってみる
使い方は簡単で、ドライバープログラムのフルパスを引数としてドライバーのインスタンスを生成し、そのインスタンスに対して、get メソッドを呼ぶとHTMLが取得できます。
また、find_element_by_xxxx という具合に、要素毎に検索してテキストを取り出したり、その要素に対してキーを入力することが出来ます。
下記のサンプルは、1行目が「fドライブ直下に置かれた chromedriver」 を使うコードです。
フルパスと言いましたが、exe は省略できます。
2行目は任意のWebサイトのページを読み込み、3行目で name=’log’ の要素を検索し、それに対して’aaaaa’+エンターキーを入力しています。
1 2 3 |
driver = webdriver.Chrome('f:/chromedriver') res = driver.get('https://kakaku.com/camera/digital-camera/') driver.find_element_by_name("log").send_keys("aaaaa", Keys.RETURN) |
find_element_by_xxxxx メソッドでテキストを使えば、必要な要素を取得することは可能ですが、HTMLの解析はBeautifulSoupの方が使いやすいです。
従って、Seleniumで覚えることは、上記の3行くらいで十分で、後はBeautifulSoupを使ってHTMLを解析することをお勧めします。
前回記事で紹介した共通クラスにメソッドを追加
前回記事で紹介した共通クラス(Scrape)に、1つだけメソッドを追加しました。
get_soup(thml) というメソッドで、引数にHTMLを渡すと、BeautifulSoup のインスタンスが返ってくるというものです。
今回は request メソッドは使わず、Seleniumu 経由でHTMLを取得するための処置です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
class Scrape(): def __init__(self,wait=1,max=None): self.response = None self.df = pd.DataFrame() self.columns = [] self.values = [] self.wait = wait self.max = max self.headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"} self.timeout = 5 def request(self,url,wait=None,max=None,console=True): ''' 指定したURLからページを取得する。 取得後にwaitで指定された秒数だけ待機する。 max が指定された場合、waitが最小値、maxが最大値の間でランダムに待機する。 Params --------------------- url:str URL wait:int ウェイト秒 max:int ウェイト秒の最大値 console:bool 状況をコンソール出力するか Returns --------------------- soup:BeautifulSoupの戻り値 ''' self.wait = self.wait if wait is None else wait self.max = self.max if max is None else max start = time.time() response = requests.get(url,headers=self.headers,timeout = self.timeout) time.sleep(random.randint(self.wait,self.wait if self.max is None else self.max)) if console: tm = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S') lap = time.time() - start print(f'{tm} : {url} 経過時間 : {lap:.3f} 秒') return self.get_soup(response.content) def get_soup(self,thml): ''' 指定したhtml から BeautifulSoup を取得する Params --------------------- thml:str 解析したいhtml Returns --------------------- return : BeautifulSoupの戻り値 条件を満たすurlのリスト ''' return BeautifulSoup(thml, "html.parser") def get_href(self,soup,contains = None): ''' soupの中からアンカータグを検索し、空でないurlをリストで返す containsが指定された場合、更にその文字列が含まれるurlだけを返す Params --------------------- soup:str BeautifulSoupの戻り値 contains:str 抽出条件となる文字列 Returns --------------------- return :[str] 条件を満たすurlのリスト ''' urls = list(set([url.get('href') for url in soup.find_all('a')])) if contains is not None: return [url for url in urls if self.contains(url,contains)] return [url for url in urls if urls is not None or urls.strip() != ''] def get_src(self,soup,contains = None): ''' soupの中からimgタグを検索し、空でないsrcをリストで返す containsが指定された場合、更にその文字列が含まれるurlだけを返す Params --------------------- soup:str BeautifulSoupの戻り値 contains:str 抽出条件となる文字列 Returns --------------------- return :[str] 条件を満たすurlのリスト ''' urls = list(set([url.get('src') for url in soup.find_all('img')])) if contains is not None: return [url for url in urls if contains(url,self.contains)] return [url for url in urls if urls is not None or urls.strip() != ''] def contains(self,line,kwd): ''' line に kwd が含まれているかチェックする。 line が None か '' の場合、或いは kwd が None 又は '' の場合は Trueを返す。 Params --------------------- line:str HTMLの文字列 contains:str 抽出条件となる文字列 Returns --------------------- return :[str] 条件を満たすurlのリスト ''' if line is None or line.strip() == '': return False if kwd is None or kwd == '': return True return kwd in line def omit_char(self,values,omits): ''' リストで指定した文字、又は文字列を削除する Params --------------------- values:str 対象文字列 omits:str 削除したい文字、又は文字列 Returns --------------------- return :str 不要な文字を削除した文字列 ''' for n in range(len(values)): for omit in omits: values[n] = values[n].replace(omit,'') return values def add_df(self,values,columns,omits = None): ''' 指定した値を DataFrame に行として追加する omits に削除したい文字列をリストで指定可能 Params --------------------- values:[str] 列名 omits:[str] 削除したい文字、又は文字列 ''' if omits is not None: values = self.omit_char(values,omits) columns = self.omit_char(columns,omits) df = pd.DataFrame(values,index=self.rename_column(columns)) self.df = pd.concat([self.df,df.T]) def to_csv(self,filename,dropcolumns=None): ''' DataFrame をCSVとして出力する dropcolumns に削除したい列をリストで指定可能 Params --------------------- filename:str ファイル名 dropcolumns:[str] 削除したい列名 ''' if dropcolumns is not None: self.df.drop(dropcolumns,axis=1,inplace=True) self.df.to_csv(filename,index=False,encoding="shift-jis",errors="ignore") def get_text(self,soup): return ' ' if soup == None else soup.text def rename_column(self,columns): lst = list(set(columns)) for column in columns: dupl = columns.count(column) if dupl > 1: cnt = 0 for n in range(0,len(columns)): if columns[n] == column: if cnt > 0: columns[n] = f'{column}_{cnt}' cnt += 1 return columns def write_log(self,filename,message): message += '\n' with open(filename, 'a', encoding='shift-jis') as f: f.write(message) print(message) def read_log(self,filename): with open(filename, 'r', encoding='shift-jis') as f: lines = f.read() return lines.split('\n') |
尚、Seleniumu で取得したHTMLを BeautifulSoup で解析する場合、次の様に記述する必要があります。
1 |
html = driver.page_source.encode('utf-8') |
Cocoonの統計情報をスクレイピングする
下記はこのブログの管理画面です。
赤く囲っている部分がスクレイピングで取得したい箇所になります。

一応ソースは掲載しておきますが、今回はスクレイピングの内容が特殊なので、HTMLの解析に関する解説はしません。
使い方の具体例としてご利用いただければと思います。
しかし、WordPressのテーマでCocoonを使っている方であれば、ユーザー名とパスワード、ドライバープログラムのパスと、CSVを出力するパス及びファイル名を修正して頂ければ、統計情報のCSVが入手できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
driver = webdriver.Chrome('f:/chromedriver') scr = Scrape() res = driver.get('https://resanaplaza.com/wp-admin/') driver.find_element_by_name("log").send_keys("ユーザー名", Keys.RETURN) driver.find_element_by_name("pwd").send_keys("パスワード", Keys.RETURN) urls = [f'https://resanaplaza.com/wp-admin/edit.php?paged={n}' for n in range(9)] urls.extend([f'https://resanaplaza.com/wp-admin/edit.php?post_type=page&paged={n}' for n in range(3)]) columns = [ 'タイトル','公開日','日','週','月','全'] for url in urls: page = driver.get(url) html = driver.page_source.encode('utf-8') soup = scr.get_soup(html) titles = soup.find_all('div',class_='post_title') dates = soup.find_all('td',class_='date column-date') cnts = soup.find_all('span',class_='pv-title-count') for p in range(len(titles)): pos = p * 4 values = [ titles[p].text, dates[p].text[4:].split(' ')[0], cnts[pos].text, cnts[pos+1].text, cnts[pos+2].text, cnts[pos+3].text ] scr.add_df(values,columns) scr.to_csv('p:/統計情報.csv') |
まとめ
今回はSeleniumの使い方と、それを使って WordPress のログイン認証を行い、統計情報をCSVに保存するという少々ニッチなスクレイピングについて紹介いたしました。
実は、WordPress が使っているデータベースに統計情報は保存されているので、それを見るのが手っ取り早いかもしれません。
しかし、DBを直接参照する際に、誤った操作でデータを壊してしまうリスクも少なからずあるので、これが一番安心安全です。
この記事が皆様のスクレイピングの参考になれば幸いです。