PythonとBeautiful Soupを使って宮城県県HPからの産直情報をスクレーピングしてみました〜
今週初めてwebスクレーピングに挑戦しましたので、ここどうやって何をしたのかご紹介します。I'm going to do it in Japanese only, because the page I'm scraping is in Japanese, and also because I'm too lazy to write both.
きっかけは県内の産直地図がほしかった
産直が結構好きで、今住んでいる宮城県内にどこら辺に産直があるのが知りたかったのです。最近グーグルマップですと、産直も含めて何かを検索すると関係ないものがかなり表示されてしまいますあまり使いたくないと思いました。
宮城県では、県内の産直情報が載っているウェブページがあります。
https://www.pref.miyagi.jp/soshiki/nariwai/cyokubaijyo.html
各地域の産直ページに行きますと、住所、営業時間、産直の名前などリストアップされていますが、各産直の位置が分かるような画像や地図はないのです。
そこで、各産直ページの情報をwebスクレーピングして、その情報をエクセルに書き込んで、エクセルをgoogle sheetsにインポートすれば、自分で宮城県の産直グーグルマップ(マイマップ)が作れるのではないかと思って、やってみたら実際に作れました。
主に使った参考資料
主に使った参考資料はAutomate the Boring Stuff with Pythonでした。英語版は無料で読めます。
Automate the Boring Stuff with Python
日本語版は残念ながら無料ではないみたいですが、近くの図書館にあるかもしれません...
退屈なことはPythonにやらせよう(Calilページ)
PythonよりJavascriptに慣れていて、頻繁にPythonに「インデントがっ!コロンがっ!そのセミコロンいらない!」などと怒られながらですが、なんとか書けるので9章「ファイルの読み書き」、10章「ファイルの管理」、12章「Webスクレーピング」、13章「エクセルシート」を一通り読んで、参考にしながらプログラムを作ってみました。
グーグルシートの章もあり、グーグルマップの前段階でグーグルシートにする必要があったため直接グーグルシートに書き込もうとトライしてみましたが、Automate the Boring Stuffのグーグルシート情報が少し古く、グーグルさんの説明があまりにもわかりづらいので諦めてエクセルにしました。
Show Me the Code
コードです。
import requests, bs4
from openpyxl import Workbook
from pathlib import Path
url = "https://www.pref.miyagi.jp"
p = Path.home()
mega_list = []
↑ 色々インポートします。ここで説明しようとするより...どこかの説明にリンクします。
requests: PythonのRequest Moduleとは
bs4: Beautiful Soup 4.2.0 Doc. 日本語訳
openpyxl: openpyxl による Excelファイル操作方法のまとめ
pathlibとPathはホームデレクトリーにエクセルファイルを保存するためです。
mega_listは最終的にエクセルファイルに書き込む配列です。
wb = Workbook()
ws = wb.active
ws.title = "ザ・産直spreadsheet"
sheet_headings = ["市町村", "産直名", "所在地", "連絡先", "運営主体", "営業時間", "駐車スペース",
"主な取扱品目", "ここが自慢!"]
ws.append(sheet_headings)
↑ 次はエクセルファイルを作って、シートのタイトルと各コラムのタイトルを設定します。
res = requests.get(url + "/soshiki/nariwai/cyokubaijyo.html")
res.raise_for_status()
main_page_soup_object = bs4.BeautifulSoup(res.content, features="html.parser")
links = main_page_soup_object.select("ul strong a")
↑ 宮城県のメイン産直ページに行きます。
半分(https://www.pref.miyagi.jp)を「url」にして、それに「/soshiki/nariwai/cyokubaijyo.html」などをつける理由は、ページ内のリンクが相対URLだからです。
bs4を使って帰ってくるhtmlをBeautiful Soupオブジェクトにします。
「みやぎの農産物直売所」ページをみてみましょう。
https://www.pref.miyagi.jp/soshiki/nariwai/cyokubaijyo.html
ブラウザーのデベロッパーツールで見ますと7つの地域ページにつながっているリンクはulに入っているリンクです。上のメニューもulですが、strongタグを使っていません。"ul strong a"で欲しいリンクのみをセレクトできます。
for link in links:
area_url = url + link.get("href")
print("going to " + area_url)
response = requests.get(area_url)
response.raise_for_status()
area_soup_object = bs4.BeautifulSoup(response.content, features="html.parser")
table_links = area_soup_object.select("table a")
# Kurihara has no table, alternate way
if(len(table_links)==0):
links = area_soup_object.select(".detail_free a")
sliced_list = links[0:9:1]
sanchoku_info(sliced_list)
else:
sanchoku_info(table_links)
↑ links配列に入っているのはBeautiful Soupのタグオブジェクトです。
各地域ページへのリンクです(気仙地域、登米地域など)。
一個ずつのタグオブジェクトからリンク(href)をゲットし、上と同様requestsとbs4を使ってhtmlを持ってきてBeautiful Soupオフジェクトにします。
ある地域のページを見てみまましょう!気仙地域にしましょう。
https://www.pref.miyagi.jp/soshiki/ks-tihouken-n/cyokubaijyo-kesennuma-all.html
欲しい情報に近づきました!個別産直ページへのリンクです。今度は、tableの中に入っています。リンクを"table a"でセレクトできます。
ただし、栗原地域のページだけはtableに入っていないので、tableからのリンク収穫がゼロだった場合に別方法でセレクトし、産直ではないリンクも付いてくるのでそれをカット。
最後に、リンク(タグオブジェクト)の配列をsanchoku_infoという関数に渡します。
本当は、sanchoku_infoはもっと上に置かないと「sanchoku_infoってやつ知らないよ、宣言されていないし」と怒られますが、流れ的に説明したいと思ってこの順番にしました。
def sanchoku_info(sanchoku_links):
for link in sanchoku_links:
sanchoku_row = []
sanchoku_url = url + link.get("href")
# go to individual 産直 link
print("going to " + sanchoku_url)
response = requests.get(sanchoku_url)
if(response.status_code != 200):
print("bad status code:")
print(response.status_code)
continue
↑ sanchoku_infoの前半です。個別産直のリンク(タグオブジェクト)をfor loopで一個ずつ情報をとって行きます。
産直の情報がぞれぞれ配列に入ります。その配列が最終的エクセルの一つの行になります。産直リンクの中で404が返ってくるものがあるため、continueを使ってスルーします
sanchoku_soup = bs4.BeautifulSoup(response.content, features="html.parser")
table_headers = sanchoku_soup.select("th")
table_body = sanchoku_soup.find("tbody")
city_name = table_headers[0].getText()
sanchoku_name = table_headers[1].getText()
sanchoku_row.append(city_name)
sanchoku_row.append(sanchoku_name)
table_rows = table_body.select("tr")
for row in table_rows:
cells = row.select("p")
if len(cells)==1 and cells[0].getText()!="ここが自慢!" and cells[0].getText()!="地図":
sanchoku_row.append(cells[0].getText())
elif len(cells)>1 and cells[1].getText()!="地図":
sanchoku_row.append(cells[1].getText())
mega_list.append(sanchoku_row)
↑ sanchoku_info後半です。個別産直ページを見てみましょう。
https://www.pref.miyagi.jp/soshiki/et-sgsin-n/cyokubaijyo-i-jyobonnosatohitakami.html
個別産直の情報もtableに入っていて、thの一個目に市町村名が入っていて、2個目に産直名ですので、それぞれのテキストをとって配列に入れます。
tbodyは、行ずつでみると2個のセルがある場合は2個目のセルに欲しい情報(住所など)が入っているので、そのテキストをゲットして配列に入れます。地図リンクはいらないから配列に入れません。
セルが一つしかない場合は、地図リンクか、タイトルの「ここが自慢!」か、ここが自慢の説明文。説明文以外はいらないので、「ここが自慢」と「地図」のテキストが入っていない場合のみ配列に入れます。
最後に、その一個の産直情報が入っている配列を大きい配列のmega_listに入れます!
for row in mega_list:
ws.append(row)
wb.save(p / "test.xlsx")
↑ プログラムの最後に、mega_listに入っている配列をエクセルシートの行に入れて、エクセルを保存して、完了です!(pは、ホームデレクトリーのpathをpに入れたものです)
エクセルシートはこういう感じ。見づらいですが、地図用なので別にみづらくても大丈夫です。住所、営業時間、主な取り扱い品などが無事に同じ列に入っているのがわかります。
エクセルシートをグーグルマイマップにする流れはこのページなどを参考にしてください〜↓
Googleスプレッドシートに記録した営業記録をGoogleマイマップに取り込む方法
結局住所が住所のセルになかったとかでマイマップでエラーになっているのもありましたが、3個程度で元の産直ページを見ながら手直ししたらマップが完成。
さて、美味しい野菜でも買いに行こうか....