なぜWebサイト監視ツールを自作すべきなのか?
大事な会議の15分間に、iPhoneが300万ドンも安くなっていたのに、F5キーでページを更新できず買い逃してしまったことがあります。その時、手動で価格やニュースをチェックし続けるのは非常に労力がかかり、挫折しやすい作業だと痛感しました。
Distill.ioやVisualpingのようなツールは優秀ですが、無料版は月に30回程度のチェック制限があることが多いです。5分おきに継続してチェックしたい場合、月に15ドル〜25ドルほどかかることもあります。Pythonを使えば、広告フィルタリングをカスタマイズした高機能なクローンを自作でき、完全に無料でTelegramに通知を送ることができます。
多くの人はデータのスクレイピングにBeautifulSoupを使っています。しかし、ReactやVueを使用している現代的なWebサイトでは、BeautifulSoupだと真っ白なHTMLフレームしか返ってこないことがよくあります。そこで今回はPlaywrightを採用します。このライブラリは本物のブラウザをシミュレートし、JavaScriptのレンダリングが完了してからデータを取得するため、難易度の高いWebサイトでも処理が可能です。
環境構築
まず、Python 3.8以上がインストールされていることを確認してください。ブラウジング用にPlaywright、Telegram APIとの連携用にrequestsライブラリを使用します。
# ライブラリの衝突を避けるために仮想環境を作成
python -m venv venv
source venv/bin/activate # Windowsの場合: venv\Scripts\activate
# Playwrightとrequestsをインストール
pip install playwright requests
# Playwright用のChromiumブラウザをダウンロード(約100-150MB)
playwright install chromium
Telegramについては、@BotFatherとチャットし、/newbotと入力してAPI Tokenを取得してください。ボットに一度メッセージを送信してから、https://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesにアクセスして、自分のchat_idを確認するのを忘れないでください。
コードの実装詳細
スクリプトは「アクセス -> 抽出 -> 比較 -> 通知」というモデルで動作します。
1. Playwrightによるデータ取得
HTML全体を取得するのではなく、特定のSelector(価格のクラスやタイトルのIDなど)を直接指定することをお勧めします。これにより、タイムスタンプや広告バナーが変わっただけで通知が飛んでしまうのを防げます。
import asyncio
from playwright.async_api import async_playwright
async def get_website_content(url, selector):
async with async_playwright() as p:
# ヘッドレズモードで実行(ブラウザウィンドウを表示しない)
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
# アンチボットシステムによるブロックを避けるためにUser-Agentを偽装
await page.set_extra_http_headers({"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0"})
try:
await page.goto(url, wait_until="networkidle", timeout=60000)
await page.wait_for_selector(selector)
content = await page.inner_text(selector)
return content.strip()
except Exception as e:
print(f"アクセスエラー: {e}")
return None
finally:
await browser.close()
2. コンテンツ変更のチェック
更新があったかどうかを判断する最も簡単な方法は、前回の実行結果を.txtファイルに保存しておくことです。新しい内容が古い内容と異なる場合、ファイルを更新して通知をトリガーします。
import os
def has_changed(new_content, filename="last_state.txt"):
if not os.path.exists(filename):
with open(filename, "w", encoding="utf-8") as f:
f.write(new_content)
return False
with open(filename, "r", encoding="utf-8") as f:
old_content = f.read()
if new_content != old_content:
with open(filename, "w", encoding="utf-8") as f:
f.write(new_content)
return True
return False
3. Telegram経由で通知を送信
requestsライブラリを使えば、数行のコードでスマートフォンに即座に通知をプッシュできます。
import requests
def notify_telegram(message, token, chat_id):
url = f"https://api.telegram.org/bot{token}/sendMessage"
data = {"chat_id": chat_id, "text": message, "parse_mode": "HTML"}
return requests.post(url, data=data)
各パーツの統合
以下はプロセス全体を動かすmain関数です。実際のURLとSelectorに書き換えるだけで、すぐに実行できます。
async def main():
CONFIG = {
"url": "https://tiki.vn/dien-thoai-iphone-15-pro-max-p251643444.html",
"selector": ".price-and-icon",
"token": "YOUR_TOKEN",
"chat_id": "YOUR_ID"
}
print("Webサイトをチェック中...")
current_data = await get_website_content(CONFIG["url"], CONFIG["selector"])
if current_data and has_changed(current_data):
msg = f"🚀 <b>新しい更新があります!</b>\n値: {current_data}\nリンク: {CONFIG['url']}"
notify_telegram(msg, CONFIG["token"], CONFIG["chat_id"])
print("通知を送信しました!")
else:
print("変更はありません。")
if __name__ == "__main__":
asyncio.run(main())
運用上のヒント
スクリプトを安定して動作させ、IP制限を避けるために、以下の3点に注意してください。
- チェック頻度: 1〜2秒ごとのチェックは絶対に避けてください。ShopeeやAmazonのような大手サイトでは、すぐにIPがブロックされます。15〜30分周期が最も妥当な数値です。
- 自動実行: VPSをお持ちの場合は
crontabを使用してください。そうでない場合は、GitHub Actionsを活用すれば、個人のPCをつけっぱなしにすることなく、24時間365日無料でスクリプトを実行できます。 - 動的クラスの処理: 一部のWebサイトでは、ビルドのたびにクラス名が頻繁に変わります。その場合は、
xpathやdata-testidのような固定属性を使用して、要素をより正確に特定してください。
監視ボットを自作することは、お得な情報を手に入れるだけでなく、ブラウザの動作フローや実際のデータ処理を理解するための素晴らしい練習になります。ぜひ挑戦してみてください!

