LoginSignup
16
18

スクレイピングした検索語リストでGoogleMapから検索できたら地図をマーキングして周辺地図を保存してレーティングとクチコミを取得して表示するメモ2020

Last updated at Posted at 2016-08-23

参考ページ

Pythonでスクレイピングした結果をGoogleMapに反映してみた
ココナラの kotorina さん

テーマは?

1.Webアプリの動きを一通り
2.スクレイピング
3.WebAPI

具体的には何を作る?

任意のプレイスリスト(例えば水道事業者一覧) を「スクレイピング(≒HTMLタグの分子分解)」して緯度経度の配列を取得したあとに、GooglemapAPIに緯度経度の配列を投げてGoogleMapにロケーションアイコンを刺す!プレイスというのは下図のしたのほうにリストしてある緯度経度の右の「神奈川県内広域水道企業団」とかそういう「GoogleMapに僕らが検索語を投げるときのようなコトバ」です。
image

ソース

News!! googlemapが有料になった!?

というよりは支払い登録してからな、ってことか。
(2018/9) Google Mapsの料金体系について

全体図とフローチャート

全体図

image.png

フローチャート

(解決)404:JSONファイルが見つからない?

JSONファイルを書きだしたパスをそのままコピペしてJSからそのJSONを読み込もうとして沼。
image
つまり、、、
「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は非同期」というのが頭だけでわかってて実際のところはわかっていなかった、ということだね。ローカルアプリケーションで、「読み込んだはずの配列に値が入るのは "まだ" です」なんて状況は基本ないからね、、、。
非同期とはどういうことか がわかりやすいです。
image

Tips

Ajax

ajaxsample.js
    jQuery.ajax({
        type: 'GET',
        url: '/static/googlemaps/js/data.json',
        dataType: 'json',
        async: false,
        success: function(data){
            json = data;
        }
    });

BeautifulSoup

(図はHTMLの「roomListWrapper」クラスがどこを指しているか、と「roomListWrapper」で findAll して中身を抜き出す例)
image

soupsample
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」とする。

VSCode_Console
googlemap_marker> django-admin startproject mysite

プロジェクトフォルダに移動してWebサーバーを動かす

VSCode_Console
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がきた!
image.png

一気にセットアップ

Djangoがdbからデータをひったくる2019 を見てね。
いったん固定値でやるので、モデルの作成だけトバして、、、ってとこね。

テンプレートを編集

・htmlの最初に {% load static %} を忘れるな!
・javascript を読み込むときのパスは {% static 'googlemaps/js/map.js' %} だ

index.html
{% 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
image.png

動作確認

地図は出てくるけど緯度経度が取れないのは多分googleに支払い登録してないせい
image.png

googleデバッグコンソールから リンク を追うと、
image.png
(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 でいいはずなんだけど
image.png
image.png

プロジェクトの新規作成

image.png
image.png
image.png
image.png

APIKeyの作成

image.png
image.png

APIKeyに制約をかける

localhostのアプリケーションだけが接続できる、ような制約をかけることができます。ただし、意図しないところでapiの結果がもらえなかったりするので、やるならデバッグが完全に終わってからにすること

image.png

APIKeyをhtmlに書き込む

{YOUR_API_KEY}のとこね。重要なAPIキーが丸出しだぞ!ってgithub警察とかgoogleからメール来るかもしれないけどしゃあないやん。不正利用の防止はGoogle Cloud Platform側でするしかない。

index.html
<head>
    <!-- googlemap api -->
    <script src="https://maps.googleapis.com/maps/api/js?key={YOUR_API_KEY}" type="text/javascript"></script>
</head>

出てきた!

image.png

ケーススタディ

https://console.cloud.google.com/apis/library
それと同時にPlaces API も一緒にONにすると緯度経度を取得するタイミングで 例えば、写真や評価スターや口コミを動的に取得したりできるわけさ。こんだけ取得する練習したら十分でしょ。一応それぞれ独立して動くように作ったのでそのトレードオフとして全部を単純にコピペするとAPIアクセス数が多くなります。一発で取得できる情報はまとめて取得するなどの話はいわゆるチューニングの話になるのでそれぞれでやってくだせぇ

カスタム地図を保存する

カスタム地図を保存するのに必要なAPIは「Maps Static API」
世界中の場所の情報を詳しく知ることができます、だそうです

カスタム地図とは

googlemapを手でイジってズームしたりしてこんなんなったやつのことね
image.png

こういう表示にしたい

カードにして表示するための画像やね。サイズを指定して保存できる
image.png

get_mapimage

【Python】Google Maps Platformを使ってGoogleマップの画像を作る
一応つぎのセクションで書くAPIのPlacesにも画像の取得はあるんだけどサイズ変更やpngでの一発保存ができない

get_mapimage
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

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

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

get_link(事前にget_placeidを実行してplace_idを取得する必要がある)
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」
image.png

get_photoreference

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

get_picture(事前にget_photoreferenceを実行してphotoreferenceを取得する必要がある)
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

get_reviews(事前にget_placeidを実行してplace_idを取得する必要がある)
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
返却値のjsonイメージ(複数のレビューが返ってきたときにデザインが壊れるからどうするか考えてたけど面倒になったからデザインにまでは落とし込まないことにした)
{
'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を適用したりするだけでもう グッ と変わるんよ
絶対にどっかからのコピペじゃなくて基礎から理解することをおすすめする。
image.png

これからの改修ポイント

・スクレイピングをかけるURLを任意変更可能にする
・GoogleMapの初期中心位置を任意変更とその直後のマーカーのリセットを可能にする

16
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
18