このチュートリアルでは以下について説明します。
- Googleマップスクレイパーの定義
- それを使って抽出できるデータ
- PythonでGoogleマップのスクレイピングスクリプトを作成する方法
さっそく始めましょう!
Googleマップスクレイパーとは?
Googleマップスクレイパーは、Googleマップからデータを抽出するための専用ツールです。たとえばPythonスクレイピングスクリプトを使用して、マップデータを収集するプロセスを自動化します。このようなスクレイパーで取得したデータは、一般的に市場調査や競合分析などに使用されます。
Googleマップから取得できるデータ
Googleマップから取得できる情報には次のものが含まれます。
- ビジネスの名称:Googleマップに掲載されている事業者またはロケーションの名前。
- 住所:事業者またはロケーションの実際の住所。
- 電話番号:事業者の連絡先電話番号。
- Webサイト:事業者のWebサイトのURL。
- 営業時間:営業時間の開始時刻と終了時刻。
- レビュー:評価や詳細なフィードバックを含むカスタマーレビュー。
- 評価:ユーザーフィードバックに基づく平均星評価。
- 写真:事業者または顧客がアップロードした画像。
PythonでGoogleマップをスクレイピングする方法
このガイド付きセクションでは、GoogleマップをスクレイピングするためのPythonスクリプトの作成方法を学びます。
最終目標は、「イタリアンレストラン」ページのGoogleマップアイテムに含まれるデータを取得することです。
以下の手順に従ってください!
ステップ1:プロジェクトを準備する
始める前に、マシンにPython 3がインストールされていることを確認する必要があります。されていない場合は、それをダウンロードしてインストールし、インストールウィザードに従ってください。
その後、次のコマンドを使用してプロジェクト用のフォルダを作成して、そのフォルダに移動し、その中に仮想環境を作成します。
mkdir google-maps-scraper
cd google-maps-scraper
python -m venv env
google-maps-scraper
ディクショナリは、Python Googleマップスクレイパーのプロジェクトフォルダを表しています。
お好みのPython IDEにプロジェクトフォルダを読み込みます。PyCharm Community EditionまたはPython拡張機能を入れたVisual Studio CodeがあればOKです。
プロジェクトフォルダ内にscraper.py
ファイルを作成します。これがこのプロジェクトの現在のファイル構造です。scraper.py
は現在は空白のPythonスクリプトですが、まもなくスクレイピングロジックが含まれるようになります。
IDEのターミナルで、仮想環境を有効にします。そのためには、LinuxまたはmacOSで以下のコマンドを実行してください。
./env/bin/activate
または、Windowsでは以下を実行してください。
env/Scripts/activate
これでスクレイパー向けのPython環境が整いました!
ステップ2:スクレイピングライブラリを選択する
Googleマップは非常にインタラクティブなプラットフォームであるため、静的サイトか動的サイトかを判断するために時間を費やすのは無意味です。こうした場合、ベストなスクレイピングの方法はブラウザ自動化ツールを使用することです。
このテクノロジーに詳しくない場合は、ブラウザ自動化ツールを使用すると、制御可能なブラウザ環境でWebページのレンダリングや操作を行うことができます。さらに、有効なGoogleマップ検索URLを作成するのは簡単ではありません。この問題を解決する最も簡単な方法は、ブラウザで直接検索を実行することです。
Python向けの最良のブラウザ自動化ツールの1つはSeleniumで、Googleマップをスクレイピングするのに理想的な選択肢です。このタスクで使用する主なライブラリとなるので、インストールする準備をしておいてください!
ステップ3:スクレイピングライブラリのインストールと構成
アクティブなPython仮想環境で、次のコマンドでselenium
pipパッケージを使用してSeleniumをインストールします。
pip install selenium
このツールの使用方法の詳細については、Seleniumを使用したWebスクレイピングに関するチュートリアルに従ってください。
Seleniumをscraper.py
にインポートし、Chromeインスタンスをヘッドレスモードで制御するためにWebDriver
オブジェクトを作成します。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
# to launch Chrome in headless mode
options = Options()
options.add_argument("--headless") # comment it while developing
# create a Chrome web driver instance with the
# specified options
driver = webdriver.Chrome(
service=Service(),
options=options
)
上記のスニペットは、Chromeのブラウザウィンドウをプログラムで制御するためにChrome WebDriver
インスタンスを初期化します。--headless
フラグはChromeをヘッドレスモードで起動するためのオプションです。ヘッドレスモードでは、Chromeはバックグラウンドで起動し、ウィンドウは表示されません。デバッグ時には、この行をコメントアウトしてスクリプトのアクションをリアルタイムで確認できます。
Googleマップのスクレイピングスクリプトの最後の行では、忘れずにWebドライバを閉じてください。
driver.quit()
できました!これで、Googleマップページのスクレイピングを始めるための構成が完了しました。
ステップ4:対象のページに接続する
get()
メソッドを使用してGoogleマップのホームページに接続します。
driver.get("https://www.google.com/maps")
今のところ、scraper.py
には次の行が含まれています。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
# to launch Chrome in headless mode
options = Options()
options.add_argument("--headless") # comment it while developing
# create a Chrome web driver instance with the
# specified options
driver = webdriver.Chrome(
service=Service(),
options=options
)
# connect to the Google Maps home page
driver.get("https://www.google.com/maps")
# scraping logic...
# close the web browser
driver.quit()
それでは、Googleマップのような動的Webサイトのスクレイピングを始めましょう。
ステップ5:GDPR(一般データ保護規則)クッキーダイアログへの対処
注:EU(欧州連合)圏内にお住まいでない方は、このステップをスキップできます。
scraper.py
スクリプトをヘッドモードで実行し、可能であれば最後の行の前にブレークポイントを追加します。これにより、ブラウザウィンドウがすぐに閉じることなく、開いたままで観察できます。EU圏内にお住まいの場合は、以下のように表示されるはずです。
注:「Chromeは自動テストソフトウェアにより制御されています」というメッセージは、ChromeがSeleniumによって正常に制御されていることを確認するものです。
GDPR要件により、GoogleはEU圏内のユーザーにいくつかのクッキーポリシーオプションを表示する必要があります。これに該当する場合、ページの操作を行なうためには、この選択を処理する必要があります。該当しない場合は、ステップ6に進んでください。
ブラウザのアドレスバーのURLを見てみると、get()
で指定されたページと一致していないことがわかります。その理由は、Googleがあなたをリダイレクトしたからです。[すべて承認]ボタンをクリックすると、対象となるページ、つまりGoogleマップのホームページに戻ります。
GDPRオプションを処理するには、Googleマップのホームページをブラウザでシークレットモードで開き、リダイレクトされるまでお待ちください。「すべて承認」ボタンを右クリックし、「調査」オプションを選択します。
お気づきかもしれませんが、ページ上のHTML要素のCSSクラスはランダムに生成されているように見えます。これは、それらがWebスクレイピングには不向きであることを示しています。というのも、それらは展開のたびに更新される可能性が高いからです。そのため、aria-label
のような、より安定した属性をターゲットに絞る必要があります。
accept_button = driver.find_element(By.CSS_SELECTOR, "[aria-label=\"Accept all\"]")
find_element()
は、さまざまな戦略を用いてページ上のHTML要素を特定するために使用されるSeleniumのメソッドです。このケースでは、CSSセレクターを使用しました。さまざまなセレクタータイプについて詳しく知りたい場合は、「XPathとCSSセレクター」を参照してください。
このインポートをscraper.py
に追加してBy
をインポートすることをお忘れなく。
from selenium.webdriver.common.by import By
次の指示はボタンをクリックするよう指示しています。
accept_button.click()
以下は、オプションのGoogleクッキーページを処理するにあたり、すべての要素がどう連携しているかを示しています。
try:
# select the "Accept all" button from the GDPR cookie option page
accept_button = driver.find_element(By.CSS_SELECTOR, "[aria-label=\"Accept all\"]")
# click it
accept_button.click()
except NoSuchElementException:
print("No GDPR requirenments")
click()
コマンドで「すべて承認」ボタンを押すと、GoogleによりGoogleマップのホームページにリダイレクトされます。EU圏外にお住まいの場合、このボタンはページに表示されないため、NoSuchElementException
が発生します。これは重大なエラーではなく、潜在的なシナリオにすぎないため、スクリプトは例外をキャッチして処理を進めます。
必ずNoSuchElementException
をインポートしてください。
from selenium.common import NoSuchElementException
お疲れ様でした!これで、Googleマップのスクレイピングに集中するための準備ができました。
ステップ6:検索フォームを送信する
現在、Googleマップスクレイパーは、以下のようなページに到達しているはずです。
Googleマップ上のロケーションは、IPのロケーションによって異なることに注意してください。この例では、所在地はニューヨークです。
次に、「Googleマップを検索」フィールドに入力し、検索フォームを送信してみましょう。この要素を見つけるには、ブラウザのシークレットモードでGoogleマップのホームページを開きます。検索入力フィールドを右クリックし、「調査」オプションを選択します。
search_input = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#searchboxinput"))
)
WebDriverWait
は特殊なSeleniumクラスで、ページ上で指定された条件が満たされるまでスクリプトを一時停止します。上の例では、入力されたHTML要素が表示されるまで最大5秒待機します。この待機は、(リダイレクトを理由として)ステップ5に従った場合に必要となる、ページの完全な読み込みを保証します。
上記の行を機能させるには、これらのインポートをscraper.py
に追加してください。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
Next, fill out the input with the `[send_keys()](https://www.selenium.dev/documentation/webdriver/actions_api/keyboard/#send-keys)` method:
search_query = "italian restaurants"
search_input.send_keys(search_query)
この場合、「イタリアンレストラン」が検索クエリですが、他の用語でも検索できます。
あとはフォームを送信するだけです。虫めがねの形をした送信ボタンを調査します。
送信ボタンのaria-label
属性をターゲットにしてボタンを選択し、クリックしてください。
search_button = driver.find_element(By.CSS_SELECTOR, "button[aria-label=\"Search\"]")
search_button.click()
素晴らしい!これで、制御されたブラウザがスクレイピング対象のデータを読み込むようになります。
ステップ7:Googleマップアイテムを選択する
スクリプトは、現在以下の場所にあるはずです。
スクレイピング対象のデータは、左側のGoogleマップアイテムに含まれています。これはリストであるため、スクレイピングされたデータを格納するのに最適なデータ構造は配列です。1つを初期化:
items = []
ここでの目標は、左側のGoogleマップアイテムを選択することです。そのうちの1つを調査してください。
繰り返しになりますが、CSSクラスはランダムに生成されているように見えるため、スクレイピングには適していません。代わりに、jsaction
属性をターゲットにするといいでしょう。この属性のコンテンツの一部もランダムに生成されて表示されるので、その中の一貫した文字列、具体的には"mouseover:pane"
に注目してください。
以下のXPathセレクタは、role="feed"
であり、そのjsaction
属性に"mouseover:pane"
という文字列が含まれる親
要素を選択するのに役立ちます。
maps_items = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.XPATH, '//div[@role="feed"]//div[contains(@jsaction, "mouseover:pane")]'))
)
繰り返しになりますが、左側のコンテンツは動的にページに読み込まれるため、WebDriverWait
が必要です。
各要素を繰り返し処理し、Googleマップスクレイパーでデータを抽出できるように準備します。
for maps_item in maps_items:
# scraping logic...
素晴らしい!次のステップでは、これらの要素からデータを抽出します。
ステップ8:Googleマップのアイテムを削除する
Googleマップのアイテムを1つ調べて、その中の要素に注目してください。
ここでは、スクレイプできることが分かります。
[jsaction][jslog]
要素からのGoogleマップアイテムのリンクdiv.FontheadlinesMall
要素からのタイトルspan[role="img"]からの星とレビューの数
これは以下のロジックで実現できます。
link_element = maps_item.find_element(By.CSS_SELECTOR, "a[jsaction][jslog]")
url = link_element.get_attribute("href")
title_element = maps_item.find_element(By.CSS_SELECTOR, "div.fontHeadlineSmall")
title = title_element.text
reviews_element = maps_item.find_element(By.CSS_SELECTOR, "span[role=\"img\"]")
reviews_string = reviews_element.get_attribute("aria-label")
# define a regular expression pattern to extract the stars and reviews count
reviews_string_pattern = r"(\d+\.\d+) stars (\d+[,]*\d+) Reviews"
# use re.match to find the matching groups
reviews_string_match = re.match(reviews_string_pattern, reviews_string)
reviews_stars = None
reviews_count = None
# if a match is found, extract the data
if reviews_string_match:
# convert stars to float
reviews_stars = float(reviews_string_match.group(1))
# convert reviews count to integer
reviews_count = int(reviews_string_match.group(2).replace(",", ""))
get_attribute()
関数は指定されたHTML属性内のコンテンツを返し、.text
はノード内の文字列コンテンツを返します。
“X.Y stars in Z reviews”という文字列から特定のデータフィールドを抽出するために、正規表現が使用されていることに注目してください。詳細については、「Webスクレイピングの正規表現」の使用についての記事を参照してください。
Python標準ライブラリから忘れずにre
パッケージをインポートしてください。
import re
Googleマップアイテムを引き続き調査します。
fondBodyMedium
クラスを持つ
style
属性のみを持つ
ノードから、ほとんどの情報を取得できます。オプションの価格設定要素については、aria-label
属性に“Price”を含むノードをターゲットにすることで選択できます。info_div = maps_item.find_element(By.CSS_SELECTOR, ".fontBodyMedium")
# scrape the price, if present
try:
price_element = info_div.find_element(By.XPATH, ".//*[@aria-label[contains(., 'Price')]]")
price = price_element.text
except NoSuchElementException:
price = None
info = []
# select all <span> elements with no attributes or the @style attribute
# and descendant of a <span>
span_elements = info_div.find_elements(By.XPATH, ".//span[not(@*) or @style][not(descendant::span)]")
for span_element in span_elements:
info.append(span_element.text.replace("⋅", "").strip())
# to remove any duplicate info and empty strings
info = list(filter(None, list(set(info))))
価格要素はオプションなので、そのコードをtry...except
ブロックで囲む必要があります。これにより、価格ノードがページ上にない場合でも、スクリプトは失敗することなく続行されます。ステップ5をスキップする場合は、NoSuchElementException
のインポートを追加してください。
from selenium.common import NoSuchElementException
空の文字列や重複した情報要素を避けるため、filter()
とset()
の使用に注意してください。
では、今度は画像にフォーカスを当てます。
次のコードでデータを抽出できます。
img_element = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "img[decoding=\"async\"][aria-hidden=\"true\"]"))
)
image = img_element.get_attribute("src")
画像は非同期で読み込まれ、表示されるまでに時間がかかる場合があるため、WebDriverWait
が必要であることに注意してください。
最後のステップは、最下部の要素のタグをスクレイピングすることです。
最後の.fontBodyMedium
要素のstyle
属性を持つノードから、それらすべてを取得できます。
tags_div = maps_item.find_elements(By.CSS_SELECTOR, ".fontBodyMedium")[-1]
tags = []
tag_elements = tags_div.find_elements(By.CSS_SELECTOR, "span[style]")
for tag_element in tag_elements:
tags.append(tag_element.text)
お見事!PythonのGoogleマップスクレイピングロジックが完成しました。
ステップ9:スクレイピングされたデータを収集する
これで、いくつかの変数にスクレイピングしたデータが格納されました。新しいアイテム
オブジェクトを作成し、そこにそのデータを入力します。
item = {
"url": url,
"image": image,
"title": title,
"reviews": {
"stars": reviews_stars,
"count": reviews_count
},
"price": price,
"info": info,
"tags": tags
}
次に、それをアイテム
配列に追加します。
items.append(item)
Googleマップのアイテムノードのfor
ループの終了時には、アイテム
にすべてのスクレイピングデータが含まれます。その情報をCSVなどの人間が読めるファイルにエクスポートするだけで済みます。
ステップ10:CSVにエクスポートする
Python標準ライブラリからcsv
パッケージをインポートします。
import csv
次に、これを使用してフラットなCSVファイルにGoogleマップデータを入力します。
# output CSV file path
output_file = "items.csv"
# flatten and export to CSV
with open(output_file, mode="w", newline="", encoding="utf-8") as csv_file:
# define the CSV field names
fieldnames = ["url", "image", "title", "reviews_stars", "reviews_count", "price", "info", "tags"]
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
# write the header
writer.writeheader()
# write each item, flattening info and tags
for item in items:
writer.writerow({
"url": item["url"],
"image": item["image"],
"title": item["title"],
"reviews_stars": item["reviews"]["stars"],
"reviews_count": item["reviews"]["count"],
"price": item["price"],
"info": "; ".join(item["info"]),
"tags": "; ".join(item["tags"])
})
上記のスニペットはアイテム
をitems.csv
という名前のCSVファイルにエクスポートします。使用されている主な関数は以下のとおりです。
open()
:指定されたファイルをUTF-8エンコーディングの書き込みモードで開き、テキスト出力を処理します。csv.dictWriter()
:指定されたフィールド名を使用してCSVライターオブジェクトを作成すると、行をディクショナリとして書き込めるようになります。writeheader()
:フィールド名に基づいてヘッダー行をCSVファイルに書き込みます。writer.writerow()
:各項目をCSVに行として書き込みます。
join()
文字列関数を使用して配列をフラット文字列に変換していることに注意してください。これにより、出力CSVはクリーンな単一レベルのファイルになります。
ステップ11:仕上げ
PythonのGoogleマップスクレイパーの最終コードは次のとおりです。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.common import NoSuchElementException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import re
import csv
# to launch Chrome in headless mode
options = Options()
options.add_argument("--headless") # comment it while developing
# create a Chrome web driver instance with the
# specified options
driver = webdriver.Chrome(
service=Service(),
options=options
)
# connect to the Google Maps home page
driver.get("https://www.google.com/maps")
# to deal with the option GDPR options
try:
# select the "Accept all" button from the GDPR cookie option page
accept_button = driver.find_element(By.CSS_SELECTOR, "[aria-label=\"Accept all\"]")
# click it
accept_button.click()
except NoSuchElementException:
print("No GDPR requirenments")
# select the search input and fill it in
search_input = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#searchboxinput"))
)
search_query = "italian restaurants"
search_input.send_keys(search_query)
# submit the search form
search_button = driver.find_element(By.CSS_SELECTOR, "button[aria-label=\"Search\"]")
search_button.click()
# where to store the scraped data
items = []
# select the Google Maps items
maps_items = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.XPATH, '//div[@role="feed"]//div[contains(@jsaction, "mouseover:pane")]'))
)
# iterate over the Google Maps items and
# perform the scraping logic
for maps_item in maps_items:
link_element = maps_item.find_element(By.CSS_SELECTOR, "a[jsaction][jslog]")
url = link_element.get_attribute("href")
title_element = maps_item.find_element(By.CSS_SELECTOR, "div.fontHeadlineSmall")
title = title_element.text
reviews_element = maps_item.find_element(By.CSS_SELECTOR, "span[role=\"img\"]")
reviews_string = reviews_element.get_attribute("aria-label")
# define a regular expression pattern to extract the stars and reviews count
reviews_string_pattern = r"(\d+\.\d+) stars (\d+[,]*\d+) Reviews"
# use re.match to find the matching groups
reviews_string_match = re.match(reviews_string_pattern, reviews_string)
reviews_stars = None
reviews_count = None
# if a match is found, extract the data
if reviews_string_match:
# convert stars to float
reviews_stars = float(reviews_string_match.group(1))
# convert reviews count to integer
reviews_count = int(reviews_string_match.group(2).replace(",", ""))
# select the Google Maps item <div> with most info
# and extract data from it
info_div = maps_item.find_element(By.CSS_SELECTOR, ".fontBodyMedium")
# scrape the price, if present
try:
price_element = info_div.find_element(By.XPATH, ".//*[@aria-label[contains(., 'Price')]]")
price = price_element.text
except NoSuchElementException:
price = None
info = []
# select all <span> elements with no attributes or the @style attribute
# and descendant of a <span>
span_elements = info_div.find_elements(By.XPATH, ".//span[not(@*) or @style][not(descendant::span)]")
for span_element in span_elements:
info.append(span_element.text.replace("⋅", "").strip())
# to remove any duplicate info and empty strings
info = list(filter(None, list(set(info))))
img_element = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "img[decoding=\"async\"][aria-hidden=\"true\"]"))
)
image = img_element.get_attribute("src")
# select the tag <div> element and extract data from it
tags_div = maps_item.find_elements(By.CSS_SELECTOR, ".fontBodyMedium")[-1]
tags = []
tag_elements = tags_div.find_elements(By.CSS_SELECTOR, "span[style]")
for tag_element in tag_elements:
tags.append(tag_element.text)
# populate a new item with the scraped data
item = {
"url": url,
"image": image,
"title": title,
"reviews": {
"stars": reviews_stars,
"count": reviews_count
},
"price": price,
"info": info,
"tags": tags
}
# add it to the list of scraped data
items.append(item)
# output CSV file path
output_file = "items.csv"
# flatten and export to CSV
with open(output_file, mode="w", newline="", encoding="utf-8") as csv_file:
# define the CSV field names
fieldnames = ["url", "image", "title", "reviews_stars", "reviews_count", "price", "info", "tags"]
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
# write the header
writer.writeheader()
# write each item, flattening info and tags
for item in items:
writer.writerow({
"url": item["url"],
"image": item["image"],
"title": item["title"],
"reviews_stars": item["reviews"]["stars"],
"reviews_count": item["reviews"]["count"],
"price": item["price"],
"info": "; ".join(item["info"]),
"tags": "; ".join(item["tags"])
})
# close the web browser
driver.quit()
約150行のコードで、Googleマップのスクレイピングスクリプトを作成できました。
scraper.py
ファイルを起動して、正しく動作することを確認してください。Windowsでは、以下のコマンドでスクレイパーを起動します。
python scraper.py
同様に、LinuxまたはmacOSでは、以下を実行してください。
python3 scraper.py
スクレイパーの実行が終了するのを待つと、プロジェクトのルートディレクトリにitems.csv
ファイルが表示されます。ファイルを開いて抽出されたデータを表示します。このデータには次のようなデータが含まれているはずです。
おめでとうございます、ミッション完了です!
まとめ
このチュートリアルでは、Googleマップスクレイパーの概要とPythonでの作成方法を学びました。ここで見てきたように、Googleマップからデータを自動的に取得するシンプルなスクリプトを構築するには、Pythonコードを数行書くだけで済みます。
このソリューションは小規模なプロジェクトでは機能しますが、大規模なスクレイピングには実用的ではありません。Googleでは、ユーザーをブロックできるCAPTCHAやIP禁止などの高度なボット対策を講じています。プロセスを複数のページにまたがって拡張すると、インフラストラクチャのコストも増加します。さらに、この単純な例では、Googleマップページで必要な複雑な操作をすべて網羅しているわけではありません。
それはつまり、Googleを効率的かつ確実にスクレイピングすることは不可能だということでしょうか?そんなことはありません!必要なのはBright DataのGoogleマップスクレイパーAPIのような高度なソリューションだけです。
Google Maps Scraper APIは、Googleマップからデータを取得するためのエンドポイントを提供しており、主な課題をすべて解決します。簡単なAPI呼び出しで、必要なデータをJSONまたはHTML形式で取得できます。API呼び出しがお好みでない場合は、すぐに使えるGoogleマップデータセットもご覧ください。
今すぐ無料のBright Dataアカウントを作成して、スクレイパーAPIをお試しいただくか、当社のデータセットをじっくりご覧ください。
クレジットカードは必要ありません