
Alibabacloud Iqs Weather Query
- 61 installs
- 208 repo stars
- Updated August 4, 2026
- aliyun/alibabacloud-aiops-skills
alibabacloud-iqs-weather-query is a Claude skill that returns 7-day city weather forecasts by combining Alibaba Cloud IQS web search (UnifiedSearch) with page reading (ReadPageBasic).
About
This skill returns a 7-day weather forecast for any city using Alibaba Cloud IQS web search (UnifiedSearch) and page reading (ReadPageBasic). It searches for the best weather page, then parses known sites with dedicated parsers into structured JSON or returns raw text for the agent to interpret on unknown sites. It runs via node scripts/weather.mjs and requires an ALIYUN_IQS_API_KEY.
- Returns 7-day weather forecasts for any city using Alibaba Cloud IQS web search and page reading
- Uses UnifiedSearch to find weather pages and ReadPageBasic to parse them
- Hybrid parsing: dedicated parsers for known sites (weather.cma.cn, weather.com.cn) or raw text for unknown sites
Alibabacloud Iqs Weather Query by the numbers
- 61 all-time installs (skills.sh)
- +7 installs in the week ending Jun 23, 2026 (Skillselion tracking)
- Ranked #991 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
alibabacloud-iqs-weather-query capabilities & compatibility
- Works with
- weather
- Use cases
- web search · web scraping
- Pricing
- Bring your own API key
What alibabacloud-iqs-weather-query says it does
7-day weather forecast query powered by Alibaba Cloud IQS web search and page reading.
Query 7-day weather forecasts for any city using Alibaba Cloud IQS web search (UnifiedSearch) and page reading (ReadPageBasic) capabilities.
npx skills add https://github.com/aliyun/alibabacloud-aiops-skills --skill alibabacloud-iqs-weather-queryAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 61 |
|---|---|
| repo stars | ★ 208 |
| Last updated | August 4, 2026 |
| Repository | aliyun/alibabacloud-aiops-skills ↗ |
What it does
Query a 7-day weather forecast for any city via Alibaba Cloud IQS web search and page reading.
Who is it for?
Agents that need a structured 7-day weather forecast for a named city via Alibaba Cloud IQS.
Skip if: Historical weather, non-weather search, or forecasts beyond 7 days.
When should I use this skill?
The user asks for a weather forecast, 7-day weather, or whether it will rain in a specific city.
By the numbers
- 7-day weather forecast
- 3 priority sites with dedicated parsers plus raw fallback
Files
IQS Weather Query - 7-Day Weather Forecast
Query 7-day weather forecasts for any city using Alibaba Cloud IQS web search (UnifiedSearch) and page reading (ReadPageBasic) capabilities.
Underlying Service: alibabacloud-iqs-search
Hybrid Parsing Strategy:
- Known sites (weather.cma.cn, weather.com.cn): Dedicated parsers extract structured JSON →
parseMode: "structured" - Unknown sites: ReadPage extracts main content (readabilityMode: article), returns raw text with extraction hint →
parseMode: "raw", agent (LLM) interprets directly
| Output Field | Description |
|---|---|
| parseMode | "structured" (parsed JSON) or "raw" (text for agent) |
| weather | Weather condition (sunny, cloudy, rain, etc.) |
| temperature | Temperature range |
| windSpeed | Wind speed/level |
| windDirection | Wind direction |
---
Environment Configuration
Pre-check: ALIYUN_IQS_API_KEY Required
>
```bash
if [ -z "$ALIYUN_IQS_API_KEY" ]; then echo "API Key 未配置"; else echo "API Key 已配置(已脱敏)"; fi
```
If output is 'API Key 未配置', the API Key is not configured.
>
How to obtain ALIYUN_IQS_API_KEY: Please refer to Aliyun IQS Documentation
>
Configure environment variable (choose one):
>
Option 1: Temporary (current terminal session only)
```bash
export ALIYUN_IQS_API_KEY="your-api-key-here"
```
>
Option 2: Permanent (recommended)
>
Add to ~/.zshrc or ~/.bashrc:
```bash
export ALIYUN_IQS_API_KEY="your-api-key-here"
```
Runsource ~/.zshrcorsource ~/.bashrcto apply.
>
Alternative: Place API Key in ~/.alibabacloud/iqs/env file:```
ALIYUN_IQS_API_KEY=your-api-key-here
```
---
Workflow
User Input (city name)
│
▼
┌─────────────────────────┐
│ Step 1: UnifiedSearch │ Search: "{city} 天气预报 未来7天"
│ (Web Search) │ Priority: weather.cma.cn > weather.com.cn
└──────────┬──────────────┘
│ Best weather URL
▼
┌─────────────────────────┐
│ Step 2: ReadPageBasic │ Known site → readabilityMode: normal
│ (Page Reading) │ Unknown site → readabilityMode: article
└──────────┬──────────────┘
│ Page content
▼
Known site?
╱ ╲
YES NO
│ │
▼ ▼
Parser Return rawText
Router + hint for agent
│ (parseMode: raw)
▼
Structured JSON
(parseMode: structured)---
Usage
Prerequisites
- Node.js >= 18 (native
fetchsupport required) - No additional npm dependencies needed
Execute Query
node scripts/weather.mjs <city>Examples:
node scripts/weather.mjs 北京
node scripts/weather.mjs 上海
node scripts/weather.mjs 杭州
node scripts/weather.mjs TokyoOutput Format
Structured mode (known sites — parsed successfully):
{
"success": true,
"data": {
"city": "北京",
"parseMode": "structured",
"queryTime": "2026-03-26T10:00:00.000Z",
"forecastDays": 7,
"forecast": [
{
"date": "3月26日",
"weather": "晴",
"temperature": "5°C ~ 18°C",
"windDirection": "北风",
"windSpeed": "3-4级"
}
],
"source": "https://weather.cma.cn/..."
}
}Raw mode (unknown sites — agent interprets the text):
{
"success": true,
"data": {
"city": "北京",
"parseMode": "raw",
"hint": "以下是北京天气网页的正文内容,请从中提取未来7天的天气预报信息...",
"rawText": "北京天气预报\n今天 晴 18°C/5°C ...",
"evolveHint": "[持续进化] 当前站点 \"example.com\" 没有匹配的解析器...",
"source": "https://example.com/weather/beijing"
}
}---
IQS APIs Used
| API | Endpoint | Purpose | Documentation |
|---|---|---|---|
| UnifiedSearch | cloud-iqs.aliyuncs.com/search/unified | Web search for weather pages | Doc |
| ReadPageBasic | cloud-iqs.aliyuncs.com/readpage/scrape | Read and parse weather webpage | Doc |
Search Priority Sites (with dedicated parsers): 1. weather.cma.cn (China Meteorological Administration) — parseCmaWeather → readabilityMode: normal 2. baidu.weather.com.cn / sq.weather.com.cn (China Weather Network mobile) — parseBaiduWeatherComCn → readabilityMode: normal 3. weather.com.cn (China Weather Network, all other subdomains) — parseWeatherComCn → readabilityMode: normal 4. Other sites — no parser, return raw text → readabilityMode: article (cleaner body extraction)
---
Error Handling
| Error Code | Description | Solution |
|---|---|---|
| CREDENTIALS_MISSING | API Key not configured | Set ALIYUN_IQS_API_KEY environment variable |
| NO_RESULTS | No weather results found | Check city name is correct |
| QUERY_FAILED | API call failed | Check network, verify credentials |
| PARSE_FAILED | Failed to parse weather data from webpage | The target site's page structure may have changed; update the corresponding parser or add a new one (see Extending section) |
---
Best Practices
1. Use Chinese city names for domestic weather queries for better accuracy 2. Node.js >= 18 required - native fetch API is used, no npm install needed 3. Cache awareness - ReadPage uses maxAge not set, always fetches latest data 4. Extensible parsers - Each weather site has its own parser; add new sites by registering in PARSER_REGISTRY
---
Continuous Evolution
This skill is designed to self-improve over time. When the script encounters an unknown site format (no matching parser), it returns parseMode: "raw" with an evolveHint field.
Agent behavior when `parseMode` is `"raw"`:
1. First: Answer the user — Extract weather info from rawText using LLM understanding and respond to the user immediately. 2. Then: Evolve — Read the evolveHint and analyze the rawText format pattern, then write a new parser function and register it in PARSER_REGISTRY (see Extending section below). This ensures that next time the same site format is hit, it goes through structured mode directly.
First query for unknown site:
search → readpage → no parser match → raw mode → agent answers user
→ agent writes new parser ← evolveHint
Subsequent queries for same site:
search → readpage → parser match → structured mode → direct JSON outputKey: The skill gets smarter with every new city searched. Each raw-mode encounter is an opportunity to add a new parser, progressively covering more site formats.
---
Extending: Add a New Weather Site Parser
The script uses a Parser Registry pattern. Each weather site has its own dedicated parser function, and the router automatically dispatches based on URL. To add support for a new site, follow these 3 steps:
Step 1: Write a Parser Function
Add a new parser function in scripts/weather.mjs. It must accept (content, city) and return the standard format:
function parseMyNewSite(content, city) {
const forecast = [];
// Parse the text content from ReadPage for this specific site
// Extract: date, weather, temperature, windDirection, windSpeed
// ...your parsing logic here...
return {
city,
queryTime: new Date().toISOString(),
forecastDays: Math.min(forecast.length, 7),
forecast: forecast.slice(0, 7),
raw: forecast.length === 0 ? content.substring(0, 2000) : undefined,
};
}Return format for each forecast item:
| Field | Type | Example |
|---|---|---|
| date | string | "04/07 星期二" |
| weather | string | "晴转多云" |
| temperature | string | "5°C ~ 18°C" |
| windDirection | string | "北风" |
| windSpeed | string | "3-4级" |
Step 2: Register in PARSER_REGISTRY
Add your parser to the registry array at the top of weather.mjs. Order matters — higher position = higher priority:
const PARSER_REGISTRY = [
{ pattern: 'weather.cma.cn', parser: parseCmaWeather },
{ pattern: 'baidu.weather.com.cn', parser: parseBaiduWeatherComCn },
{ pattern: 'sq.weather.com.cn', parser: parseBaiduWeatherComCn },
{ pattern: 'weather.com.cn', parser: parseWeatherComCn },
{ pattern: 'mynewsite.com', parser: parseMyNewSite }, // ← Add here
];Step 3: Add to Search Priority (optional)
If you want the new site to be prioritized in search results, add it to PREFERRED_WEATHER_SITES:
const PREFERRED_WEATHER_SITES = [
'weather.cma.cn',
'weather.com.cn',
'mynewsite.com', // ← Add here
];How the Router Works
parseWeatherData(content, city, url)
│
├─ URL contains "weather.cma.cn"? → parseCmaWeather(content, city)
├─ URL contains "baidu.weather.com.cn"? → parseBaiduWeatherComCn(content, city)
├─ URL contains "sq.weather.com.cn"? → parseBaiduWeatherComCn(content, city)
├─ URL contains "weather.com.cn"? → parseWeatherComCn(content, city)
├─ URL contains "mynewsite.com"? → parseMyNewSite(content, city)
│
└─ No match or result < 3 days? → return rawText + hint (agent interprets)Tip: Usenode -e "..."with the ReadPage API to fetch and inspect the raw text format of a new site before writing the parser. See existing parsers (parseCmaWeather,parseWeatherComCn) as reference implementations.
#!/usr/bin/env node
/**
* IQS Weather Query - 基于阿里云IQS的7天天气预报查询
*
* 流程:
* 1. 调用 UnifiedSearch 搜索天气网页 URL(优先 weather.cma.cn)
* 2. 调用 ReadPageBasic 读取天气网页正文
* 3. 已知站点 → 预设解析器 → 结构化 JSON(parseMode: structured)
* 未知站点 → 返回正文原文 + hint → 交由 agent(LLM) 理解(parseMode: raw)
*
* 用法:
* node weather.mjs <城市名称>
*/
import fs from 'fs';
import path from 'path';
import os from 'os';
const SEARCH_ENDPOINT = 'https://cloud-iqs.aliyuncs.com/search/unified';
const READPAGE_ENDPOINT = 'https://cloud-iqs.aliyuncs.com/readpage/scrape';
// 优先搜索的天气网站(按优先级排序)
const PREFERRED_WEATHER_SITES = [
'weather.cma.cn', // 中央气象台(优先)
'weather.com.cn', // 中国天气网(备选,匹配所有子域名)
];
/**
* 从环境变量或配置文件加载 API Key
* @returns {string|null}
*/
function loadApiKey() {
if (process.env.ALIYUN_IQS_API_KEY) {
return process.env.ALIYUN_IQS_API_KEY;
}
try {
const configPath = path.join(os.homedir(), '.alibabacloud', 'iqs', 'env');
const content = fs.readFileSync(configPath, 'utf-8');
const match = content.match(/^ALIYUN_IQS_API_KEY=(.+)$/m);
return match ? match[1].trim() : null;
} catch {
// 配置文件不存在或不可读
return null;
}
}
/**
* 调用 UnifiedSearch 搜索天气信息
* @param {string} apiKey - API Key
* @param {string} city - 城市名称
* @returns {Promise<Object>} 搜索结果
*/
async function searchWeather(apiKey, city) {
const body = {
query: `${city} 天气预报 未来7天`,
engineType: 'Generic',
timeRange: 'NoLimit',
contents: {
mainText: true,
summary: false
}
};
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000);
try {
const response = await fetch(SEARCH_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
'User-Agent': 'AlibabaCloud-Agent-Skills/alibabacloud-iqs-weather-query',
'x-iqs-source': 'skill.alibabacloud-iqs-weather-query'
},
body: JSON.stringify(body),
signal: controller.signal
});
clearTimeout(timeoutId);
const data = await response.json();
if (data.errorCode) {
throw new Error(`${data.errorCode}: ${data.errorMessage}`);
}
return data;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('搜索请求超时 (15s)');
}
throw error;
}
}
/**
* 从搜索结果中筛选最优天气网页 URL
* @param {Object} searchResult - 搜索结果
* @returns {string|null} 最优天气网页 URL
*/
function findBestWeatherUrl(searchResult) {
const pageItems = searchResult?.pageItems || [];
if (pageItems.length === 0) {
return null;
}
// 优先查找中央气象台等权威天气网站
for (const site of PREFERRED_WEATHER_SITES) {
const match = pageItems.find(item =>
item.link && item.link.includes(site)
);
if (match) {
return match.link;
}
}
// 查找包含"天气"关键词的结果
const weatherResult = pageItems.find(item =>
item.title && (item.title.includes('天气') || item.title.includes('weather'))
);
if (weatherResult) {
return weatherResult.link;
}
// 返回第一个结果
return pageItems[0]?.link || null;
}
/**
* 判断 URL 是否匹配某个已知站点解析器
*/
function isKnownSite(url) {
return PARSER_REGISTRY.some(({ pattern }) => url.includes(pattern));
}
/**
* 调用 ReadPage 读取网页内容
* @param {string} apiKey - API Key
* @param {string} url - 网页 URL
* @returns {Promise<Object>} 网页解析结果
*/
async function readPage(apiKey, url) {
// 已知站点用 normal 保留更多结构,未知站点用 article 精简正文
const mode = isKnownSite(url) ? 'normal' : 'article';
const body = {
url: url,
formats: ['text'],
timeout: 60000,
pageTimeout: 15000,
stealthMode: 0,
readability: {
readabilityMode: mode
}
};
// 客户端侧超时保护:服务端 timeout(60s) + pageTimeout(15s) + 15s 网络缓冲 = 90s
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 90000);
try {
const response = await fetch(READPAGE_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
'User-Agent': 'AlibabaCloud-Agent-Skills/alibabacloud-iqs-weather-query',
'x-iqs-source': 'skill.alibabacloud-iqs-weather-query'
},
body: JSON.stringify(body),
signal: controller.signal
});
clearTimeout(timeoutId);
const data = await response.json();
if (data.errorCode) {
throw new Error(`${data.errorCode}: ${data.errorMessage}`);
}
return {
title: data.data?.metadata?.title,
url: data.data?.metadata?.url,
text: data.data?.text,
statusCode: data.data?.statusCode
};
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('网页读取请求超时 (90s)');
}
throw error;
}
}
// ============================================================
// 解析器注册表:URL 模式 → 解析函数
// 新增站点只需:1.写解析函数 2.在此注册
// ============================================================
const PARSER_REGISTRY = [
{ pattern: 'weather.cma.cn', parser: parseCmaWeather },
{ pattern: 'baidu.weather.com.cn', parser: parseBaiduWeatherComCn },
{ pattern: 'sq.weather.com.cn', parser: parseBaiduWeatherComCn },
{ pattern: 'weather.com.cn', parser: parseWeatherComCn },
];
/**
* 解析器路由:根据 URL 匹配对应解析函数
* - 已知站点:使用预设解析器,返回结构化 JSON
* - 未知站点 / 解析失败:返回 rawText + hint,交由 agent(LLM) 直接理解
*/
function parseWeatherData(content, city, url) {
if (!content) {
return { city, forecast: [], parseMode: 'empty' };
}
// 尝试匹配已知站点解析器
for (const { pattern, parser } of PARSER_REGISTRY) {
if (url && url.includes(pattern)) {
const result = parser(content, city);
if (result.forecast.length >= 3) {
result.parseMode = 'structured';
return result;
}
}
}
// 未知站点或解析失败 → 返回正文原文,交由 agent 理解
// 同时输出 evolveHint,引导 agent 为该站点编写新解析器
const urlPattern = url ? new URL(url).hostname : 'unknown';
return {
city,
queryTime: new Date().toISOString(),
parseMode: 'raw',
hint: `以下是${city}天气网页的正文内容,请从中提取未来7天的天气预报信息,包括:日期、天气状况、温度范围、风向、风力等级。如果信息不完整,请尽量提取可用部分。`,
rawText: content.substring(0, 4000),
evolveHint: `[持续进化] 当前站点 "${urlPattern}" 没有匹配的解析器,走了 raw 模式。请在回答用户后,分析 rawText 的格式规律,为该站点编写一个新的解析器函数并注册到 scripts/weather.mjs 的 PARSER_REGISTRY 中。参考现有解析器 parseCmaWeather / parseWeatherComCn 的实现方式。解析器函数签名: function parseXxx(content, city) → { city, queryTime, forecastDays, forecast: [{date, weather, temperature, windDirection, windSpeed}] }`,
};
}
// ============================================================
// 解析器1: 中央气象台 weather.cma.cn
// 格式: 每天10行 — 星期、日期、白天天气、白天风向、白天风力、
// 最高温、最低温、夜间天气、夜间风向、夜间风力
// ============================================================
function parseCmaWeather(content, city) {
const forecast = [];
try {
const lines = content.split('\n').map(l => l.trim()).filter(l => l);
let startIdx = lines.findIndex(l => l.includes('7天天气预报'));
if (startIdx === -1) {
return { city, forecast: [] };
}
startIdx += 1;
const dayBlocks = [];
for (let i = startIdx; i < lines.length; i++) {
if (/^星期[一二三四五六日]/.test(lines[i])) {
const block = [];
for (let j = i; j < Math.min(i + 12, lines.length); j++) {
if (j > i && (/^星期[一二三四五六日]/.test(lines[j]) || lines[j].startsWith('时间'))) break;
block.push(lines[j]);
}
dayBlocks.push(block);
}
if (lines[i].startsWith('时间') && lines[i].includes('|')) break;
}
for (const block of dayBlocks) {
if (block.length < 6) continue;
const weekday = block[0].trim();
const date = block[1]?.trim() || '';
const dayWeather = block[2]?.trim() || '';
const dayWindDir = block[3]?.trim() || '';
const dayWindSpeed = block[4]?.trim() || '';
const highTemp = block[5]?.replace(/[^\d.-]/g, '') || '';
const lowTemp = block[6]?.replace(/[^\d.-]/g, '') || '';
const nightWeather = block.length > 7 ? block[7]?.trim() : '';
let weather = dayWeather;
if (nightWeather && nightWeather !== dayWeather && !/风/.test(nightWeather)) {
weather = `${dayWeather}转${nightWeather}`;
}
let temperature = '';
if (highTemp && lowTemp) {
temperature = `${lowTemp}°C ~ ${highTemp}°C`;
} else if (highTemp) {
temperature = `${highTemp}°C`;
}
forecast.push({
date: `${date} ${weekday}`,
weather,
temperature,
windDirection: dayWindDir,
windSpeed: dayWindSpeed,
});
}
} catch (e) {
// 解析失败返回空
}
return {
city,
queryTime: new Date().toISOString(),
forecastDays: Math.min(forecast.length, 7),
forecast: forecast.slice(0, 7),
raw: forecast.length === 0 ? content.substring(0, 2000) : undefined,
};
}
// ============================================================
// 解析器2: 中国天气网 weather.com.cn (所有子域名)
// 支持两种格式:
// A) 15天预报页: 列表标记 "* 日期\n周X" + Raphaël 图表温度
// B) 7天预报页: "# 7日(今天)\n多云\n22/15℃\n<3级"
// ============================================================
function parseWeatherComCn(content, city) {
const forecast = [];
try {
const lines = content.split('\n').map(l => l.trim().replace(/^\*\s*/, '').trim());
// ---- 格式B: www 桌面版 7天预报 ----
// 特征: "# 7日(今天)" / "# 8日(明天)" / "# 9日(后天)" / "# 10日(周五)"
const format7day = [];
for (let i = 0; i < lines.length; i++) {
const m = lines[i].match(/^#\s*(\d{1,2})日((.+?))$/);
if (m) {
const date = `${m[1]}日`;
const label = m[2]; // 今天/明天/后天/周X
const weather = (i + 1 < lines.length) ? lines[i + 1] : '';
const tempLine = (i + 2 < lines.length) ? lines[i + 2] : '';
const windLine = (i + 3 < lines.length) ? lines[i + 3] : '';
const tempMatch = tempLine.match(/(-?\d+)\s*\/\s*(-?\d+)℃/);
let temperature = '';
if (tempMatch) {
temperature = `${tempMatch[2]}°C ~ ${tempMatch[1]}°C`;
}
format7day.push({
date: `${date} ${label}`,
weather,
temperature,
windSpeed: windLine.includes('级') ? windLine : '',
windDirection: ''
});
}
}
if (format7day.length >= 3) {
forecast.push(...format7day.slice(0, 7));
} else {
// ---- 格式A: 15天预报页 ----
const dates = [];
const weekdays = [];
for (let i = 0; i < lines.length; i++) {
const dateMatch = lines[i].match(/^(\d{1,2}日)$/);
if (dateMatch) {
dates.push(dateMatch[1]);
if (i + 1 < lines.length && /^周[一二三四五六日]$/.test(lines[i + 1])) {
weekdays.push(lines[i + 1]);
} else {
weekdays.push('');
}
}
}
const weatherWords = '晴|多云|阴|小雨|中雨|大雨|暴雨|雷阵雨|小雪|中雪|大雪|雨夹雪|雾|霾|阵雨|雷雨|雨|雪';
const weatherPattern = new RegExp(`^(${weatherWords})(转(${weatherWords}))?$`);
const weathers = [];
const winds = [];
for (let i = 0; i < lines.length; i++) {
if (weatherPattern.test(lines[i])) {
weathers.push(lines[i]);
if (i + 1 < lines.length && lines[i + 1].includes('级')) {
winds.push(lines[i + 1]);
} else {
winds.push('');
}
}
}
const tempLines = content.match(/Created with Raphaël[\s\S]*?(\d+°C[\d°C]*)/g) || [];
let highTemps = [];
let lowTemps = [];
for (let idx = 0; idx < tempLines.length; idx++) {
const cleaned = tempLines[idx].replace(/Created with Raphaël\s*[\d.]+/, '');
const temps = cleaned.match(/(\d+)°C/g)?.map(t => parseInt(t.replace('°C', ''))) || [];
if (idx === 0) highTemps = temps;
else if (idx === 1) lowTemps = temps;
}
const count = Math.min(dates.length, 7);
if (count > 0 && (weathers.length > 0 || highTemps.length > 0)) {
for (let i = 0; i < count; i++) {
const high = highTemps[i] !== undefined ? highTemps[i] : '';
const low = lowTemps[i] !== undefined ? lowTemps[i] : '';
let temperature = '';
if (high !== '' && low !== '') {
temperature = `${low}°C ~ ${high}°C`;
} else if (high !== '') {
temperature = `${high}°C`;
}
forecast.push({
date: dates[i] + (weekdays[i] ? ` ${weekdays[i]}` : ''),
weather: weathers[i] || '',
temperature,
windSpeed: winds[i] || '',
windDirection: ''
});
}
}
}
} catch (e) {
// 解析失败返回空
}
return {
city,
queryTime: new Date().toISOString(),
forecastDays: Math.min(forecast.length, 7),
forecast: forecast.slice(0, 7),
raw: forecast.length === 0 ? content.substring(0, 2000) : undefined,
};
}
// ============================================================
// 解析器3: 中国天气网移动版 (baidu.weather.com.cn / sq.weather.com.cn)
// 格式: "* 今天/周X\n04/07\n多云转阴\n21/10℃\n[空气质量]\n风向风力 | 日出..."
// 每天以 "* " 列表标记开头,后跟周几/今天
// ============================================================
function parseBaiduWeatherComCn(content, city) {
const forecast = [];
try {
// 按 " * " 列表标记分割天数块
const dayBlocks = content.split(/\n\s*\*\s+/).filter(b => b.trim());
for (const block of dayBlocks) {
const lines = block.split('\n').map(l => l.trim()).filter(l => l);
if (lines.length < 4) continue;
// 第1行: 今天 / 周X
const dayLabel = lines[0];
if (!/^(今天|周[一二三四五六日])$/.test(dayLabel)) continue;
// 第2行: 日期 MM/DD
const dateMatch = lines[1].match(/^(\d{2})\/(\d{2})$/);
if (!dateMatch) continue;
const dateStr = `${dateMatch[1]}/${dateMatch[2]}`;
// 第3行: 天气
const weather = lines[2];
// 第4行: 温度 高/低℃
const tempMatch = lines[3].match(/(-?\d+)\s*\/\s*(-?\d+)℃/);
if (!tempMatch) continue;
const temperature = `${tempMatch[2]}°C ~ ${tempMatch[1]}°C`;
// 剩余行中找风力: "东南风3-4级 东南风4-5级 | 日出..."
let windDirection = '';
let windSpeed = '';
for (let i = 4; i < Math.min(lines.length, 8); i++) {
const windMatch = lines[i].match(/^(东南风|东北风|西南风|西北风|东风|南风|西风|北风|无)([\d<>-]+级)?\s+(东南风|东北风|西南风|西北风|东风|南风|西风|北风|无)([\d<>-]+级)?/);
if (windMatch) {
windDirection = windMatch[1];
windSpeed = windMatch[2] || '';
break;
}
}
forecast.push({
date: `${dateStr} ${dayLabel}`,
weather,
temperature,
windDirection,
windSpeed,
});
}
} catch (e) {
// 解析失败返回空
}
return {
city,
queryTime: new Date().toISOString(),
forecastDays: Math.min(forecast.length, 7),
forecast: forecast.slice(0, 7),
raw: forecast.length === 0 ? content.substring(0, 2000) : undefined,
};
}
/**
* 打印使用帮助
*/
function printUsage() {
console.log(`
IQS Weather Query - 7天天气预报查询
用法:
node weather.mjs <城市名称>
示例:
node weather.mjs 北京
node weather.mjs 上海
node weather.mjs 杭州
环境变量:
ALIYUN_IQS_API_KEY - 阿里云 IQS API Key
获取 API Key: https://help.aliyun.com/zh/document_detail/3025781.html
`);
}
/**
* 主函数
*/
async function main() {
const args = process.argv.slice(2);
if (args.includes('-h') || args.includes('--help') || args.length === 0) {
printUsage();
process.exit(0);
}
const city = args.join(' ');
// 安全校验:城市参数必须是非空字符串,长度不超过100字符
const safeCity = String(city).trim();
if (!safeCity || safeCity.length === 0) {
console.error(JSON.stringify({
error: '城市名称不能为空'
}, null, 2));
process.exit(1);
}
if (safeCity.length > 100) {
console.error(JSON.stringify({
error: '城市名称过长,请控制在100字符以内'
}, null, 2));
process.exit(1);
}
// 过滤控制字符(ASCII 0-31),保留常见中英文字符、数字、标点
const sanitizedCity = safeCity.replace(/[\x00-\x1F\x7F]/g, '');
if (!sanitizedCity) {
console.error(JSON.stringify({
error: '城市名称包含无效字符'
}, null, 2));
process.exit(1);
}
// 加载 API Key
const apiKey = await loadApiKey();
if (!apiKey) {
console.error(JSON.stringify({
error: 'ALIYUN_IQS_API_KEY 未配置。请设置环境变量或参考 https://help.aliyun.com/zh/document_detail/3025781.html'
}, null, 2));
process.exit(1);
}
console.error(`正在查询 ${sanitizedCity} 未来7天天气预报...`);
try {
// Step 1: 搜索天气信息,筛选最优 URL
console.error('Step 1/3: 调用 IQS UnifiedSearch API 搜索天气信息...');
const searchResult = await searchWeather(apiKey, sanitizedCity);
const weatherUrl = findBestWeatherUrl(searchResult);
if (!weatherUrl) {
console.error(JSON.stringify({
error: `未找到 ${sanitizedCity} 的天气信息`
}, null, 2));
process.exit(1);
}
// Step 2: 读取天气网页内容
console.error(`Step 2/3: 调用 IQS ReadPageBasic API读取天气网页 ${weatherUrl}`);
const pageResult = await readPage(apiKey, weatherUrl);
const content = pageResult.text || '';
// Step 3: 解析天气数据
console.error('Step 3/3: 解析天气数据...');
const weatherData = parseWeatherData(content, sanitizedCity, weatherUrl);
weatherData.source = weatherUrl;
// 输出结果到 stdout
console.log(JSON.stringify({
success: true,
data: weatherData,
}, null, 2));
} catch (error) {
console.error(JSON.stringify({
error: error.message
}, null, 2));
process.exit(1);
}
}
main();
Related skills
FAQ
How does it get the forecast?
It runs UnifiedSearch to find a weather page then ReadPageBasic to read and parse it, preferring weather.cma.cn and weather.com.cn.
What output does it return?
Structured JSON for known sites (parseMode structured) or raw text for the agent to interpret on unknown sites (parseMode raw).