はじめに
2025年11月18日、Googleが公開した Google Antigravity は、cursorのようなAIによるコード生成機能に加え、AIエージェントが「人間のように」Chromeを直接操作し、Web上での調査や、開発中のアプリの動作確認を自律的に行うBrowser Agentという機能を持っています。
さらに Agent Manager という管理画面で、複数のエージェントを並列で動かすこともできます。(ただしローカル上。)
そのAntigravity上で、12月18日に公開されたばかりのGemini 3.0 Flashが使えるようになったので、組み合わせた使い勝手をレポします。
作ったもの
某ECサイトで特定の商品の値動きを調べるプログラムです。
※こちらの価格はテストのために勝手にスプシ上で書き換えておりますので、実際の金額とは異なっております。ご了承ください。
- 動作環境:
- MacBook Air 2022 ( CPU : Apple M2 )
- Google Antigravity v1.13.3
- Gemini 3.0 Flash
- 個人用 Google アカウント
- Google スプレッドシート
- Google cloud
- Google Sheets APIを利用するためのサービスアカウント発行
- 1日あたり 1,000,000 リクエストまで無料
- Google Sheets APIを利用するためのサービスアカウント発行
- Discord
- 個人サーバー
※Mac以外は全部無料!
作り方
Antigravity の Agent Viewで、Gemini 3.0 Flashに日本語で指示しただけ。
Browser Agent機能を使い、chromeを操作して、Amazonで特定の商品の価格監視をしたいです。商品はこれ です https://www.amazon.co.jp/dp/B08TJM7N9D/
アーキテクチャを示してください。
結果をスプレッドシートに書き出すように修正してください。
金額が前日より安くなった場合に、DiscordにDMするように修正してください。
プロンプト実行時に、AntigravityがAmazonの該当ページを開き、HTMLを読み取っている様子が表示されました。

Gemini 3.0 Flashが作成したコードは大筋問題なし。異常系のエラーハンドリングの追加は必要でしたが、とても賢いと思います。
import os
import requests
import json
import datetime
import time
from dotenv import load_dotenv
from playwright.sync_api import sync_playwright
import gspread
from oauth2client.service_account import ServiceAccountCredentials
# .envファイルの読み込み
load_dotenv()
DISCORD_WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL")
SPREADSHEET_ID = os.getenv("SPREADSHEET_ID")
PRODUCT_URL = os.getenv("PRODUCT_URL")
def get_amazon_price(url):
with sync_playwright() as p:
# ブラウザの起動(ユーザーエージェントを設定してスクレイピング対策)
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
)
page = context.new_page()
print(f"Accessing {url}...")
page.goto(url, wait_until="domcontentloaded")
# 商品名と価格の取得
product_name = page.locator("#productTitle").inner_text().strip()
# カンマなどを除いた数値のみを取得
price_text = page.locator(".a-price .a-offscreen").first.inner_text().replace("¥", "").replace(",", "").strip()
try:
price = int(price_text)
except ValueError:
print(f"Failed to parse price: {price_text}")
price = None
browser.close()
return product_name, price
def update_spreadsheet(product_name, price):
# Google Sheets APIの認証
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
# GitHub Actions等の環境変数から認証情報を取得する場合
google_creds_json = os.getenv("GOOGLE_CREDENTIALS")
if google_creds_json:
with open("credentials.json", "w") as f:
f.write(google_creds_json)
if not os.path.exists("credentials.json"):
raise FileNotFoundError("credentials.json not found. Please provide it or set GOOGLE_CREDENTIALS env var.")
creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
client = gspread.authorize(creds)
# スプレッドシートを開く
sheet = client.open_by_key(SPREADSHEET_ID).sheet1
# 前回の価格を取得(最後の行)
records = sheet.get_all_values()
last_price = None
if len(records) > 1:
last_row = records[-1]
try:
last_price = int(last_row[2]) # 3列目が価格と想定
except (ValueError, IndexError):
pass
# データの追加
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sheet.append_row([now, product_name, price])
print(f"Saved to sheet: {now}, {price}")
return last_price
def send_discord_notification(product_name, current_price, last_price):
if DISCORD_WEBHOOK_URL == "your_webhook_url_here" or not DISCORD_WEBHOOK_URL:
print("Discord Webhook URL is not set.")
return
message = f"📉 **価格が下がりました!**\n\n**商品**: {product_name}\n**現在の価格**: ¥{current_price:,}\n**前回の価格**: ¥{last_price:,}\n**URL**: {PRODUCT_URL}"
payload = {"content": message}
response = requests.post(DISCORD_WEBHOOK_URL, json=payload)
if response.status_code == 204:
print("Notification sent to Discord.")
else:
print(f"Failed to send notification: {response.status_code}")
import traceback
def main():
if not SPREADSHEET_ID or not PRODUCT_URL:
print("SPREADSHEET_ID or PRODUCT_URL is missing in .env")
return
try:
product_name, price = get_amazon_price(PRODUCT_URL)
if price is None:
print("Could not retrieve price.")
return
print(f"Current Price: ¥{price:,}")
last_price = update_spreadsheet(product_name, price)
# 価格が安くなった場合に通知
if last_price and price < last_price:
print(f"Price dropped from ¥{last_price:,} to ¥{price:,}! Sending notification...")
send_discord_notification(product_name, price, last_price)
else:
print("No price drop or first entry.")
except Exception as e:
print(f"Error occurred: {e}")
traceback.print_exc()
if __name__ == "__main__":
main()
# Discord Webhook URL (Discordのチャンネル設定から取得)
DISCORD_WEBHOOK_URL=your_webhook_url_here
# Google Spreadsheet ID (URLの /d/ と /edit の間の文字列)
SPREADSHEET_ID=your_spreadsheet_id_here
# Target Amazon URL
PRODUCT_URL=https://www.amazon.co.jp/dp/B08TJM7N9D/
Agent Manager上で動かすためのワークフロー化するための設定。
このワークフローを実行すると、
ローカル環境(Mac)でAmazonの価格チェックを行い、
結果をスプレッドシートに保存します。
// turbo
1. スクリプトを実行
`python3 price_monitor.py`
アカウント作成や実装についての質問にも、丁寧に答えてくれました。
Discordでの通知設定とGoogle Sheets APIの準備方法を教えて
Google Sheets API用の credentials.jsonの取得方法を教えて
credentials.jsonの取得はGoogle Cloudのダッシュボードからやるので、そっちのアカウントを持ってない人はやや煩雑かもしれませんが、提示された内容どおりやればできるはず。
エラー発生時の修正もばっちり
以下のエラーを対策して
Error occurred: Page.goto: Timeout 30000ms exceeded. Call log:
navigating to "https://www.amazon.co.jp/dp/B08TJM7N9D/", waiting until "networkidle"
上述のとおり、実行したら異常系のエラーが出たので、エラーメッセージを食わせる〜改修まで2〜3回修正指示をしました。コードを全く書く事なく1時間程度で完成しました。
気づき
当初私はAgent ManagerはManusのような時間起動ができるAIエージェント管理ツールだと思っていたので、それができないとわかった時にだいぶガッカリしました。
そう伝えたところ、Gemini3.0 Flashは以下のように回答しました。
Antigravity の Browser Agent機能 が最も真価を発揮したのは、
実はこの開発の 「調査フェーズ」 でした。
1. セレクタの特定: Amazon のような複雑なサイトで、
どの CSS セレクタ(.a-price .a-offscreen など)を使えば
確実に価格が取れるかを、実際にブラウザを動かして検証しました。
2. 回避策の確認: 人間と同じようにブラウザを操作することで、
どんな対策(ロボット除け)があるかを事前に確認し、自動実行用の
スクリプトに反映させることができました。
つまり、「Antigravity で賢いスクリプトをサクッと作り、
GitHub Actions で放っておいても動くようにした」 という、
非常に効率的なフローだったと言えます!
使い分けは確かにありですね。
最後に、Antigravityの良かった点とのびしろをまとめて終わりにします。
-
Antigravityの良い点:
-
Antigravityの残念な点:
- 今の所、会社のGoogleアカウントでは利用できない。(パブリックプレビュー版だかららしい)
-
Agent Managerは、コマンド一発でジョブを起動できて便利ではあるが、時間指定はできない。ジョブ実行の時間指定がしたいなら、Macのlaunchdから呼び出したり、cronが実行できる環境にデプロイして動かす必要あり。 - あくまでも開発->検証の場という感じ。それに使えば全然OK。
現場からは以上です!



