WEBスクレイピング - AOKI編 -
前回の記事では、AOKIと青山の店舗位置関係を調べたくなったので、pythonによるwebスクレイピングをして住所情報を抽出しよう、と意気込みました。
今回Webスクレイピングをするにあたり使用したモジュールとバージョンを先に書いておきます。
- Requests 2.14.2
- Webページをダウンロード。
- Beautiful Soup 4.6.0
- HTMLをパース。
またPCのOSとwebブラウザおよびPythonのバーションは以下のものを使用しました。
- Windows7 64bit
- Google Chrome
- Python 3.6.1
私はwebスクレイピングをはじめて行うので全くスマートでないコードを書いていると思います。こういう風に書いた方が良い等ありましたら是非教えて頂きたいです。
それではまずAOKIのサイトのHTMLを見てみます。
AOKIの店舗検索ページ
AOKIのwebサイト(https://www.aoki-style.com/)にアクセスしページ右上に「店舗検索」ボタンがあるので押します。
ページ左上の「都道府県別で探す」でデフォルトのまま「北海道」として検索ボタンを押してみると下図のように店名と住所がセットで表示されます。
このページの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?
都道府県ループが減らせ、ページ番号を増やしていくだけで全店舗情報を抽出できそうです。
このページを基準にして情報を抽出していきたいと思います。
ソースHTMLを見てみる
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()