参考ページ
Pythonでスクレイピングした結果をGoogleMapに反映してみた
ココナラの kotorina さん
テーマは?
1.Webアプリの動きを一通り
2.スクレイピング
3.WebAPI
具体的には何を作る?
任意のプレイスリスト(例えば水道事業者一覧) を「スクレイピング(≒HTMLタグの分子分解)」して緯度経度の配列を取得したあとに、GooglemapAPIに緯度経度の配列を投げてGoogleMapにロケーションアイコンを刺す!プレイスというのは下図のしたのほうにリストしてある緯度経度の右の「神奈川県内広域水道企業団」とかそういう「GoogleMapに僕らが検索語を投げるときのようなコトバ」です。
ソース
News!! googlemapが有料になった!?
というよりは支払い登録してからな、ってことか。
(2018/9) Google Mapsの料金体系について
全体図とフローチャート
全体図
フローチャート
(解決)404:JSONファイルが見つからない?
JSONファイルを書きだしたパスをそのままコピペしてJSからそのJSONを読み込もうとして沼。
つまり、、、
「view.py」 は 「'googlemaps/static/googlemaps/js/data.json'」で書き出すが
「jQuery.js」 は 「"/static/googlemaps/js/data.json"」でhtmlが読み込む。
viewで(裏側で)動いてるpythonと、htmlが認識するjsフォルダとでパスの捉え方が違うってことかね...。
view.py がいるディレクトリは(プロジェクト名の)「mysite」だと。runserverコマンドを打つのが mysite だから...なるほどな。ローカルアプリケーションばっか作ってると多分ハマるところだな...。
(解決)JSONが読めない!?非同期ということについて
結果的にJSON読み込み処理(getJSON)が重かっただけで、結局ただ待ってりゃよかった...っていうオチ。「Ajaxは非同期」というのが頭だけでわかってて実際のところはわかっていなかった、ということだね。ローカルアプリケーションで、「読み込んだはずの配列に値が入るのは "まだ" です」なんて状況は基本ないからね、、、。
非同期とはどういうことか がわかりやすいです。
Tips
Ajax
jQuery.ajax({
type: 'GET',
url: '/static/googlemaps/js/data.json',
dataType: 'json',
async: false,
success: function(data){
json = data;
}
});
BeautifulSoup
(図はHTMLの「roomListWrapper」クラスがどこを指しているか、と「roomListWrapper」で findAll して中身を抜き出す例)
import urllib.request
from bs4 import BeautifulSoup
url = 'http://www.ikyu.com/ap/srch/UspW11104.aspx?rc=1&ppc=1&are=210529&pn=1&adc=1&st=1&msf=1&acmap=1'
html = urllib.request.urlopen(url).read()
soup = BeautifulSoup(html)
for link in soup.findAll(class_='roomListWrapper'):
# 部屋名
roomname = link.find(class_ = 'roomNm').text
# 金額
roomprice = link.find(class_ = 'roomPrice').find(class_ = 'small').text.replace('(消費税込 ','').replace(' 円~)','').replace(',','')
# TEST
print(roomname + roomprice)
Django
今回は「googlemap_marker」というフォルダを作成する
インストール
プロジェクトの作成
Django は visualstudio のように「プロジェクト」とその中に複数の「アプリケーション」という単位でモノゴトを管理するようだ。プロジェクト名はとりあえず「mysite」とする。
googlemap_marker> django-admin startproject mysite
プロジェクトフォルダに移動してWebサーバーを動かす
googlemap_marker> cd mysite
googlemap_marker\mysite> python manage.py runserver
Django version 2.1.10, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
HelloWorld!
ブラウザに「localhost」と打ち込むと、Helloworldがきた!
一気にセットアップ
Djangoがdbからデータをひったくる2019 を見てね。
いったん固定値でやるので、モデルの作成だけトバして、、、ってとこね。
テンプレートを編集
・htmlの最初に {% load static %} を忘れるな!
・javascript を読み込むときのパスは {% static 'googlemaps/js/map.js' %} だ
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>GoogleMapsAPI Sample</title>
<!-- jquery -->
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<!-- jsonファイルを読み込んだりする js -->
<script src="{% static 'googlemaps/js/map.js' %}"></script>
</head>
<!-- body の load のタイミングで json を読んでマーキングする -->
<body onload="read_json_to_marking()">
<!-- 地図の生成 -->
<div id="map_canvas" style="width: 500px; height: 500px"></div>
<!-- 取得した緯度経度リストを debug 表示 -->
以下のリストが取得できました
<ul>
{% for j in json %}
<li>{{ j.lat }},{{ j.lng }},{{ j.name }}</li>
{% endfor %}
</ul>
以下のリストは取得できませんでした
<ul>
{% for j in miss %}
<li>{{ j.lat }},{{ j.lng }},{{ j.name }}</li>
{% endfor %}
</ul>
<form>
<p>
<input type="text" name="データ名" value="35.383575,139.344170" />
<input type="button" id="btn" value="マーカー作成" onclick="loadJson()" />
</p>
</form>
</body>
</html>
views.py を編集
JSの編集
・static
・googlemaps
・js
のフォルダは、手で作る必要があります(templateと同じ階位にstaticをつくります)
ソースはgithubを参照 https://github.com/duri0214/Python/tree/master/googlemaps_marker
動作確認
地図は出てくるけど緯度経度が取れないのは多分googleに支払い登録してないせい
googleデバッグコンソールから リンク を追うと、
(Google Maps Platformは、Google Cloud Platformコンソールから管理されるAPIとSDKのセットです。 Google Maps Platformの使用を開始するには、次を行う必要があります)
(請求先アカウントを作成する、プロジェクトを作成する、1つ以上のAPIまたはSDKを有効にします、APIキーを取得、追加、制限する。これらの手順は、クイックスタートを使用するか、GCPコンソールの「Getting Videos and Get Started Procedures」の手順に従って実行できます)
(「GetStarted」のボタンを押して、製品(googlemap)を選んで、請求を有効化してください)
製品の選択
製品いうてもたくさんあるね...
timezoneとか、Roads(道路)、Places(場所)、あぁ~細分化されてるのね。多分 Maps JavaScript API でいいはずなんだけど
プロジェクトの新規作成
APIKeyの作成
APIKeyに制約をかける
localhostのアプリケーションだけが接続できる、ような制約をかけることができます。ただし、意図しないところでapiの結果がもらえなかったりするので、やるならデバッグが完全に終わってからにすること
APIKeyをhtmlに書き込む
{YOUR_API_KEY}のとこね。重要なAPIキーが丸出しだぞ!ってgithub警察とかgoogleからメール来るかもしれないけどしゃあないやん。不正利用の防止はGoogle Cloud Platform側でするしかない。
<head>
<!-- googlemap api -->
<script src="https://maps.googleapis.com/maps/api/js?key={YOUR_API_KEY}" type="text/javascript"></script>
</head>
出てきた!
ケーススタディ
https://console.cloud.google.com/apis/library
それと同時にPlaces API も一緒にONにすると緯度経度を取得するタイミングで 例えば、写真や評価スターや口コミを動的に取得したりできるわけさ。こんだけ取得する練習したら十分でしょ。一応それぞれ独立して動くように作ったのでそのトレードオフとして全部を単純にコピペするとAPIアクセス数が多くなります。一発で取得できる情報はまとめて取得するなどの話はいわゆるチューニングの話になるのでそれぞれでやってくだせぇ
カスタム地図を保存する
カスタム地図を保存するのに必要なAPIは「Maps Static API」
世界中の場所の情報を詳しく知ることができます、だそうです
カスタム地図とは
googlemapを手でイジってズームしたりしてこんなんなったやつのことね
こういう表示にしたい
カードにして表示するための画像やね。サイズを指定して保存できる
get_mapimage
【Python】Google Maps Platformを使ってGoogleマップの画像を作る
一応つぎのセクションで書くAPIのPlacesにも画像の取得はあるんだけどサイズ変更やpngでの一発保存ができない
def get_mapimage(apikey, place, size=(250, 240), img_format='png'):
"""
dependency
----------
Maps Static API
parameters
----------
place: 東京都\n
size: (width, height) 最大 640x640\n
img_format: png(png8), png32, gif, jpg\n
"""
url = 'https://maps.googleapis.com/maps/api/staticmap?center={}' \
'&size={}&zoom=18&format={}&maptype=roadmap&key={}'
lat, lng = get_geo(place)
location = '{},{}'.format(lat, lng)
size_param = '{}x{}'.format(*size)
url = url.format(location, size_param, img_format, apikey)
res = urllib.request.urlopen(url)
if res.code == 200:
file_path = 'googlemaps/static/googlemaps/img/' + '{}.{}'.format(place, img_format[:3])
with open(file_path, 'wb') as file:
file.write(res.read())
レーティングを取得する
レーティングを取得するのに必要なAPIは「Places API」
5段階評価を付けることができるjQuery Ratyプラグインの使い方
get_rating
def get_rating(apikey, place):
'''
dependency
----------
Places API
parameters
----------
place: 東京都
return
------
e.g. 4.5
'''
url = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json?' \
'input={}&inputtype=textquery&fields=rating&key={}'
url = url.format(urllib.parse.quote(place), apikey)
res = urllib.request.urlopen(url)
rating = None
if res.code == 200:
res_json = json.loads(res.read())
if res_json.get('candidates'):
if res_json["candidates"][0].get("rating"):
rating = res_json["candidates"][0]["rating"]
else:
rating = 0
return rating
カスタム地図へのリンクを取得する
カスタム地図へのリンクを取得するのに必要なAPIは「Places API」
get_placeid
def get_placeid(apikey, place):
'''
dependency
----------
Places API
parameters
----------
place: 東京都
return
------
e.g. CmRaAAAARRAYThPn0sTB1aE-Afx0_...
'''
url = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json?' \
'input={}&inputtype=textquery&fields=place_id&key={}'
url = url.format(urllib.parse.quote(place), apikey)
res = urllib.request.urlopen(url)
retvalue = None
if res.code == 200:
res_json = json.loads(res.read())
if res_json.get("candidates"):
retvalue = res_json["candidates"][0].get("place_id")
return retvalue
get_link
def get_link(apikey, place_id):
'''
dependency
----------
Places API
parameters
----------
place_id: ChIJN1t_tDeuEmsRUsoyG83frY4
return
------
e.g. https://maps.google.com/?cid=10281119596374313554
'''
retvalue = '#'
if place_id:
url = 'https://maps.googleapis.com/maps/api/place/details/json?' \
'place_id={}&fields=url&key={}'.format(place_id, apikey)
res = urllib.request.urlopen(url)
if res.code == 200:
res_json = json.loads(res.read())
if res_json.get("result"):
if res_json["result"].get("url"):
retvalue = res_json["result"]["url"]
return retvalue
場所の写真を保存する
場所の写真を保存するのに必要なAPIは「Places API」
get_photoreference
def get_photoreference(apikey, place):
'''
dependency
----------
Places API
parameters
----------
place: 東京都
return
------
e.g. CmRaAAAARRAYThPn0sTB1aE-Afx0_...
'''
url = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json?' \
'input={}&inputtype=textquery&fields=photos&key={}'
url = url.format(urllib.parse.quote(place), apikey)
res = urllib.request.urlopen(url)
retvalue = None
if res.code == 200:
res_json = json.loads(res.read())
if res_json.get("candidates"):
if res_json["candidates"][0].get("photos"):
retvalue = res_json["candidates"][0]["photos"][0].get("photo_reference")
return retvalue
get_picture
def get_picture(apikey, photoreference, file_path, size=(250, 240)):
'''
dependency
----------
Places API
parameters
----------
photoreference: CmRaAAAARRAYThPn0sTB1aE-Afx0_...\n
file_path: .../static/googlemaps/img/東京タワー.png
'''
if photoreference:
url = 'https://maps.googleapis.com/maps/api/place/photo?' \
'maxwidth={}&maxheight={}&photoreference={}&key={}'
url = url.format(*size, photoreference, apikey)
res = urllib.request.urlopen(url)
if res.code == 200:
with open(file_path, 'wb') as file:
file.write(res.read())
口コミを取得する
レーティングを取得するのに必要なAPIは「Places API」
get_reviews
def get_reviews(apikey, place_id):
'''
dependency
----------
Places API
parameters
----------
place_id: ChIJN1t_tDeuEmsRUsoyG83frY4
return
------
json data: {'html_attributions': [], 'result': {'revie ...
'''
retvalue = None
url = 'https://maps.googleapis.com/maps/api/place/details/json?' \
'place_id={}&fields=reviews&key={}'.format(place_id, apikey)
res = urllib.request.urlopen(url)
if res.code == 200:
res_json = json.loads(res.read())
if res_json.get("result"):
if res_json["result"].get("reviews"):
retvalue = res_json["result"]["reviews"]
return retvalue
{
'html_attributions': [],
'result':
{'reviews': [
{'author_name': 'ちもばよ', 'author_url': 'https://www.google.com/maps/contrib/101059595934018229672/reviews', 'language': 'ja', 'profile_photo_url': 'https://lh5.ggpht.com/-bICsraLrREw/AAAAAAAAAAI/AAAAAAAAAAA/vKCJv_SCOR4/s128-c0x00000000-cc-rp-mo-ba5/photo.jpg', 'rating': 4, 'relative_time_description': 'a year ago', 'text': 'お水は美味しい。売上はアフリカ支援に役立つそうです。
', 'time': 1532214527}]},
'status': 'OK'}
CSSでデザイン
で、カードデザインのCSSを適用したりするだけでもう グッ と変わるんよ
絶対にどっかからのコピペじゃなくて基礎から理解することをおすすめする。
これからの改修ポイント
・スクレイピングをかけるURLを任意変更可能にする
・GoogleMapの初期中心位置を任意変更とその直後のマーカーのリセットを可能にする