环保监测公开数据应用:OpenClaw 抓取空气与水质公开监测数据,开展区域环境质量趋势分析

一、从公开数据到环境洞察:为什么这件事值得做

环境质量数据的公开,是环境治理走向透明化、科学化的重要标志。近年来,国家和地方生态环境部门持续推进监测信息公开,空气质量和地表水水质等核心指标的实时数据、历史数据陆续向公众开放。这些数据不是用来简单"看一看"的,它们背后承载着区域污染变化、治理成效、季节规律和长期趋势等丰富信息。对于研究人员、环境从业者、数据工程师乃至普通市民来说,系统性获取并分析这些公开数据,能够形成对区域环境质量更客观、更完整的认识。

然而,理想的数据应用与现实之间存在明显落差。公开监测数据通常以网页表格、动态图表或查询接口的形式呈现,数据量大、更新频繁,且部分平台采用异步加载和前端渲染,人工复制粘贴既不现实,也容易出错。传统的人工采集方式还面临格式不统一、历史数据难以追溯、多站点对比困难等问题。要真正把公开数据用起来,第一步不是做复杂的模型,而是建立一条稳定、可维护、可持续的数据采集管线,把分散在不同平台上的监测数据自动、规范地汇集到本地。

自动化抓取在这条管线中处于最前端的位置。它解决的是"数据从哪来、怎么持续来"的问题。一个设计良好的抓取方案,不仅要能拿到数据,还要能适应目标网站的页面结构变化,处理反爬策略,并保证抓取过程对目标服务器友好、不给对方造成压力。本文将以 OpenClaw 这一开源网页抓取框架为核心工具,结合空气质量和地表水水质两类典型公开监测数据源,完整演示从数据抓取、清洗入库、趋势分析到可视化报告的全过程。文章面向有一定 Python 基础的读者,尽量把每一步的操作细节、设计考量和可能遇到的问题讲清楚。

需要特别说明的是,本文所有示例都基于公开可访问的监测信息平台,抓取行为遵守目标平台的访问规则与频次约束,仅用于技术学习和个人研究场景。在实际落地时,建议优先查询平台是否提供官方数据接口或下载通道,如果有,应优先使用官方渠道;抓取只是在不提供稳定接口时的一种合理补充手段。

二、OpenClaw 是什么:一个面向动态网页的数据抓取利器

2.1 从 Crawl4AI 到 OpenClaw

OpenClaw 的前身是开源社区广为人知的 Crawl4AI 项目。Crawl4AI 最初定位于"为大型语言模型准备网页数据",强调把网页内容转换为干净、结构化、便于后续处理的文本或标记格式。随着项目演进,其能力逐步从单纯的静态页面抓取,扩展到动态内容渲染、浏览器会话管理、内容过滤、结构化提取等多个层面,并最终更名为 OpenClaw。更名后的项目延续了原有的技术路线,同时强化了通用网页数据抓取和 AI 数据工程场景的支持。

OpenClaw 底层基于 Playwright 构建,这意味着它具备真实浏览器环境下的页面渲染能力。许多环保监测平台采用 JavaScript 动态生成表格、图表或者通过异步接口延迟加载数据,普通的 HTTP 请求库拿到的可能只是一个空壳页面。OpenClaw 通过浏览器引擎执行页面脚本,等数据渲染完成后再读取 HTML 内容,从而解决了动态页面的数据获取难题。这是它相对 requests、httpx 等传统 HTTP 客户端最显著的优势。

2.2 与传统爬虫相比,OpenClaw 解决的关键问题

传统爬虫通常采用"发送请求、接收响应、正则或选择器解析"的固定模式。这种方式在页面结构简单、数据直接嵌入 HTML 时效率很高,但面对动态渲染、登录态、验证码、异步分页等场景时往往力不从心。OpenClaw 把浏览器自动化与数据提取能力进行了封装,开发者不需要手动维护 WebDriver,也不需要处理复杂的浏览器启动和关闭逻辑,就能完成真实渲染、等待元素出现、执行交互操作等任务。

另一个关键差异在于工程化程度。OpenClaw 提供了异步 API、上下文管理器、浏览器配置、运行配置和多浏览器管理机制,适合在定时任务、批量抓取和长期运行的采集服务中使用。它还内置了内容过滤和格式转换能力,例如把 HTML 转为 Markdown 或纯文本,便于后续送入分析流程或语言模型。对于环保监测数据这种需要长期、稳定、批量采集的场景,这套机制能够显著降低维护成本。

2.3 适合环保监测数据的三个能力

第一是动态渲染能力。空气质量和地表水水质的实时发布平台,很多都采用动态表格和滚动加载,OpenClaw 可以等待目标元素出现后再抓取,避免拿到空数据。第二是选择器提取能力。OpenClaw 支持 CSS 选择器和 XPath,能够精准定位监测站点、监测指标和数值所在的位置,配合 BeautifulSoup 等解析库可以快速完成表格化提取。第三是异步与并发能力。当需要同时抓取多个城市、多个监测站点或不同时间范围的数据时,可以使用异步任务控制抓取节奏,兼顾效率与礼貌性。

三、环保监测公开数据源盘点

3.1 空气质量公开数据源

空气质量数据的公开来源比较丰富。在国家层面,中国环境监测总站建设并维护了全国城市空气质量实时发布平台,提供各城市国控监测站点的空气质量指数、细颗粒物、可吸入颗粒物、二氧化硫、二氧化氮、一氧化碳、臭氧等指标的实时数据和历史数据。这类平台通常以城市为入口,可以查看不同监测站点的实时数值、小时均值变化和日报数据。数据更新频率较高,适合做区域空气质量趋势和污染过程分析。

在地方层面,各省、市生态环境厅局也普遍建立了本地空气质量发布系统,往往包含更密集的省控、市控站点,站点空间分辨率更高。例如一些城市会发布街道级或园区级的监测数据,这对研究城市内部污染空间分布更有价值。不同平台的字段命名、计量单位和数据粒度可能存在差异,这就需要抓取阶段做好标准化设计。

3.2 地表水水质公开数据源

地表水水质数据同样有国家和地方两个层面的公开渠道。国家地表水水质自动监测实时数据发布系统提供国控断面水质的实时监测信息,指标通常包括高锰酸盐指数、氨氮、总磷、总氮、溶解氧、pH 值、水温、浊度等,并给出水质类别判定结果。与空气质量类似,这些数据以断面为基本单元,分布在河流、湖库的不同位置,具有明确的地理坐标和水系归属。

地方生态环境部门还会发布更细粒度的水功能区、集中式饮用水水源地或入河排污口监测数据。水质数据在时间尺度上有时比空气数据更复杂,因为部分指标存在采样周期、实验室分析滞后和自动监测频率差异。抓取水质数据时,需要特别关注数据发布时间与监测时间之间的对应关系,避免把发布延迟误认为趋势变化。

3.3 数据源共性特征与抓取难点

空气和水质平台虽然主题不同,但在技术特征上有很多共性。其一,页面普遍采用动态渲染,表格和图表常由前端脚本生成。其二,数据更新具有周期性,空气质量通常以小时为单位更新,水质则可能是小时级或日级更新。其三,部分平台会设置访问频次限制或短暂的反爬校验。其四,目标数据往往以表格形式呈现,字段排列规则相对固定,适合用选择器逐行提取。

抓取难点主要集中在三个方面。首先是页面结构变化,平台改版后选择器可能失效,因此抓取代码需要记录清楚定位逻辑,并设计校验机制。其次是动态加载时序问题,如果等待时间不足,可能抓到空表格或未完成的数据。第三是数据规范性,不同平台对"优、良、轻度污染"等分级表述可能一致,但数值单位或缺失值标记可能不同,需要在后续处理中统一。

四、环境准备与 OpenClaw 最小可用配置

4.1 安装与依赖

开始之前,需要准备一个 Python 3.9 及以上版本的运行环境,建议使用虚拟环境隔离依赖。OpenClaw 可以通过 pip 安装,安装命令如下:

bash 复制代码
pip install openclaw

OpenClaw 依赖 Playwright 提供的浏览器内核,首次使用时需要执行一次浏览器安装。这一步会下载 Chromium 内核,网络环境不佳时速度可能较慢,可以提前配置镜像源。安装命令如下:

bash 复制代码
python -m playground install chromium

除了 OpenClaw,本文还会用到 pandas 处理表格数据、BeautifulSoup 解析 HTML、SQLAlchemy 操作数据库以及 matplotlib 绘图,可以一并安装:

bash 复制代码
pip install pandas beautifulsoup4 sqlalchemy matplotlib

4.2 最小抓取示例

下面的代码展示了一个最小的 OpenClaw 抓取流程。它启动一个无头浏览器,访问目标网址,等待页面渲染后返回 HTML 内容。这个例子可以作为一个基础模板,在后续章节中逐步扩展。

python 复制代码
import asyncio
from openclaw import AsyncOpenClaw, BrowserConfig, CrawlerRunConfig


async def main():
    browser_config = BrowserConfig(
        headless=True,
        viewport_width=1440,
        viewport_height=900,
    )
    run_config = CrawlerRunConfig(
        verbose=False,
        wait_until="networkidle",
    )

    async with AsyncOpenClaw(config=browser_config) as crawler:
        result = await crawler.arun(
            url="https://example-environment-platform.com/air-quality",
            config=run_config,
        )
        if result.success:
            print("抓取成功,页面内容长度:", len(result.html))
            with open("air_quality_page.html", "w", encoding="utf-8") as f:
                f.write(result.html)
        else:
            print("抓取失败:", result.error_message)


if __name__ == "__main__":
    asyncio.run(main())

这里有几个要点。headless 参数控制是否显示浏览器窗口,服务器环境下通常设置为 True。wait_until 指定等待策略,networkidle 表示等待网络空闲,适合数据由接口异步加载的页面;如果页面加载较快,也可以换成 domcontentloaded 以缩短等待时间。result.html 是渲染完成后页面的 HTML 字符串,后续解析都基于它进行。

4.3 常见配置项

在实际抓取中,还可以通过 BrowserConfig 设置用户代理、代理服务器和浏览器路径,模拟更真实的访问环境。CrawlerRunConfig 中与等待相关的参数尤为重要,例如 page_timeout 控制页面加载超时,delay_before_return_html 可以在返回前额外等待一段时间,确保动态数据完全渲染。如果目标页面存在弹窗或 Cookie 提示,可以借助 OpenClaw 的浏览器交互能力先处理这些元素,再进行数据提取。

对于批量任务,建议在单个 AsyncOpenClaw 实例中复用浏览器上下文,而不是每次抓取都重新启动浏览器。这样可以显著降低资源开销,也便于统一管理 Cookie 和会话状态。同时要控制并发数量,避免对目标站点造成过大压力。

五、空气质量数据抓取实战

5.1 页面结构与目标字段

空气质量实时发布平台的核心数据通常位于一个结构清晰的表格中。以城市空气质量页面为例,表格的行代表不同的监测站点,列则对应空气质量指数、PM2.5、PM10、二氧化硫、二氧化氮、一氧化碳、臭氧以及首要污染物等指标。我们的目标是解析出每一行、每一列的数值,并记录抓取时间,形成一条条结构化记录。

动手写代码之前,建议先用浏览器开发者工具查看目标表格的 HTML 结构,确认表格的 id 或 class,以及表头字段的准确名称。如果表格包含在 iframe 中,需要先定位 iframe,再进入内部文档;如果表格通过分页组件展示,还需要处理翻页逻辑。本文假设目标表格可以直接通过 CSS 选择器定位到。

5.2 抓取脚本设计

下面的代码在上一章基础模板上进行扩展。它首先抓取空气质量页面,然后用 BeautifulSoup 解析 HTML,找到目标表格并逐行提取站点名称和各指标数值。为便于后续清洗,提取结果先以字典列表形式保存。

python 复制代码
import asyncio
from datetime import datetime

from bs4 import BeautifulSoup
from openclaw import AsyncOpenClaw, BrowserConfig, CrawlerRunConfig

TARGET_URL = "https://example-environment-platform.com/air-quality"


async def fetch_page(crawler, url):
    run_config = CrawlerRunConfig(
        wait_until="networkidle",
        delay_before_return_html=2,
    )
    result = await crawler.arun(url=url, config=run_config)
    if not result.success:
        raise RuntimeError(f"页面抓取失败:{result.error_message}")
    return result.html


def parse_air_table(html):
    soup = BeautifulSoup(html, "html.parser")
    table = soup.select_one("table.air-quality-table")
    if table is None:
        raise ValueError("未找到空气质量数据表格")

    headers = []
    for th in table.select("thead th"):
        headers.append(th.get_text(strip=True))

    records = []
    for tr in table.select("tbody tr"):
        cells = tr.select("td")
        if len(cells) != len(headers):
            continue
        row = {"抓取时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
        for header, cell in zip(headers, cells):
            row[header] = cell.get_text(strip=True)
        records.append(row)
    return records


async def main():
    async with AsyncOpenClaw(config=BrowserConfig(headless=True)) as crawler:
        html = await fetch_page(crawler, TARGET_URL)
        records = parse_air_table(html)
        print(f"共解析到 {len(records)} 条空气质量记录")
        for record in records[:3]:
            print(record)


if __name__ == "__main__":
    asyncio.run(main())

代码中 parse_air_table 函数承担解析职责,fetch_page 函数负责抓取页面,两者分离后便于单元测试。如果页面表格使用其他选择器,只需要调整 select_one 的参数即可。需要注意的是,空气质量平台有时会返回"暂无数据"的占位行,这些行在后续清洗时应识别并剔除。

5.3 数据清洗与标准化

解析得到的字典列表需要经过清洗才能入库。清洗工作通常包括:去掉字段名称和数值中的空白字符;将"---""无""暂无"等占位符统一转换为空值;将空气质量指数等字段转换为数值类型;对首要污染物进行规范化,例如统一"细颗粒物(PM2.5)"与"PM2.5"的表述。下面的示例使用 pandas 完成这些操作。

python 复制代码
import pandas as pd

def clean_air_records(records):
    df = pd.DataFrame(records)
    if df.empty:
        return df

    replace_map = {"---": None, "无": None, "暂无": None, "-": None}
    df = df.replace(replace_map)

    numeric_cols = [
        "AQI",
        "PM2.5",
        "PM10",
        "SO2",
        "NO2",
        "CO",
        "O3",
    ]
    for col in numeric_cols:
        if col in df.columns:
            df[col] = pd.to_numeric(df[col], errors="coerce")

    df["监测站点"] = df["监测站点"].str.strip()
    return df

清洗后的 DataFrame 可以立即保存为 CSV,也可以继续写入数据库。判断清洗是否成功,可以查看各列的空值比例和数值范围是否合理。例如空气质量指数应当在 0 到 500 之间,如果出现明显超出该范围的数值,需要回查原始页面,确认是否解析到了错误的内容。

六、水质监测数据抓取实战

6.1 数据源特点

地表水水质平台的页面结构与空气质量平台类似,同样以表格呈现断面信息和指标数值,但存在几个值得注意的差异。首先是字段更多,除了常规理化指标,还包括水质类别、主要污染指标和断面属性信息。其次是部分平台的数据并非一次性渲染完成,而是根据选择的流域、省份或断面动态刷新,抓取时需要先触发相应的查询操作。第三是历史数据往往以日期为维度组织,抓取历史趋势时可能需要选择日期或处理分页。

针对这些特点,抓取水质数据的代码需要在页面渲染后增加交互步骤。例如先用 OpenClaw 定位流域下拉框,选择目标流域,再点击查询按钮,等待结果表格刷新。这类交互需要访问 OpenClaw 暴露的页面对象,通过 DOM 操作完成。

6.2 多站点抓取与翻页

下面的示例展示了带交互的多站点抓取思路:先进入查询页面,选择省份,触发查询,再解析结果表格。代码中使用 crawler 的底层页面对象执行选择操作,如果平台交互复杂,也可以改用 Playwright 原生 API 直接操作。

python 复制代码
import asyncio

from bs4 import BeautifulSoup
from openclaw import AsyncOpenClaw, BrowserConfig, CrawlerRunConfig

WATER_URL = "https://example-environment-platform.com/water-quality"


async def main():
    browser_config = BrowserConfig(headless=False)
    run_config = CrawlerRunConfig(wait_until="networkidle")

    async with AsyncOpenClaw(config=browser_config) as crawler:
        await crawler.arun(url=WATER_URL, config=run_config)
        page = crawler.page

        await page.select_option("#province", value="jiangsu")
        await page.click("#query-button")
        await page.wait_for_selector("table.water-quality-table tbody tr")

        html = await page.content()
        records = parse_water_table(html)
        print(f"共解析到 {len(records)} 条水质记录")


def parse_water_table(html):
    soup = BeautifulSoup(html, "html.parser")
    table = soup.select_one("table.water-quality-table")
    records = []
    if table is None:
        return records

    headers = [th.get_text(strip=True) for th in table.select("thead th")]
    for tr in table.select("tbody tr"):
        cells = [td.get_text(strip=True) for td in tr.select("td")]
        if len(cells) == len(headers):
            records.append(dict(zip(headers, cells)))
    return records


if __name__ == "__main__":
    asyncio.run(main())

翻页是另一个常见需求。水质平台通常按页展示断面数据,如果目标断面较多,需要循环点击"下一页"并累加解析结果。实现时要注意设置最大翻页次数,避免因选择器变化而陷入无限循环。每翻一页都应加入短暂等待,防止请求过快。

6.3 数据整合

多批次抓取得到的数据需要按断面和时间整合。建议在不依赖实时环境的前提下,先建立抓取任务清单,明确每个任务的目标断面、抓取时间范围和期望字段,再逐项执行。任务清单可以用一个简单的 DataFrame 或配置文件维护,方便断点续跑。整合后的水质数据应至少包含断面名称、所属水系、监测时间、各指标数值和水质类别,这些字段是后续趋势分析的基础。

七、数据存储与数据质量管理

7.1 数据库表设计

对于需要长期积累的趋势分析,数据不能只停留在内存或临时文件里,建议写入关系型数据库。以 SQLite 为例,可以分别建立空气质量监测记录表和水质监测记录表。两张表的结构思路一致:用自增主键标识每条记录,用站点或断面标识空间维度,用监测时间标识时间维度,用各指标字段存储数值,并增加抓取时间字段用于追溯数据来源。

下面的建表语句展示了基本结构。实际项目中应根据平台字段适当增减指标列,并为站点与监测时间建立联合唯一索引,以便进行增量更新和去重。

sql 复制代码
CREATE TABLE IF NOT EXISTS air_quality (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    station_name TEXT NOT NULL,
    monitor_time TEXT NOT NULL,
    aqi REAL,
    pm25 REAL,
    pm10 REAL,
    so2 REAL,
    no2 REAL,
    co REAL,
    o3 REAL,
    primary_pollutant TEXT,
    crawl_time TEXT NOT NULL,
    UNIQUE(station_name, monitor_time)
);

CREATE TABLE IF NOT EXISTS water_quality (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    section_name TEXT NOT NULL,
    river_basin TEXT,
    monitor_time TEXT NOT NULL,
    codmn REAL,
    nh3n REAL,
    tp REAL,
    tn REAL,
    do_value REAL,
    ph REAL,
    water_level TEXT,
    crawl_time TEXT NOT NULL,
    UNIQUE(section_name, monitor_time)
);

使用 SQLAlchemy 可以让入库逻辑更简洁,也便于后续切换到 MySQL 或 PostgreSQL。无论使用哪种方式,都应采用事务批量写入,减少单条插入带来的性能开销。

7.2 数据校验与去重

数据入库之前,需要做两级校验。第一级是结构校验,确认解析结果包含必要的字段,且字段类型符合预期。第二级是业务校验,检查数值范围是否合理,例如 pH 值应在 0 到 14 之间,水质类别应属于规定的分类集合。校验不通过的数据不应直接丢弃,而应记录到单独的异常表中,便于排查是解析错误还是平台数据本身异常。

去重问题也很关键。由于定时抓取可能重复访问同一时段的数据,按联合唯一索引执行插入时,可以通过"存在则更新、不存在则插入"的策略处理。SQLite 中可以使用 INSERT OR REPLACE,也可以在应用层先查询再决定插入或更新。需要注意的是,同一监测时间的数据如果来源平台发起修订,应优先采用最新抓取到的版本。

7.3 缺失值与异常值处理

长期积累的数据难免出现缺失和异常。缺失可能来自站点临时停运、仪器校准或抓取失败,异常则可能来自解析错位或单位换算错误。处理策略应结合分析目标而定:如果做小时级趋势分析,个别缺失小时可以使用前后值插补或标为缺失;如果做月度或季度汇总,少量缺失对整体结论影响通常有限。

异常值识别可以采用统计规则,例如计算指标的历史分布,将超出均值加减三倍标准差的数值标记为可疑,再结合人工判断。对于明显由单位错误导致的异常,例如臭氧浓度数值扩大了一千倍,应在入库前通过单位换算修正。数据质量管理不是一次性的动作,而应贯穿整个采集与分析流程。

八、区域环境质量趋势分析方法

8.1 数据预处理与指标计算

分析趋势之前,需要把数据整理成规整的时间序列。对于空气质量,常见的分析粒度是小时、日和月。小时数据适合识别污染过程,日数据适合观察阶段性变化,月数据适合分析季节规律。对于水质数据,由于更新频率较低,通常以周或月为分析粒度。将原始记录按时段聚合,需要明确聚合方式,例如空气质量指数取均值或最大值,各污染物浓度取均值,水质类别则可通过频次统计反映达标情况。

聚合后的数据建议重采样为连续时间索引,缺失的时间点用空值填充,再根据分析方法决定是否需要插值。下面的代码展示了对空气质量数据进行日均聚合和重采样的基本过程。

python 复制代码
import pandas as pd

df = pd.read_sql(
    "SELECT monitor_time, station_name, aqi, pm25, pm10 FROM air_quality",
    con=engine,
)
df["monitor_time"] = pd.to_datetime(df["monitor_time"])
df["date"] = df["monitor_time"].dt.date

daily = (
    df.groupby(["station_name", "date"])
    .agg(
        aqi_mean=("aqi", "mean"),
        aqi_max=("aqi", "max"),
        pm25_mean=("pm25", "mean"),
        pm10_mean=("pm10", "mean"),
    )
    .reset_index()
)

daily["date"] = pd.to_datetime(daily["date"])
daily = daily.sort_values("date")

8.2 趋势分析常用方法

趋势分析可以从描述性统计、时间序列分解和统计检验三个层面展开。描述性统计主要回答"整体水平如何、波动多大",常用均值、中位数、分位数和变化率等指标。时间序列分解可以把数据分解为长期趋势、季节波动和随机波动三部分,其中长期趋势是判断环境质量是否改善的核心依据。简单移动平均是观察长期趋势的常用方法,计算简单、结果直观。

如果希望从统计上判断趋势是否显著,可以引入 Mann-Kendall 检验或线性回归。Mann-Kendall 检验不要求数据服从正态分布,对时间序列中的缺失值和异常值也有较好的稳健性,适合环境监测数据。斜率估计可以采用 Sen 斜率,它用成对数据的中位数斜率代表整体变化速率,受异常值影响较小。

下面的代码实现了对某个站点 PM2.5 月度均值的简单移动平均和线性趋势拟合,并输出趋势方向。

python 复制代码
import numpy as np
from scipy import stats

series = daily.set_index("date")["pm25_mean"].resample("ME").mean()

rolling = series.rolling(window=3, min_periods=1).mean()

x = np.arange(len(series))
mask = series.notna()
slope, intercept, r_value, p_value, std_err = stats.linregress(
    x[mask], series[mask].values
)

if p_value < 0.05 and slope < 0:
    direction = "显著下降"
elif p_value < 0.05 and slope > 0:
    direction = "显著上升"
else:
    direction = "变化不显著"
print(f"线性趋势斜率:{slope:.4f},判定:{direction}")

8.3 区域对比与空间分布

区域环境质量分析不仅要看时间维度,还要看空间维度。把不同站点或断面的趋势指标放在一起比较,可以发现区域内哪些位置改善较快、哪些位置长期偏高。空间对比的关键是统一口径,例如同一时间段、同一指标、同一聚合方式。对于空气数据,可以对比不同城市或同一城市不同功能区的站点;对于水质数据,可以对比同一河流上下游断面,观察污染物浓度的沿程变化。

空间分析的输出通常是一张汇总表:每一行是一个站点或断面,列包括平均浓度、趋势斜率、达标天数或达标比例等指标。这张表可以直接用于排序,也可以进一步绑定经纬度信息,绘制区域空间分布图,直观呈现环境质量的空间格局。

九、可视化与报告生成

9.1 时间序列折线图

折线图是展示环境质量时间变化的常用方式。绘图时可以将原始日值画成浅色散点或细线,再叠加移动平均线,突出长期趋势。横轴为时间,纵轴为浓度值,多条线代表不同站点,可以用颜色区分。示例代码如下:

python 复制代码
import matplotlib.pyplot as plt

plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False

fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(series.index, series.values, color="#cccccc", linewidth=0.8, label="月度均值")
ax.plot(rolling.index, rolling.values, color="#1f77b4", linewidth=2, label="3期移动平均")
ax.set_xlabel("时间")
ax.set_ylabel("PM2.5 浓度")
ax.set_title("某站点 PM2.5 月度变化趋势")
ax.legend()
ax.grid(True, linestyle="--", alpha=0.4)
plt.tight_layout()
plt.show()

9.2 区域对比柱状图

柱状图适合展示不同站点或断面的指标对比。可以按平均值降序排列,并在柱顶标注数值,让读者一眼看出哪些区域偏高。对于包含达标标准的环境指标,还可以在图中画出标准线,帮助判断达标情况。

python 复制代码
station_avg = daily.groupby("station_name")["pm25_mean"].mean().sort_values()

fig, ax = plt.subplots(figsize=(10, 6))
bars = ax.barh(station_avg.index, station_avg.values, color="#4c9f70")
ax.axvline(x=35, color="#d62728", linestyle="--", label="年均标准参考值")
ax.set_xlabel("PM2.5 平均浓度")
ax.set_title("各站点 PM2.5 平均浓度对比")
for bar, value in zip(bars, station_avg.values):
    ax.text(bar.get_width() + 0.3, bar.get_y() + bar.get_height() / 2,
            f"{value:.1f}", va="center", fontsize=9)
ax.legend()
plt.tight_layout()
plt.show()

9.3 趋势报告输出

在图表基础上,可以进一步生成结构化的分析报告。报告内容通常包括:数据范围和完整性说明、整体空气质量或水质状况、时间趋势结论、站点或断面对比、主要指标变化,以及值得关注的异常时段。报告可以用 HTML 或 PDF 输出,也可以在 Jupyter Notebook 中直接展示。关键是每一个结论都要有对应的数据和图表支撑,避免用模糊表述代替量化结果。

十、工程化落地与运维保障

10.1 定时调度与失败重试

要把抓取任务变成稳定运行的采集服务,调度和容错是必须考虑的环节。Linux 环境可以使用 cron 定时执行脚本,也可以在应用内使用 APScheduler 实现更灵活的任务管理。调度频率应根据数据更新频率设定,例如空气质量按小时抓取,水质按日抓取,不要高于目标平台的实际更新频率。

失败重试机制要分清失败类型。网络超时、页面加载不完整等临时性错误可以进行有限次重试,每次重试之间增加退避等待;而解析错误、字段缺失等确定性错误重试无效,应记录日志并跳过,等待人工排查。每次抓取都应写入运行日志,包括开始时间、结束时间、成功条数、失败原因等,方便回溯。

10.2 抓取频率与合规边界

公开监测数据虽然公开,但抓取行为仍然需要克制。高频访问可能影响平台正常服务,也容易触发对方的风控。设定合理的请求间隔,控制并发数,遵守 robots.txt 约定,是基本的工程礼貌。如果平台明确禁止自动化采集,或者已经提供官方接口,就不应强行抓取。对于研究用途的数据积累,建议与数据发布方沟通,争取获得更稳定的数据获取方式。

在数据使用层面,还要注意数据质量和时效性的声明。公开监测数据可能存在仪器误差、校准修正和历史修订,分析报告中应标注数据来源和获取时间,避免把未经核验的实时数据直接等同于最终认定数据。

10.3 监控与告警

长期运行的采集任务需要监控其健康状态。最简单的做法是每次运行后检查数据量是否符合预期,例如某城市应有固定数量的站点,如果本次解析出的记录数明显少于历史均值,说明可能出现页面改版或抓取失败。将这类检查结果通过日志或消息推送通知维护者,可以及时发现问题。

更完整的监控可以包括:数据库中的最新数据时间是否滞后、各站点数据覆盖率是否下降、告警事件的持续时长等。绘制一张数据覆盖热力图,可以直观展示哪些时间段、哪些站点的数据存在缺口,帮助定位采集管线的薄弱环节。

十一、总结与展望

本文以 OpenClaw 为核心工具,完整梳理了从环保监测公开数据抓取到区域环境质量趋势分析的流程。我们从为什么需要自动化采集谈起,介绍了 OpenClaw 的技术特点,盘点了空气和水质两类数据源,给出了可行的抓取、清洗、入库和分析方案,并讨论了工程化落地中的调度、容错与合规问题。整条链路的核心不是某一段代码,而是一套可维护、可扩展的思维框架:先明确数据源和字段,再设计抓取与解析,接着做好存储与质量管控,最后围绕分析目标展开趋势和空间研究。

随着环境监测网络的不断加密和数据开放程度的提高,这类应用的价值会越来越明显。未来可以在此基础上探索更丰富的方向,例如把多源数据与气象、工业企业、交通流量等外部数据融合,分析污染成因;或者引入时空统计模型,识别区域间的传输贡献;也可以把清洗后的数据做成自动更新的看板,向公众直观展示区域环境质量变化。无论方向如何延伸,稳定、规范、合规的数据采集能力始终是最重要的起点。

环境数据是一座富矿,但只有经过系统的采集、治理和分析,才能真正转化为对人类决策有意义的洞察。希望本文能帮助读者迈出从"看到数据"到"用好数据"的第一步。

相关推荐
大模型真好玩1 小时前
大模型训练全流程实战指南实战篇(十五)——预训练数据治理
人工智能·agent·deepseek
RSABLOCKCHAIN1 小时前
打造工业级多智能体量化交易闭环:基于 Alpaca、LangGraph 与双轨执行的智能投资与持仓治理系统
人工智能·python·ai·aigc
用户019027581611 小时前
如何用 Python 做多因子打分选股?
python
光锥智能1 小时前
HUAWEI WATCH 6系列正式发布:鸿蒙AI手表,智慧健康旗舰
人工智能·华为·harmonyos
烂蜻蜓2 小时前
Flask入门教程(三十一):实用函数与类API——全局工具函数速查
后端·python·flask
晓窗科技2 小时前
AI基座哪家服务好
大数据·人工智能·python
高洁012 小时前
AI智能体落地价值:从工具赋能到产业重构,解锁企业降本增效新逻辑
人工智能·深度学习·机器学习·transformer·知识图谱