関東・東北にあるAOKIと青山の店舗位置関係を分析してみる

前回は全国のAOKIと青山の店舗位置関係をざっくりと調べてみました。
今回は分析範囲をUTM54N内に絞り、前回より詳細に両店舗の位置関係について調べていきたいと思います。

 

具体的には、

  • AOKIの各店舗に最も近い青山の店舗との距離を取得し、ヒストグラムを作成
  • AOKIと青山の各店舗に最も近い道路との距離を取得し、ヒストグラムを作成

ということをしていきます。

 

【目次】

 

分析範囲を絞る

今まではUTM54N範囲外に位置する地物もUTM54Nとして変換していました。
今回はそのような無茶なことはせず、UTM54N内にある関東・東北地方の店舗を対象に分析します。

 

UTM54内にあるAOKIと青山の店舗を抽出するために、下図のようなUTM54Nの東西縁に沿った灰色のポリゴンを作成し、空間検索を用いてこのポリゴン内の店舗を抽出し、今回の分析対象店舗レイヤを作成しました。

 

f:id:hhgingisland:20180526191541p:plain

なお、ポリゴンを作らなくても店舗レイヤに緯度経度情報がのっているので、属性テーブル上で「式を使った地物選択」により緯度経度の範囲指定をすれば分析対象店舗を抽出することができます。むしろこちらの方が楽です(この記事を書きながら思いついた。。。)

 

プラグイン「NNJoin」のインストール

NNJoinプラグインhttps://plugins.qgis.org/plugins/NNJoin/)は、入力レイヤ内の地物に最も近いターゲットレイヤ内の地物との距離を導出してくれる素晴らしいプラグインです。

 

このプラグインを用いることでAOKIの各店舗とそれに対応する最も近くある青山との店舗間距離データを取得できるため、前回の記事より詳細な分析が可能になります。

コントロールパネル内の「プラグイン」から「NNJoin」と入力しインストールします。

 

NNJoinによる店舗間距離データの取得

 
f:id:hhgingisland:20180526192041p:plain

入力レイヤにAOKIの店舗レイヤ、検索対象のレイヤに青山の店舗レイヤを設定します。


「OK」を押すと、新たなレイヤが出力されます。レイヤの属性テーブルは下図のようになり、入力レイヤの属性値の後に、各店舗に最も近い青山の店舗情報(属性値)と距離データが書き込まれます。

f:id:hhgingisland:20180526192225p:plain
 

ヒストグラムの作成

店舗間距離データを取得できたのでヒストグラムを作ってみます。

こちらのサイト(https://bellcurve.jp/statistics/blog/15352.html)を参考にエクセルで作成しました。

f:id:hhgingisland:20180526192345p:plain

 

取得データとヒストグラムから以下のことが分かります。

  • 500m間隔で区切ると500m以下の店舗間距離である店舗数が一番多い。
  • 3.5km以下の店舗間距離である店舗は全体の9割を占める。
  • 10km以上比較的離れた距離にある店舗は2軒ある。

 

前回の分析でも述べましたが、AOKIを見つけたら車で10分以内に行ける距離に青山があることが言えます。

中でも徒歩10分以内で行ける近い距離にある青山が最も多いことが今回の分析で分かりました。

 

さらに10km以上離れた距離にある店舗2軒は、AOKIが青山のいない商圏で商売していると考えてもよいでしょう。

 

元々は青山も近くにいたが商売に負けて撤退したとか、既に他のスーツ販売店が出店しており、参入の余地がなかったのか、など色々考えられます。

 

数少ない青山という競合が近くにいない特徴的で面白い店舗です。
ちなみに下図の黄色点が該当するAOKIの店舗です。

f:id:hhgingisland:20180526203151p:plain

各店舗と近傍にある道路との距離データの取得

同様にNNJoinを使って、近傍の道路との距離データを取得します。
国道(高速道路含む)だけの場合と、国道と主要地方道を両方含めた場合の2パターンで距離を導出しました。

 

ヒストグラムの作成

国道(高速道路含む)との距離のヒストグラム

f:id:hhgingisland:20180526194406p:plain
f:id:hhgingisland:20180526194417p:plain

先ほどの結果からもAOKIと青山は近い位置にあるので、上2つのグラフは似たような形になっています。

 

分布数が多いのは、距離が100m以内および1km以上の範囲のもので、それぞれ全体の30%程度を占めています。1km以上離れている店舗は、確実に国道沿いにはなく、地方道などの異なる道路に分布していると考えられます。

 

主要地方道も含めた全ての道路との距離のヒストグラム

f:id:hhgingisland:20180526194912p:plain
f:id:hhgingisland:20180526194928p:plain

 

主要地方道を含めると、道路との距離が100m以内の店舗が大幅に増加し、1km以上離れた店舗数が大幅に減少しているのが見てとれます。

 

100m以内の店舗が両社ともに概ね全体の5割程度、200m以内が6~7割程度となっています。

 

前回の記事で200m以内を道路沿いにある店舗と定義しましたが、今回作成したグラフから200mという設定で悪くはなさそうだなという感じです(100~200mに含まれる店舗数も15%程度と2番目に大きなウェイトを占めているので)。

 

以上より、主要な道路から200m以内に6~7割程度の店舗が分布していることから、AOKIと青山は交通量の多い道路沿いに多く出店していると考えられます。

 

まとめ

今回はヒストグラムを作成し店舗間距離データ、店舗と道路間距離データの分布状況を明らかにしました。

 

基本的に前回の記事で考えていたことを変わりませんが、ヒストグラムを作成することで、

  • AOKIと青山の店舗間距離が500m以内である店舗が全体で最も割合が多い
  • ほぼ全てのAOKIで、近く(5km、車で10分以内)には青山の店舗があるが、東北・関東エリアでは2店舗が周りに青山がいない(15km以上離れている)エリアに出店している
  • 店舗と道路間距離は100m以内の店舗が全体で最も割合が多い

というようなことが分かりました。

 

ひとまず今回の分析で終了としますが、記事を書いている時に、QGIS上で人口分布を入れたりしたら面白い発見がありそうだな等のアイデアが浮かんできたので、また色々いじっていけたらと思います。

 

ふとした疑問をプログラミングとソフトを使って色々検証できて面白かったです。勉強にもなりました。

 

このようなブログを書くのははじめてでしたが、内容がうまくまとまらず自分の文章力のなさを痛感しました。日々ブラッシュアップしていきたいと思います。

 

ここまで読んでくださった方、まことにありがとうございました。

 

全国にあるAOKI・青山の店舗をざっくり分析してみる

前回はQGISを使ってAOKI、青山の店舗と道路データを表示させました。
今回はQGISの分析機能を使って、店舗位置関係をざっくりですが定量的に調べてみたいと思います。

具体的にはAOKIの近傍にある青山の店舗の抽出と各店舗の近傍にある道路との距離測定を行っていきます。

【目次】

 

 各店舗レイヤの投影法の定義

各店舗レイヤの座標系はJGD2000の緯度経度で表される地理座標系であるため、投影法を定義します。

投影法を定義することで、店舗間距離測定等の空間演算処理で「m」といった馴染みのある距離単位を使用することができます。

f:id:hhgingisland:20180526175707p:plain

今回、投影法は「UTM54N」にします。

UTM54Nは上図の青線内の地域(東経138-144度)、大体長野県や静岡県あたりから北海道までを投影対象としています。このため、全国の都道府県をUTM54Nで投影すると、投影対象範囲外の県では歪みが大きくなると思われます(範囲外に対しどのような計算をしているか不明)。

今回は近傍の地物(店舗や道路)の距離を測定していくので、近傍にあれば歪みの大きさは同じだろうと思い、全国の地物に対してUTM54Nで投影します。

(実際は当初関東だけを対象に分析をしようと思っていたのでUTM54Nで設定しており、それをそのまま使用したというだけです。。。)

投影方法は、保存対象のレイヤを右クリックして「名前をつけて保存」内の「CRS」で投影法をJGD2000 / UTM zone 54N (EPSG: 3100)として保存します。

f:id:hhgingisland:20180526180939p:plain 

同様に地理座標系であった道路レイヤもJGD2000 / UTM zone 54N (EPSG: 3100)に変換します。

なお、道路レイヤ内にある高速道路データは新しいデータ(高速道路時系列、前回記事のピンク線のデータ)に入れ替えて使用しています。この入れ替える方法については違う記事に記したいと思います。そのまま道路レイヤ内にある高速道路を使用しても結果に大きな違いはないと思います。

 

 AOKIの近くに青山があるか調べる

分析方法概要

AOKIの店舗レイヤに指定した距離のバッファ処理をし、そのレイヤと青山の店舗レイヤが交差するAOKIの店舗数を数えていきたいと思います。

「近くにある」の距離の定義

個人的な感覚ですが、徒歩10分以内でいける500mから車で10分以内にいける5kmの範囲が「近くにある」のではないかと思いました。

なのでこの2つの距離を用いて調べてみたいと思います。

バッファ処理

バッファ処理は対象とするポイントから指定した距離の範囲内を示すポリゴンを生成します。

コントロールパネルの「ベクタ」→「空間演算ツール」→「固定距離バッファ」をクリックし、以下の画面の「距離」に500を入力します。

「バッファ」にレイヤの保存場所を指定し、「Run」を押すと、バッファ処理されたレイヤが生成されます。

f:id:hhgingisland:20180526181228p:plain

AOKIから500m以内に青山があるようなAOKIの店舗数を導出

空間検索を用いて該当する店舗を抽出します。

コントロールパネル→「ベクタ」→「空間検索」→「空間検索」をクリックし、下の画像のようにバッファ処理したレイヤを選択、地物の場所を「交差」にし、地物の参照に青山の店舗レイヤを入力します。

f:id:hhgingisland:20180526181433p:plain

適用を押すと以下のように、AOKIの577軒中141軒となりました。

個人的にはもっと多いかと思っていましたが、500m以内だとこんなものなんでしょう。

f:id:hhgingisland:20180526181636p:plain

同様に、AOKIの5km以内に青山があるようなAOKIの店舗数を調べてみると、577軒中554軒あることがわかりました。

以上の結果からAOKIの店を見かけたら、

  • 約25% (141軒÷577軒より) の確率で、徒歩10以内の近距離に青山がある
  • 約96% (554軒÷557軒より) ほぼ100%の確率で、車で10分走らない距離で青山を見つけることができる

と言えます。

 

 国道沿いにAOKIと青山があるか調べる

次に、AOKIと青山が国道沿いに立地しているか調べてみます。
分析方法は前述のAOKIと青山の位置関係を調べた方法と同じように行います。

「沿いにある」の距離の定義

道路レイヤや投影法による歪み、店舗位置と道路までの実際の距離(道路沿いに店舗があっても入り口前に駐車場がある場合など)を加味して200mに設定します。

バッファ処理&空間検索

AOKIと青山の店舗レイヤに200mのバッファ処理をします。

次に、空間検索対象として国道を指定したいので、道路レイヤから主要地方道を除外したレイヤを作成します。

レイヤパネル内にある道路レイヤを右クリック→属性テーブルを開く→コントロールパネルの「式による選択」をクリック。

f:id:hhgingisland:20180526182242p:plain

主要地方道は「N01_001」属性の属性値「3」を示すので、上図のように"N01_001"  =  '3'と式を入力・適用した後に、コントロールパネルの「選択結果を反転する」をクリックし、主要地方道以外の道路を選択します(高速道路も含めています)。

選択結果を別レイヤとして保存します。

空間検索により、国道から200m以内にあるAOKIと青山の店舗はそれぞれ以下のような結果となりました。

  • AOKI:215軒 / 575軒 → 37%
  • 青山: 396軒 / 812軒 → 49%

マップ上だとほとんどの店舗が国道沿いにあるという印象を受けていたので、意外と少ない結果でした。

しかしよくよく考えてみると、県道などの地方道も大きな道路は多くあり、その道路沿いにも店舗はあるだろうと思い、主要地方道も入れて検索し直してみます。

ちなみに、下図のように地方道データ(紫色の線)は分布しています。

f:id:hhgingisland:20180526183123p:plain

主要地方道を入れて再検索した結果は、

  • AOKI:365軒 / 575軒  → 63%
  • 青山: 555軒 / 812軒  → 68%

となりました。

やはり地方道を入れるとヒットする件数が増えます。
AOKIも青山も6~7割程度の店舗が国道および主要地方道沿いに分布しています。その他の店舗は道路レイヤの位置や店舗位置の誤差により、実際に道路沿いにあるのに200m以上離れてしまっているケースと、ここで示した道路と違う道路沿いに分布しているケースがあります。

どちらのケースが多いか判断するのは難しい(できない?)ですが、主要地方道に含まれないが比較的交通量の多い道路や最近できた道路(使用データは20年前)沿いにできた店舗が多く検索に漏れてきているのではないかと思っています。

いずれにせよ、AOKIと青山は過半数が交通量の多い主要な道路沿いに分布していることから、そのような場所を狙って出店しているのだろうと考えられます。

 

 まとめ

今回はざっくりとですが、定量的にAOKIと青山、そして国道との位置関係について調べてみました。

調べてみた結果、

  • 4軒中1軒のAOKIで、すぐ近く(500m、徒歩10分以内)には青山の店舗がある
  • ほぼ全てのAOKIで、近く(5km、車で10分以内)には青山の店舗がある
  • 国道沿いにあるAOKI、青山の店舗は全体の約4~5割程度
  • 主要地方道を含めると、それら道路沿いにある店舗は全体の6~7割程度

ということが分かりました。

 

記事1でたてた仮説【「AOKI」の近くに「青山」がある】は合っていたのではないかと思っています。

仮説【「AOKI」と「青山」は田舎の国道沿いに多くある】については、確かに半分程度の店舗は国道沿いに分布しているが、それ以外の地方道といった道路沿いにも多く分布している、と修正することができました。

 

次回の記事では、分析範囲をUTM54N内に絞り、もう少し詳細に調べてみたいと思います。

 

QGISを使って店舗位置を表示する

これまでの記事でAOKIと青山の店舗名と住所をwebスクレイピングCSVファイルに保存しました。

WEBスクレイピング - AOKI編 - - 道草を楽しむブログ

WEBスクレイピング - 青山編 - - 道草を楽しむブログ


今回はフリーのGISソフトであるQGISを使用して店舗位置を地図上に表示してみます。

【目次】


環境


ジオコーディング

QGISは地物位置のインプットデータとして住所を用いることができないため、これまで取得した住所情報を緯度経度といった座標値に変換するジオコーディングが必要になります。

ジオコーディングには、東京大学空間情報科学研究センターが提供する「CSVアドレスマッチングサービス」を利用します。

f:id:hhgingisland:20180522213422j:plain:w300

パラメータ設定で以下の内容を記載します。

  • 対象範囲:全国街区レベル(経緯度・世界測地系
  • 住所を含むカラム番号:2 (AOKIの場合)、3(青山の場合)
  • 入力ファイルの漢字コード:シフトJISCSVファイルを保存した時の文字コードによって変わります)

送信ボタンを押すと、入力ファイルに緯度経度情報等(fX、fY、iConf、iLvlの4列)が追加されたcsvファイルが返ってきます。

fXが経度、fYが緯度を表しています。
なお、下図の1行目にある項目名col0~col2の自分が分かる名称に変えておくと、QGISでラベル表示するときに分かりやすいです。

f:id:hhgingisland:20180522215751j:plain:w400


QGIS上に店舗位置を表示する

QGISを起動し、「デリミティッドテキストファイルからレイヤを作成」をクリックし、ジオコーディングしたCSVファイルを入力します。

パラメータ設定はデフォルトで大丈夫かと思いますが、エンコーディングとXYフィールドに経度と緯度の列項目名を正しく指定できているか確認しましょう。

f:id:hhgingisland:20180522220342j:plain:w400

「OK」を押して店舗位置の座標系をJGD2000に設定すると店舗位置が表示されます。 緑丸がAOKI、紫丸が青山の店舗位置です。

なお、OTFを有効にし座標系はWEBメルカトル(EPSG: 3857)にしています。


f:id:hhgingisland:20180526154548p:plain


ベースマップの追加

店舗位置だけでも日本の概形が分かりますが、あると便利なのでベースマップを一応入れておきます。

OpenLayers Plugin」という便利なプラグインhttps://plugins.qgis.org/plugins/openlayers_plugin/ )をインストールすれば、Google MapsOpenStreetMapなどの地図を表示することができます。私の場合はOpenStreetMapを入れました。

f:id:hhgingisland:20180526155344p:plain


道路レイヤの追加

国道沿いに店舗があるかどうか調べたいので道路情報を追加します。

国土数値情報 ダウンロードサービス」には、平成9年の古い道路データ(http://nlftp.mlit.go.jp/ksj/gmlold/meta/ksjshpgml-N01.html)しか見当たりませんでしたが、それを使うことにします(探し方が悪かったか??)。

QGISの「ベクタレイヤの追加」によりダウンロードしたシェープファイル(N01-07L-2K_Road.shp)をロードします。座標系はJGD2000 (EPSG: 4612)です。

このデータは道路種別コード属性(N01_001)により高速道路、一般国道主要地方道の3タイプの道路が格納されています。

f:id:hhgingisland:20180526155651p:plain:w400

レイヤプロパティのスタイルで、「分類された」を用いてN01_001の属性値によりシンボルの場合分けをします。

一般国道(水色の線)のみを表示させた場合は以下の図のようになりました。

f:id:hhgingisland:20180526160142p:plain
国土交通省国土政策局「国土数値情報(道路)」をもとに編集したものである。


高速道路に関しては新しいデータ(http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N06-v1_2.html)があり、このデータはバイパスも含むようなのでこちらもダウンロードして使うことにします。

こちらも表示すると以下のようになります。ピンク線が高速道路です。

f:id:hhgingisland:20180526160425p:plain
国土交通省国土政策局「国土数値情報(道路)」をもとに編集したものである。


作成したマップを眺める

作成したマップを見てみると以下のことが言えそうです。

  1. 青山は全国展開していてるが、AOKIは出店エリアが絞られている。
  2. AOKIと青山は近いところにありそう。
  3. 両店舗は主要国道沿いに分布してそう。


f:id:hhgingisland:20180526162608p:plain:w400
AOKIレイヤ(緑)を上、青山レイヤ(紫)を下にして表示
f:id:hhgingisland:20180526163903p:plain:w400
AOKIレイヤ(緑)を下、青山レイヤ(紫)を上にして表示

上の2つの図はAOKIと青山レイヤの上下間を変えたものです。

紫点の青山は全国にまんべんなく分布しているのに対し、緑色のAOKIは中国地方や四国に店舗がほぼなく、町が大きそうなところに店舗を絞って出店していそうです。

また、上の2つ目の図を見ますと、紫色のAOKIレイヤが青山レイヤをほぼ覆いかくしていることから、AOKIと青山の店舗は両者近いところにありそうです。

f:id:hhgingisland:20180526160425p:plain

そして上図のように水色の一般国道上に各店舗がのっていることから、国道沿いに店舗が分布してそうだと推測できます。

以上、定性的にAOKIと青山の位置関係を考えてみました。


次の記事では、少し定量的に位置関係を調べてみたいと思います。

WEBスクレイピング - 青山編 -

前回の記事ではpython + Beautiful Soupを使ってAOKIの店舗住所をスクレイピングしてみました。

hhgingisland.hatenablog.com

今回は青山の店舗住所を抽出したいと思います。
作業環境は前回の記事と同様です。


青山の店舗検索ページ

f:id:hhgingisland:20180529001253p:plain


青山の店舗検索ページ(https://www.y-aoyama.jp/shop/?lc=header)にアクセスし調べてみたところ、上の画像のように左上の都道府県欄にどこかの都道府県を指定しないと店舗情報が出てきませんでした。

上の画像では北海道を指定しており、
URLはhttps://www.y-aoyama.jp/shop/?pref=1となっています。

?pref=以下の数値が各都道府県と対応づけられているので、この数値を変えることで、全国の店舗名と住所を抽出できそうです。


ソースHTMLを見てみる

f:id:hhgingisland:20180522205425j:plain


Google chromeの開発者ツールを使って見ていくと、

  • 店舗名は、<div class=” arrowTicket_body”> → <h2>の内部テキスト
  • 住所は、<div class=” arrowTicket_body”> → <p class=” arrowTicket_text arrowTicket_margin”>の内部テキスト

であることが分かります。


以下のコードのようにfind_all()get_text()を使って簡単に店舗名と住所(検索ページの最初に記載されている1店舗分ですが)を抽出することができました。

なお、get_text()で得られた住所情報は、郵便番号と住所が混じっていためsplit()を用いて分離しています。

#! python3
# -*- coding: utf-8 -*-

import requests
from bs4 import BeautifulSoup

url = r'https://www.y-aoyama.jp/shop/?pref=1'

res = requests.get(url)
res.raise_for_status()

soup = BeautifulSoup(res.content, 'html.parser')
shop_elems = soup.find_all('div', class_='arrowTicket_body')

# 店舗名
shop_name = shop_elems[0].h2.get_text()
# 住所
shop_address_elem = shop_elems[0].find_all('p', class_='arrowTicket_text arrowTicket_margin')
shop_address = shop_address_elem[0].get_text()

# 郵便番号と住所を分離し保存 
post_code = shop_address.split(None,1)[0]
address = shop_address.split(None,1)[1]


コード内のshop_elemsはページ内の店舗情報が記載されているdiv要素のリストです。

ページ内の店舗情報を抽出する場合には、リスト内でループを回すことで取得できます。


全店舗の位置情報を取得するためのコード

上述のコードで店舗名と店舗住所が抽出できたので、それを基にfor文を使用して全店舗の情報を抽出していきます。

ソースコードを下に貼りました。流れは以下の通りです。

  • 都道府県のページに移動するために、都道府県ごとの店舗名と住所を取得しリストに保存。
  • 既に閉店しているものを除外。
  • 住所に &nbsp が入っているのを除外(文字化け対策)。
  • 抽出した情報をCSVファイルに格納。


取得した住所情報には、既に閉店した店舗情報が入っており、それらは店舗名に「完全閉店」と書かれているので、リスト内包記とfilter関数を用いて閉店した店舗を除外しています。

また、住所に半角スペースの一種である&nbsp (0xA0)が含まれており、本環境では文字化けするのでreplace()を使用して除外しています。


以上で、AOKIと青山の店舗位置情報を得ることができたので、次回からQGISを用いて各店舗の位置関係を調べていきたいと思います。


#! python3
# -*- coding: utf-8 -*-

import csv
import requests
from bs4 import BeautifulSoup

# 店:青山(全国)
# 変数prefは「都道府県」ドロップリストの並び順になっている模様
url = r'https://www.y-aoyama.jp/shop/'

res = requests.get(url)
res.raise_for_status()

# 都道府県名とそのidを取得
soup = BeautifulSoup(res.content, 'html.parser')
pref = []
pref_id = []

pref_elem = soup.find_all('option')
for i in range(len(pref_elem)):
        pref.append(pref_elem[i].get_text())
        pref_id.append(pref_elem[i]['value'])

# 最初と最後に格納される'選択してください'を削除
pref = pref[1:-1]
pref_id = pref_id[1:-1]

# pref&pref_idを用いて各都道府県のページに移動し店舗情報の抽出
# 店舗名と店舗住所の抽出用パラメータの初期化
shop_list = []
shop_name = []
post_code = []
shop_address = []
shop_pref = []

# 都道府県ごとにループ
for i in range(len(pref)):
        res_temp = requests.get(url+'?pref='+pref_id[i])
        res_temp.raise_for_status()

        # 店舗名と店舗情報の抽出
        soup_temp = BeautifulSoup(res_temp.content, 'html.parser')
        shop_elems = soup_temp.find_all('div', class_='arrowTicket_body')
        for shop_elem in shop_elems:
                shop_name.append(shop_elem.h2.get_text())
                shop_address_elem = shop_elem.find_all('p', class_='arrowTicket_text arrowTicket_margin')
                post_code.append(shop_address_elem[0].get_text().split(None,1)[0])
                shop_address.append(shop_address_elem[0].get_text().split(None,1)[1])
                shop_pref.append(pref[i])

# 閉店している店舗があるためそれらを除外する shop_nameに'完全閉店'と書かれている。
close_shop_indexes = [i for i, x in enumerate(shop_name) if '完全閉店' in x]

for close_shop_index in close_shop_indexes:
        shop_name[close_shop_index] = ''
        shop_address[close_shop_index] = ''
        shop_pref[close_shop_index] = ''

shop_name = list(filter(lambda x:x != '', shop_name))
shop_address = list(filter(lambda x:x != '', shop_address)のみ)
shop_pref = list(filter(lambda x:x != '', shop_pref))

# &nbsp を除外(いくつかの店舗の住所に含まれており文字化けする)
shop_address = [x.replace('\xa0', '') for x in shop_address]


# csvファイルに書き出し
output_file = open('aoyama_all.csv', 'w', newline='')
output_writer = csv.writer(output_file)

for i in range(len(shop_name)):
        output_writer.writerow([shop_name[i], post_code[i], shop_address[i], shop_pref[i]])

output_file.close()

WEBスクレイピング - AOKI編 -

前回の記事では、AOKIと青山の店舗位置関係を調べたくなったので、pythonによるwebスクレイピングをして住所情報を抽出しよう、と意気込みました。

hhgingisland.hatenablog.com


今回Webスクレイピングをするにあたり使用したモジュールとバージョンを先に書いておきます。

  • Requests 2.14.2
    • Webページをダウンロード。
  • Beautiful Soup 4.6.0
    • HTMLをパース。

またPCのOSとwebブラウザおよびPythonのバーションは以下のものを使用しました。


私はwebスクレイピングをはじめて行うので全くスマートでないコードを書いていると思います。こういう風に書いた方が良い等ありましたら是非教えて頂きたいです。

それではまずAOKIのサイトのHTMLを見てみます。


AOKIの店舗検索ページ

AOKIのwebサイト(https://www.aoki-style.com/)にアクセスしページ右上に「店舗検索」ボタンがあるので押します。

ページ左上の「都道府県別で探す」でデフォルトのまま「北海道」として検索ボタンを押してみると下図のように店名と住所がセットで表示されます。

f:id:hhgingisland:20180522002011j:plain

このページのURLは、
https://www.aoki-style.com/shoplist/search_result?prefecture_id=1&p=1
です。

検索結果が1ページあたり10軒表示され、次の10軒の検索結果を表示する場合はURLの末尾のp=以下に数字を指定することで見ることができます。

また、URLのprefecture_id=1が北海道を示すため、idの値を変えることで全都道府県の検索結果を見ることができます。

全国の店舗情報をプログラミングで抽出するには、各都道府県prefecture_idと検索結果ページpの2つの変数のループを回すことで取得できそうです。


しかしこれはちょっとコード書くの面倒だなあと気が進まず、サイトを色々見ていたところ、以下のURLをセットすると全店舗の検索結果が出ました。

https://www.aoki-style.com/shoplist/search_result?


f:id:hhgingisland:20180522193323j:plain

都道府県ループが減らせ、ページ番号を増やしていくだけで全店舗情報を抽出できそうです。

このページを基準にして情報を抽出していきたいと思います。


ソースHTMLを見てみる

f:id:hhgingisland:20180522211253j:plain


Google chromeの開発者ツールを使って見ていくと上の画像のように、

  • 店舗名は、<div class=”shop-list-shop-name”> → <p> →
    <a>の内部テキスト
  • 住所は、店舗名が記載されたdiv要素の次のdiv要素にあり、
    その中の<p class=”shop-list-shop-address”>要素の内部テキスト

であることが分かります。


以下のようなコードを書くと店舗名と住所(検索ページのはじめに記載されている1店舗分ですが)を取得できました。

#! python3
# -*- coding: utf-8 -*-

import requests
import bs4

url = r'https://www.aoki-style.com/shoplist/search_result?&p=1'

res = requests.get(url)
res.raise_for_status()

soup = bs4.BeautifulSoup(res.content, 'html.parser')
shop_name_elem = soup.find_all('div', class_='shop-list-shop-name')

# 店舗名
name = shop_name_elem[0].a.get_text()
# 住所
shop_details = shop_name_elem[0].next_sibling.next_sibling
address =shop_details.find_all('p', 'shop-list-shop-address')[0].get_text()


shop_name_elemはページ内の全店舗名を含むdiv要素を格納しているリストで、要素は10個あります(1ページあたり10軒の店舗名が記載されているため)。

上記コードでは、リストの0番目の要素に対して、a.get_text()をすることで、店舗名を導出しています。

したがってfor文を回すことで、ページ内の店舗名を全て取得できるようになります。


店舗住所については、next_siblingを2回使用して取得を試みています。

住所は店舗名を含むdiv要素の次に書かれているdiv要素なので、next_siblingは1個だけ書けばよいのでは?と思うかもしれませんが、div要素の後に改行コード\nが入っているため、next_siblingをもう1個書き足さないと目的のdiv要素の情報を取得できません。

後は、find_all()get_text()を使用して住所を抽出しています。


全店舗の位置情報を取得するためのコード

店舗名と店舗住所が抽出できたので、これを基にfor文を使用して全店舗の情報を抽出していきます。以下にソースコードを貼りましたのでご参照下さい。

なお、コードでは後にQGISで分析する際に各店舗の都道府県名が分かると便利かと思い、抽出した住所から正規表現を用いて都道府県名を抽出しています。

そして店舗名、店舗住所、都道府県名の3つの要素を最終的にCSVファイルに保存しています。


次回は青山の店舗情報を取得していきたいと思います。

#! python3
# -*- coding: utf-8 -*-

import csv, re
import requests
import bs4


# 店:青木(全国)
url = r'https://www.aoki-style.com/shoplist/search_result?'

res = requests.get(url)
res.raise_for_status()

soup = bs4.BeautifulSoup(res.content, 'html.parser')

# 全店舗数の抽出
shop_num_result = soup.find_all('div', class_='result-text')
a = shop_num_result[0].text.split(' / ')
shop_num = a[1].split('件')

# 全店舗検索結果ページ数の導出(1ページあたり10軒表示される)
max_page_num = int(shop_num[0]) // 10 +1

# 都道府県を抽出(わざわざやる必要もないがサイトから都道府県名を抜き出す)
shop_pref = []
shop_pref_elem = soup.find_all('option')
for i in range(len(shop_pref_elem)):
    shop_pref.append(shop_pref_elem[i].get_text())


# 各店舗検索結果ページのurl&htmlドキュメントを取得
url_list = []
res_list = []
for i in range(max_page_num):
        url_list.append(url+'&p='+str(i+1))
        res_temp = requests.get(url_list[i])
        res_temp.raise_for_status()
        res_list.append(res_temp)

# 店舗名と店舗住所の抽出用パラメータの初期化
shop_list = []
shop_name = []
shop_address = []

# 店舗名と店舗住所の抽出
# ページ1~最後のページの一つ前までの処理
for i in range(max_page_num - 1):
        soup = bs4.BeautifulSoup(res_list[i].content, 'html.parser')
        shop_name_elem = soup.find_all('div', class_='shop-list-shop-name')
        for j in range(10):
                # 店舗名
                name = shop_name_elem[j].a.get_text()
                shop_name.append(name)
                # 店舗住所
                shop_details = shop_name_elem[j].next_sibling.next_sibling
                address =shop_details.find_all('p', 'shop-list-shop-address')[0].get_text()
                shop_address.append(address)

# 最後のページの処理
soup = bs4.BeautifulSoup(res_list[max_page_num-1].content, 'html.parser')
shop_name_elem = soup.find_all('div', class_='shop-list-shop-name')
lastpage_shop_num = int(shop_num[0]) % 10

for j in range(lastpage_shop_num):
        # 店舗名
        name = shop_name_elem[j].a.get_text()
        shop_name.append(name)
        # 店舗住所
        shop_details = shop_name_elem[j].next_sibling.next_sibling
        address =shop_details.find_all('p', 'shop-list-shop-address')[0].get_text()
        shop_address.append(address)

# 店舗住所の都道府県名の取得
pref = []
for i in range(len(shop_name)):
    for j in range(len(shop_pref)):
        if re.match(shop_pref[j], shop_address[i]):
            pref.append(re.match(shop_pref[j], shop_address[i]).group())
            break

# csvファイルに書き出し
output_file = open('aoki_all.csv', 'w', newline='')
output_writer = csv.writer(output_file)

for i in range(len(shop_name)):
        output_writer.writerow([shop_name[i], shop_address[i], pref[i]])

output_file.close()

「AOKI」の近くに「青山」があるか検証してみる

f:id:hhgingisland:20180520215148j:plain

 

都心から離れた国道沿いの風景というと大体同じだなと思っている人は多いと思います。

最近そのような国道沿いを車で走っていた時にふと思いました。

「国道沿いにAOKIとか洋服の青山がよくあるけど、AOKIがあるところの近くに青山ってあるよな・・・」

f:id:hhgingisland:20180526205343p:plain

Google マップの画像に追記

グーグルマップでちょっと調べてみると、上の写真のようにAOKI(写真左手前)の近くに青山(写真右奥)がありました。

他の店舗もそうなのかなと気になったので、

  • 仮説①「AOKI」の近くに「青山」がある
  • 仮説②「AOKI」と「青山」は国道沿いに多くある

を検証してみたいと思います。

検証方法

店舗間の位置関係を調べたいので丁度最近使っていたフリーのGIS(地理情報システム)ソフトであるQGISを用いて調べてみます(勉強にもなる!)。

大枠は以下の通り。

  1. 「AOKI」と「青山」のサイトから店舗住所を抽出する。
  2. 住所情報をQGISにローディングし店舗間位置を分析する。

1については、手動でエクセルにコピペしようかと思っていましたが、これまた最近pythonの勉強をしていたので、pythonでWebスクレイピングをして情報を取得してみることにします。

Pythonに関しては、Twitterでフォローしているとくさん@nori76 (id:nori76)が紹介していた「O'Reilly Japan - 退屈なことはPythonにやらせよう」を参考にしています。

Pythonによるテキストやエクセルファイルの操作方法とかのってて、業務効率化に役立っています。

 

ということで、次の記事からwebスクレイピングをして店舗情報を抽出していきたいと思います。