做航班延误分析,我们一直缺天气这个维度。风速、降水、气温都跟延误强相关,但数据散在外部接口里。Tableau Prep 没有原生的 REST 输入连接器,这件事拖了挺久。
上周试出一条路:Tableau Prep 的「脚本」步骤走 TabPy(Python 分析扩展),能跑任意 Python。只要在脚本里写几行 requests,就能把外部 API 的数据变成一张表,接着在 Tableau Prep 里清洗、Join。下面是我们踩完坑跑通的记录。
Tableau Prep 脚本步骤怎么吐数据
Tableau Prep 从 2019.3.1 起支持 Python 脚本步骤。数据从上游流进脚本,脚本返回一张 DataFrame 流回去。官方文档有个硬约束:脚本步骤不能当流程的第一个步骤,前面必须有数据喂进来,calculation(df) 必须吃一个 df。
想凭空拉一个 API,得先找个占位输入当引子。
最小验证:占位种子拉北京天气
先做一个能跑通的最小版本,证明链路成立。
seed.csv 放一行占位数据,内容无所谓,只为让脚本步骤有上游:
seed
1脚本里直接忽略 df,拉北京当前天气。跑通后返回一张天气表,价值不在于拉到了什么,而在于证明「Tableau Prep 脚本步骤 + TabPy 能拉外部 API 并返回表」这条链路成立。链路一通,后面都是扩展。
Tableau Prep 里接法:输入选 seed.csv → 加「脚本」步骤,连接类型选「Tableau Python (TabPy) 服务器」,Host 填 127.0.0.1、Port 9004,函数名 calculation。
这版要验证的就是Tableau Prep 能直接调外部API。calculation 里其实没读传入的 df,直接拿北京坐标拉 Open-Meteo,返回成功、解析成表。代价是 Prep 不能让脚本步骤凭空起头,得先垫一张占位表当上游——多了这一步,麻烦点,但路是通的。下边的多城市版直接用 df 里的真实坐标循环拉取,就更顺手了。
多城市:把数据喂进 df
占位只是验证。真要多个城市,把城市数据放进上游输入,用 df 传进函数,函数里逐行循环。
seed.csv:
city,latitude,longitude
北京,39.9042,116.4074
上海,31.2304,121.4737
广州,23.1291,113.2644
深圳,22.5431,114.0579
成都,30.5728,104.0668
杭州,30.2741,120.1551
武汉,30.5928,114.3055
西安,34.3416,108.9398
重庆,29.4316,106.9123
哈尔滨,45.8038,126.5344
乌鲁木齐,43.8256,87.6168
拉萨,29.6520,91.1721脚本改成真正用 df:
import pandas as pd
import requests
BASE_URL = "https://api.open-meteo.com/v1/forecast"
CURRENT_FIELDS = (
"temperature_2m,relative_humidity_2m,wind_speed_10m,"
"wind_direction_10m,weather_code,is_day"
)
def _fetch_weather(lat, lon):
params = {
"latitude": lat,
"longitude": lon,
"current": CURRENT_FIELDS,
"timezone": "Asia/Shanghai",
}
resp = requests.get(BASE_URL, params=params, timeout=30)
resp.raise_for_status()
return resp.json()
def calculation(df):
rows = []
for _, r in df.iterrows():
name = str(r.get("city", ""))
lat = float(r["latitude"])
lon = float(r["longitude"])
j = _fetch_weather(lat, lon)
c = j["current"]
rows.append({
"city": name,
"latitude": j.get("latitude"),
"longitude": j.get("longitude"),
"obs_time": c.get("time"),
"temperature": c.get("temperature_2m"),
"humidity": c.get("relative_humidity_2m"),
"windspeed": c.get("wind_speed_10m"),
"winddir": c.get("wind_direction_10m"),
"weathercode": c.get("weather_code"),
"is_day": c.get("is_day"),
})
return pd.DataFrame(rows)
def get_output_schema():
return pd.DataFrame({
"city": prep_string(),
"latitude": prep_decimal(),
"longitude": prep_decimal(),
"obs_time": prep_string(),
"temperature": prep_decimal(),
"humidity": prep_decimal(),
"windspeed": prep_decimal(),
"winddir": prep_int(),
"weathercode": prep_int(),
"is_day": prep_int(),
})加城市只改 seed.csv 加一行,脚本不动。以后接延误分析,把这一层换成「机场 → 经纬度」表,脚本原样复用。我们本地实测 12 城全部拉通,北京时间 16:00 那次,北京 33.8°C、上海 29.5°C、拉萨 17.9°C。
几个真坑
TabPy 装完命令找不到。 pip3 install tabpy 之后打 tabpy 报 command not found,因为它装到 ~/Library/Python/3.9/bin,不在 PATH。用全路径启动,或把目录加进 ~/.zshrc 的 PATH。启动后默认监听 localhost:9004,终端窗口别关。
Tableau Prep 连不上 TabPy。 报错「请输入有效的服务器配置」,根因几乎都是 Host 填法:Host 框只认主机名,把 http://127.0.0.1:9004 整串塞进去会被判无效。分开填:Host 127.0.0.1、Port 9004。用 127.0.0.1 不用 localhost,能绕开 Mac 上 localhost 优先解析 IPv6 的坑。不勾 SSL,不填账号密码。
Open-Meteo 只认经纬度。 forecast 接口不吃城市名,必须给 latitude/longitude,所以上游表得带这两列。
时区要显式写。 不传 timezone,接口默认返 UTC,和航班表的北京时间差 8 小时,Join 必错。加上 timezone=Asia/Shanghai,返回的就是北京时间。
参数用新的 `current=`. 旧的 current_weather=true 还能用,但官方已转向 current=字段列表,返回更全。新流程直接上新的。
能复用到哪
这套骨架不限于天气。任何 Tableau Prep 没原生连接器、但能 HTTP 取到的数据源,汇率、股价、航班状态、内部 REST 接口,都能用同一个模子接:占位种子(或真实输入表)+ TabPy 脚本 requests 拉数 + get_output_schema 声明字段。
参考文档
• Tableau 帮助:在流程中使用 R 和 Python 脚本(官方文档)
No comments yet