Python BeautifulSoup: キャンペーン加盟店をスクレイプした備忘録

勉強中です。備忘録として記載しています。

Webスクレイピングに役立つPythonライブラリ
-
- RequestsとBeautiful Soup
- Scrapy
- Selenium

HTTP概要
-
WebブラウザとWebサーバーは、HTTP(Hyper-Text Transfer Protocol)という通信プロトコル（ルール）に則って情報の受け渡しを行っている。
クライアント（Webブラウザ）は、HTTPのGETメソッドでサーバー（Webサーバー）に、「このURLのページを下さい」とリクエストを送り、それに対して、サーバーはページをHTMLファイルを返す（リスポンス）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3190318/3956e2de-5129-3dc4-5da1-8275cc2a144b.png)

スクレイプ開始
-
スクレイピングでは，次のことを行う．
1. WebサーバーにGETリクエストを送り、ResponseのHTMLを受け取る
2. HTMLを解析し，必要な情報を抽出し保存する

今回，PythonでrequestsとBeautifulSoupモジュールを使いこの作業を行う．
スクレイピングするサイトは，名古屋市プレミアム付商品券加盟店一覧：https://nagoya-shouhinken.com/shop/
プレミアム付商品券は自治体実施のお得なキャンペーンだが，どのお店で使えるかが，このサイトで検索しない限りわからない，しかも加盟店は6000店舗を超えており，一覧を手に入れるには，10件/頁 x 600ページを延々とクリックしていく必要がある．これをプログラミングでポチッと終わらせることが今回の目標だ．

WebページのHTMLを見てみると，iframeが使われていることが分かった，iframは，別のサイトにリンクして，リンク先の情報を表示させる．
リンク先のページを確認する．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3190318/10982d43-b958-8905-6340-e391ac3c977c.png)
リンク先　https://sub.nagoya-shouhinken.com/prd/c_app/shoplists?page=614

店舗情報が下画像のボックス上に並んでいる．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3190318/4c9ec812-02fa-463a-707b-6f4c7b2ce580.png)

では，早速コードを書いていく．
まずは，HTMLファイルをGet要求し，Responseを変数に入れる．
```code.py
#必要なライブラリをインポート
from bs4 import BeautifulSoup
import requests

#Get要求し，Responseを「page」変数に入れる
page = requests.get(url)
#「page」のコンテンツをBeautifulSoupモジュールで解析し，「soup」変数に文字列として入れる
soup = BeautifulSoup(page.content, 'html.parser') 
```

次に、欲しい情報の抽出を行う。
「店名」等欲しい情報のタグを確認．HTMLタグとclassを引数にして，ページから該当するタグとclassを持つデータを抽出する．
```code.py
#「店名」，「エリア」，「カテゴリ」を抽出
#classを渡すときの，引数名は「class_」．アンダースコアを忘れないようにする
names = soup.find_all("h2", class_="p-area-shoplist__headline")
areas = soup.find_all("dd", class_="-area")
categories = soup.find_all("dd", class_="-category")
```
結果をprintし，欲しかった情報が抽出されていることを確認する．
「areas」変数の中身をprint.
```code.py
for area in areas:
  print(area.text)
```

今回は「取扱券種」は抽出対象外とした．
「住所」と「TEL」だが，タグが入れ子構造になっており，「住所」だけを指定して抽出する方法が不明だったので，より上位のタグとclassで，「TEL」と一緒に情報を抽出し，抽出したデータから，「住所」と「TEL」を抜き出すことにする．
```code.py
#「住所」，「TEL」を抽出
addresses = soup.find_all("dl", class_"p-area-shoplist__address")
```

では次に，1ページ目以外の，他のページをどのようにスクレイピングするのか考える．
2ページ目や3ページ目のリンクにカーソルをあてると，URLの最後にページ番号（page=（ページ番号））がついていることが分かる．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3190318/6d9f99d4-6f66-f30a-3972-808f0b34af6c.png)
リンク末尾にページ番号をつけ，ページ数分ループすることで，全ての情報が取れそうだ．

最終的なコードはこちら
```code.py
from bs4 import BeautifulSoup
import requests, random, time

for p in range(1,614):
  url = 'https://sub.nagoya-shouhinken.com/prd/c_app/shoplists?page=' + str(p)
  page = requests.get(url)
  soup = BeautifulSoup(page.content, 'html.parser') #page変数はHTMLなので，内容が文字列として取り出す

  names = soup.find_all("h2", class_="p-area-shoplist__headline")
  areas = soup.find_all("dd", class_="-area")
  categories = soup.find_all("dd", class_="-category")
  addresses = soup.find_all("dl", "p-area-shoplist__address")#住所とTELを含む情報をまとめて持つ

#この後CSVファイルにしてエクスポートするために，結果をリストに入れていく
  result = []
  d = {}
  for i in range(10):
      name = names[i].text.split('\n')[1].strip()　#余分な余白を削除してある
      area = areas[i].text
      category = categories[i].text
      address = addresses[i].text.split("\n")[3]　#住所のみを取り出してある
      tel = addresses[i].text.split("\n")[-3]　#TELのみを取り出してある
      d = {"name": name, "area": area, "category": category, "address": address, "tel": tel}
      result.append(d)
  time.sleep(random.randint(5,8)) #次のスクレイぷに進むまで5〜8秒ランダムに待つ
```