美團外賣採用 **前後端分離架構**,頁面資料由 **AJAX/Fetch 非同步請求 API** 獲取,直接 `requests.get()` 只能拿到空殼 HTML (含 JS 打包檔),無商家資料。
| 關鍵項 | 說明 |
|---|---|
| Request URL | 真實 API 端點 (如 `https://apim.waimai.meituan.com/poi/list`) |
| Method | 通常 POST (有時 GET) |
| Headers | 必須包含:`User-Agent`、 `Referer`、 `Origin`、 `Cookie` (含 `token`、`uuid`、`w_utm_*` 等)、`Content-Type: application/json` |
| Payload (Body) | 查詢參數:`latitude`、 `longitude`、 `keyword`、 `page`、 `limit`、 `cateId`、 `uuid` (裝置指紋) 等 |
| Response | JSON 結構:`{data: {poiList: [...], ...}, code: 0, msg: ''}` |
右鍵請求 → `Copy` → `Copy as cURL (bash)` → 用 curlconverter 轉 Python `requests` 程式碼。
import requests
import json
import time
import random
# 必須從瀏覽器複製的完整 Headers (含 Cookie)
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Referer': 'https://waimai.meituan.com/',
'Origin': 'https://waimai.meituan.com',
'Content-Type': 'application/json',
# 'Cookie': 'w_utm_...=...; token=...; uuid=...; ...', # 必須填入你的有效 Cookie
'Accept': 'application/json, text/plain, */*',
}
API_URL = 'https://apim.waimai.meituan.com/poi/list' # 實際以抓包為準
def fetch_poi_list(lat, lng, keyword='', page=1, limit=20):
payload = {
"latitude": lat,
"longitude": lng,
"keyword": keyword,
"page": page,
"limit": limit,
"cateId": 0,
"uuid": "your-device-uuid", # 從 Cookie 或 localStorage 獲取
"platform": 3,
"partner": 4,
"riskLevel": 1,
}
try:
resp = requests.post(API_URL, headers=HEADERS, json=payload, timeout=10)
resp.raise_for_status()
data = resp.json()
if data.get('code') == 0:
return data['data']['poiList']
else:
print(f"API 錯誤: {data.get('msg')}")
return []
except Exception as e:
print(f"請求失敗: {e}")
return []
def main():
# 北京北太平莊大致座標 (需精確)
lat, lng = 39.985, 116.345
all_poi = []
for page in range(1, 6): # 爬前 5 頁
print(f"正在爬取第 {page} 頁...")
poi_list = fetch_poi_list(lat, lng, page=page)
if not poi_list:
break
all_poi.extend(poi_list)
time.sleep(random.uniform(1, 3)) # 禮貌延遲
print(f"共獲取 {len(all_poi)} 家商家")
# 儲存 JSON
with open('meituan_poi.json', 'w', encoding='utf-8') as f:
json.dump(all_poi, f, ensure_ascii=False, indent=2)
if __name__ == '__main__':
main()
| 反爬機制 | 應對方案 |
|---|---|
| Header/Referer/Origin 檢查 | 完整複製瀏覽器請求 Headers |
| Cookie/Token 驗證 | 定期手動更新 Cookie、或用 Selenium/Playwright 登入獲取 |
| 簽名/加密參數 (sign, _token) | 逆向分析 JS 加密邏輯 (通常在 `vendor.xxx.js`、`app.xxx.js`)、或用 Selenium 執行 JS 獲取 |
| IP 限流/封禁 | 代理 IP 池、降低頻率、隨機延遲 |
| 裝置指紋 | 模擬 `uuid`、`deviceId`、Canvas 指紋 (極難完美模擬) |
| 行為分析 (鼠標軌跡、點擊節奏) | 使用 Playwright / Selenium / DrissionPage 驅動真實瀏覽器 |
from DrissionPage import ChromiumPage
page = ChromiumPage()
page.get('https://waimai.meituan.com/home/...') # 目標頁面
# 等待資料載入 (監聽網路請求)
packet = page.listen.wait('poi/list') # 監聽 API
data = packet.response.body
print(json.dumps(data, ensure_ascii=False, indent=2))
page.quit()
⚠ 美團 `robots.txt` 通常禁止爬蟲。大規模、高頻率爬取可能觸犯《刑法》第 285 條 (非法獲取計算機信息系統數據罪)、**反不正當競爭法**。僅供技術學習,**嚴禁商業用途、大規模採集、繞過驗證碼/登入**。請遵守《網絡安全法》、《數據安全法》、《個人信息保護法》。
爬取美團外賣核心在於 **找到真實 AJAX API + 完整模擬瀏覽器請求 Headers/Body/Cookie**。`requests` 適用於輕度反爬;重度反爬 (簽名、指紋、行為分析) **必須用 DrissionPage/Playwright 驅動真實瀏覽器**。永遠優先考慮**官方開放平台 API** (商家開放平台) 合規獲取資料。