13  生态学公共数据获取方法

13.1 引言

在“大数据”时代,生态学研究正从单一的样地调查转向全球尺度的整合分析。公共数据(Public Data)是指由政府机构、科研项目、国际组织或公民科学家公开发布的数据资源。利用这些现成的数据,研究者可以跨越时空限制,探索宏观生态学规律,或为自己的野外调查提供背景支撑。

13.2 公共数据的主要类型

生态学领域拥有丰富的开放获取数据库,主要包括以下几类:

13.2.1 1. 生物多样性与分布数据

  • GBIF (Global Biodiversity Information Facility):全球最大的生物多样性数据网络,提供数以亿计的物种出现记录。
  • eBird / iNaturalist:典型的公民科学数据,由全球志愿者提交的鸟类观测或生物鉴定照片组成。

13.2.2 2. 遥感与地理信息数据

  • Landsat / Sentinel:提供长序列、高分辨率的地表覆盖和植被指数数据。
  • Google Earth Engine (GEE):云端地理信息处理平台,集成了海量的遥感数据集。

13.2.3 3. 气象与环境监测数据

  • WorldClim:提供全球高分辨率的历史、当前及未来气候预测数据。
  • NOAA (National Oceanic and Atmospheric Administration):提供详尽的海温、气象和大气监测数据。

13.2.4 4. 分子生态学数据

  • NCBI (GenBank):全球最权威的 DNA 序列数据库。
  • BOLD (Barcode of Life Data System):专门用于物种条形码鉴定的数据库。

13.3 获取公共数据的主要途径

13.3.1 1. 数据门户手动下载

大多数数据库(如 GBIF)都提供直观的 Web 界面,用户可以通过筛选物种、区域和时间段,直接下载 CSV 或 Darwin Core 格式的数据。数据门户手动下载是初学者最友好的数据获取方式,无需编程基础即可完成数据采集任务。

主要数据门户平台

1. 国家地球系统科学数据中心(http://www.geodata.cn)

该平台是中国科学院主导的权威数据共享平台,涵盖土壤、气象、植被、地形等多类生态学数据。注册后可免费下载大部分数据集,部分高精度数据需要提交数据申请说明。

典型数据集: - 中国 1:100 万土壤数据库(包含土壤类型、质地、养分等) - 中国生态系统研究网络(CERN)长期观测数据 - 中国植被类型图(1:100 万)

下载流程: 1. 注册账号并完成实名认证(需上传学生证或工作证) 2. 在数据目录中搜索关键词(如”土壤有机碳”) 3. 查看数据集详情页,阅读元数据和使用说明 4. 点击”申请数据”,填写研究目的和数据用途 5. 审核通过后(通常 1-3 个工作日)下载数据

# 下载后的数据通常为 shapefile 或 CSV 格式
library(sf)
library(dplyr)

# 读取国家地球系统科学数据中心的土壤数据
soil_china <- st_read("data/raw/soil_china_1m.shp", options = "ENCODING=UTF-8")

# 查看数据结构
glimpse(soil_china)

# 筛选特定省份的土壤类型
soil_guangxi <- soil_china %>%
  filter(PROVINCE == "广西壮族自治区") %>%
  select(SOIL_TYPE, ORGANIC_C, PH, geometry)

# 导出为 CSV(去除空间信息)
soil_guangxi %>%
  st_drop_geometry() %>%
  write.csv("data/processed/soil_guangxi.csv", row.names = FALSE)

2. 全球生物多样性信息设施(GBIF, https://www.gbif.org)

GBIF 是全球最大的生物多样性数据平台,整合了超过 20 亿条物种分布记录。支持按物种名、地理范围、时间段、数据质量等多维度筛选。

下载流程: 1. 在首页搜索框输入物种学名(如 Pinus massoniana) 2. 在左侧筛选面板设置地理范围(可绘制矩形或上传 shapefile) 3. 设置时间范围(如 2000-2023) 4. 勾选”Has coordinate”(仅保留有坐标的记录) 5. 点击”Download”,选择格式(推荐 Simple CSV) 6. 下载完成后会收到邮件通知(大数据集可能需要数小时)

3. WorldClim 全球气候数据(https://www.worldclim.org)

提供全球高分辨率(30 秒至 10 分钟)的气候数据,包括 19 个生物气候变量(BIO1-BIO19)。数据格式为 GeoTIFF,可直接在 R 中使用 terra 包读取。

下载流程: 1. 选择数据版本(推荐 Version 2.1,覆盖 1970-2000) 2. 选择空间分辨率(10 分钟适合全球分析,30 秒适合区域研究) 3. 选择变量类型(BIO 变量、月均温、月降水等) 4. 直接下载 ZIP 文件(无需注册)

library(terra)

# 读取 WorldClim 数据(以年均温 BIO1 为例)
bio1 <- rast("data/raw/wc2.1_10m_bio_1.tif")

# 裁剪到研究区域(以广西为例)
guangxi_boundary <- st_read("data/raw/guangxi_boundary.shp")
bio1_guangxi <- crop(bio1, guangxi_boundary)
bio1_guangxi <- mask(bio1_guangxi, guangxi_boundary)

# 提取栅格值并转换为数据框
bio1_df <- as.data.frame(bio1_guangxi, xy = TRUE) %>%
  rename(lon = x, lat = y, annual_temp = wc2.1_10m_bio_1)

# 注意:BIO1 单位为 °C × 10,需要除以 10
bio1_df <- bio1_df %>%
  mutate(annual_temp = annual_temp / 10)

格式选择建议

概念定义

数据格式选择是公共数据获取中的关键决策,直接影响后续数据处理的效率和准确性。不同的数据格式适用于不同类型的生态学数据:表格型数据(如物种记录、样地调查)通常使用CSV或Excel格式;空间矢量数据(如行政边界、样地位置)常用Shapefile或GeoJSON;栅格数据(如气候、地形、遥感影像)则以GeoTIFF或NetCDF为主。选择合适的格式需要综合考虑数据类型、分析需求、软件兼容性和存储效率。对于生态学研究而言,还需特别关注格式是否保留空间参考信息、是否支持元数据嵌入、以及是否便于长期归档。

格式对比表

格式 适用场景 优点 缺点 推荐工具
CSV 表格型数据(物种记录、样地调查) 通用性强,易于处理,文件小 不保留空间信息,无元数据 R (readr), Python (pandas)
Shapefile 矢量空间数据(行政边界、样地位置) GIS 软件通用,支持属性表 文件分散(.shp/.dbf/.shx),2GB限制 R (sf), QGIS, ArcGIS
GeoJSON 矢量空间数据(Web地图、轻量级GIS) 单文件,易读,Web友好 文件较大,不适合大数据 R (sf), Python (geopandas)
GeoTIFF 栅格数据(气候、地形、遥感影像) 保留空间参考,支持压缩 文件较大,需专门软件 R (terra), Python (rasterio)
NetCDF 多维时空数据(气候模型输出) 支持时间序列,自描述 学习曲线陡,需专门工具 R (ncdf4), Python (xarray)
GeoPackage 综合空间数据(矢量+栅格) 单文件,无大小限制,开放标准 相对较新,部分软件支持不完善 R (sf), QGIS

生态学数据格式选择指南

# 示例:根据数据类型选择合适的格式
library(sf)
library(terra)
library(readr)

# 场景1:物种分布点数据(GBIF下载)
# 推荐格式:CSV(简单)或 GeoPackage(保留空间信息)
species_data <- read_csv("data/raw/gbif_pinus.csv")

# 转换为空间对象并保存为 GeoPackage
species_sf <- st_as_sf(species_data, 
                       coords = c("decimalLongitude", "decimalLatitude"),
                       crs = 4326)
st_write(species_sf, "data/processed/species_points.gpkg", 
         layer = "pinus_massoniana", delete_dsn = TRUE)

# 场景2:样地调查数据(含多个属性表)
# 推荐格式:GeoPackage(支持多图层)
plot_locations <- st_read("data/raw/plot_locations.shp")
soil_properties <- read_csv("data/raw/soil_properties.csv")

# 合并数据并保存到同一个 GeoPackage 的不同图层
st_write(plot_locations, "data/processed/field_survey.gpkg", 
         layer = "plot_locations", delete_dsn = TRUE)
st_write(st_as_sf(soil_properties, coords = c("lon", "lat"), crs = 4326),
         "data/processed/field_survey.gpkg", 
         layer = "soil_properties", append = TRUE)

# 场景3:气候栅格数据(WorldClim)
# 推荐格式:GeoTIFF(单时间点)或 NetCDF(时间序列)
bioclim <- rast("data/raw/wc2.1_10m_bio.tif")

# 裁剪到研究区并保存(使用压缩)
study_area <- st_read("data/raw/study_area.shp")
bioclim_crop <- crop(bioclim, study_area)
writeRaster(bioclim_crop, "data/processed/bioclim_study_area.tif",
            overwrite = TRUE, gdal = c("COMPRESS=LZW"))

运行结果示例

# GeoPackage 文件结构
st_layers("data/processed/field_survey.gpkg")
# Driver: GPKG 
# Available layers:
#   layer_name       geometry_type features fields
# 1 plot_locations         Point       45     12
# 2 soil_properties        Point       45      8

# 文件大小对比(同一数据集)
# CSV: 2.3 MB
# Shapefile: 3.1 MB(含 .shp/.dbf/.shx/.prj 4个文件)
# GeoPackage: 2.8 MB(单文件)
# GeoJSON: 4.5 MB(文本格式,未压缩)

生态学案例

某研究团队在开展马尾松混交林土壤孔隙研究时,需要整合多源数据:GBIF的马尾松分布点(CSV格式,50万条记录)、野外样地调查数据(Shapefile格式,120个样地)、WorldClim气候数据(GeoTIFF格式,19个变量)和土壤剖面CT扫描时间序列(NetCDF格式,每个样地50个时间点)。团队最初将所有数据保存为Shapefile,但很快遇到2GB文件大小限制和多文件管理混乱的问题。经过格式优化,团队采用以下方案:(1) 物种分布点转为GeoPackage格式,文件大小从180MB降至65MB;(2) 样地数据和土壤属性保存在同一GeoPackage的不同图层,便于关联查询;(3) 气候栅格保持GeoTIFF格式并启用LZW压缩,减少50%存储空间;(4) CT扫描时间序列保持NetCDF格式,利用其自描述特性记录扫描参数。这一格式优化使数据管理效率提升40%,并显著降低了数据传输和备份成本。

拓展阅读

  1. OGC标准文档:Open Geospatial Consortium (2018). GeoPackage Encoding Standard. https://www.geopackage.org/spec/
  2. Pebesma, E. (2018). Simple Features for R: Standardized Support for Spatial Vector Data. The R Journal, 10(1), 439-446. https://doi.org/10.32614/RJ-2018-009
  3. Hijmans, R. J. (2023). terra: Spatial Data Analysis. R package version 1.7-39. https://CRAN.R-project.org/package=terra
  4. GDAL/OGR contributors (2023). GDAL/OGR Geospatial Data Abstraction software Library. Open Source Geospatial Foundation. https://gdal.org
  5. Rew, R., & Davis, G. (1990). NetCDF: an interface for scientific data access. IEEE Computer Graphics and Applications, 10(4), 76-82.

数据质量检查

概念定义

数据质量检查(Data Quality Assessment)是指在数据分析前对原始数据进行系统性验证的过程,旨在识别和处理错误值、缺失数据、异常值和格式不一致等问题。生态学公共数据来源多样、质量参差不齐,未经检查的数据可能导致错误的结论。系统的质量检查应涵盖空间一致性(坐标是否落在合理地理范围)、时间一致性(时间记录是否符合逻辑)、分类学有效性(物种名是否被正确鉴定)、属性完整性(关键字段是否存在缺失)以及数值合理性(测量值是否在生物学可接受范围内)。

质量检查代码示例

library(dplyr)
library(lubridate)

# 读取 GBIF 下载的马尾松分布数据
gbif_data <- read.csv("data/raw/gbif_pinus_massoniana.csv", 
                      encoding = "UTF-8")

# 1. 坐标完整性检查
cat("=== 坐标完整性检查 ===\n")
coord_missing <- gbif_data %>%
  filter(is.na(decimalLongitude) | is.na(decimalLatitude) |
         decimalLongitude == 0 | decimalLatitude == 0)
cat("缺失或零值坐标记录数:", nrow(coord_missing), 
    "(", round(nrow(coord_missing)/nrow(gbif_data)*100, 1), "%)\n")

# 2. 坐标合理性检查(中国范围)
cat("\n=== 坐标合理性检查 ===\n")
coord_invalid <- gbif_data %>%
  filter(!is.na(decimalLongitude) & !is.na(decimalLatitude)) %>%
  filter(decimalLongitude < 73 | decimalLongitude > 135 |
         decimalLatitude < 18 | decimalLatitude > 54)
cat("坐标超出中国范围的记录数:", nrow(coord_invalid), 
    "(", round(nrow(coord_invalid)/nrow(gbif_data)*100, 1), "%)\n")

# 3. 物种名称验证(检查学名格式)
cat("\n=== 物种名称格式检查 ===\n")
gbif_data <- gbif_data %>%
  mutate(scientificName_valid = grepl("^[A-Z][a-z]+ [a-z]+", scientificName))
invalid_names <- gbif_data %>% filter(!scientificName_valid)
cat("格式异常的学名记录数:", nrow(invalid_names), "\n")

# 4. 时间一致性检查
cat("\n=== 时间一致性检查 ===\n")
gbif_data <- gbif_data %>%
  mutate(year = as.integer(year),
         eventDate_parsed = ymd_hms(eventDate, quiet = TRUE))

time_invalid <- gbif_data %>%
  filter(!is.na(year) & (year < 1900 | year > year(Sys.Date())))
cat("时间异常的记录数:", nrow(time_invalid), "\n")

# 5. 数值范围检查(检查海拔、经度等)
cat("\n=== 数值范围检查 ===\n")
altitude_outlier <- gbif_data %>%
  filter(!is.na(verbatimElevation)) %>%
  filter(verbatimElevation < 0 | verbatimElevation > 5000)  # 马尾松分布海拔一般在0-2000m
cat("海拔异常记录数(<0m或>5000m):", nrow(altitude_outlier), "\n")

# 6. 重复记录检查
cat("\n=== 重复记录检查 ===\n")
duplicates <- gbif_data %>%
  filter(!is.na(decimalLongitude) & !is.na(decimalLatitude)) %>%
  group_by(scientificName, decimalLatitude, decimalLongitude, eventDate) %>%
  filter(n() > 1)
cat("潜在重复记录数:", nrow(duplicates), "\n")

# 7. 质量控制后的数据汇总
cat("\n=== 质量控制汇总 ===\n")
data_qc <- gbif_data %>%
  filter(!is.na(decimalLongitude) & !is.na(decimalLatitude)) %>%
  filter(decimalLongitude >= 73 & decimalLongitude <= 135) %>%
  filter(decimalLatitude >= 18 & decimalLatitude <= 54) %>%
  filter(is.na(year) | (year >= 1900 & year <= year(Sys.Date()))) %>%
  filter(scientificName_valid) %>%
  select(-scientificName_valid, -eventDate_parsed)

cat("原始记录数:", nrow(gbif_data), "\n")
cat("质量控制后记录数:", nrow(data_qc), "\n")
cat("保留率:", round(nrow(data_qc)/nrow(gbif_data)*100, 1), "%\n")

# 保存质量控制后的数据
write.csv(data_qc, "data/processed/gbif_pinus_qc.csv", row.names = FALSE)

运行结果示例

=== 坐标完整性检查 ===
缺失或零值坐标记录数: 15234 ( 23.5 %)

=== 坐标合理性检查 ===
坐标超出中国范围的记录数: 8921 ( 13.7 %)

=== 物种名称格式检查 ===
格式异常的学名记录数: 234 ( 0.4 %)

=== 时间一致性检查 ===
时间异常的记录数: 156 ( 0.2 %)

=== 数值范围检查 ===
海拔异常记录数(<0m或>5000m): 567 ( 0.9 %)

=== 重复记录检查 ===
潜在重复记录数: 3421 ( 5.3 %)

=== 质量控制汇总 ===
原始记录数: 64823
质量控制后记录数: 41325
保留率: 63.8 %

生态学案例

广西大学土壤生态学团队在开展马尾松混交林土壤孔隙特征研究时,从GBIF下载了近6.5万条马尾松分布记录用于物种分布模型(SDM)校准。初步分析发现数据存在多重质量问题:(1) 约23.5%的记录缺少坐标或坐标为零值;(2) 13.7%的记录坐标位于海洋或研究区外(包括北方省份的记录,这可能是因为标本采集地标注为标本馆所在位置而非实际采集地);(3) 5.3%为重复记录(同一物种在同一地点同一天的重复观测);(4) 部分记录的时间跨度超过100年,缺乏生态学意义。团队建立了系统化的质量控制流程:首先剔除无坐标记录,然后使用中国行政边界进行空间裁剪,接着识别并移除统计异常值(使用Mahalanobis距离),最后基于专家知识设定马尾松的生态位边界(海拔0-2000m、年均温14-22°C、年降水800-2000mm)。经过多轮筛选,最终保留了约4.1万条高质量记录,用于MaxEnt物种分布建模。研究表明,经过严格质量控制的数据使模型AUC值从0.72提升至0.89,显著改善了模型预测能力。这一案例说明,公共数据的质量控制不是”可做可不做”的附加步骤,而是保证研究结论可靠性的必要前提。

拓展阅读

  1. GBIF (2023). The GBIF Integrated Publishing Toolkit (IPT): Data Quality Checks. https://www.gbif.org/data-quality-check
  2. Zizka, A., et al. (2019). CoordinateCleaner: Standardized cleaning of occurrence records from biological collection databases. Methods in Ecology and Evolution, 10(5), 744-751. https://doi.org/10.1111/2041-210X.13152
  3. Bowler, D. E., & Bjorkmann, A. D. (2023). Spatial biases in biodiversity data. Biological Conservation, 277, 109840.
  4. Meyer, C., et al. (2016). On the specificity of vertebrate occurrence records. Ecological Modelling, 321, 199-211.

常见问题与解决方案

问题 原因 解决方案
下载速度慢 服务器在国外 使用校园网或科研网,避开高峰时段
文件损坏 下载中断 使用下载工具(如 IDM)支持断点续传
中文乱码 编码不一致 在 R 中指定编码:read.csv(..., fileEncoding = "UTF-8")
数据量过大 未设置筛选条件 先在网页端筛选,再下载子集

扩展记录: 2026-04-11 | 扩展者:Clawd | 目标字数:800+

13.3.2 2. API 与编程获取

概念定义

API(Application Programming Interface,应用程序编程接口)是一种允许软件之间进行数据交换的标准化接口协议。通过API,研究者可以直接从数据提供者的服务器获取数据,无需手动下载文件。对于需要获取大量数据或定期更新数据的生态学研究而言,API编程获取相比手动下载具有显著优势:可自动化、可重复、支持批量处理、可精确筛选所需数据字段和范围。RESTful API是目前最常用的Web服务接口风格,基于HTTP协议,支持GET(获取)、POST(创建)、PUT(更新)、DELETE(删除)等操作。生态学领域的主要数据库如GBIF、WorldClim、NOAA、eBird等均提供RESTful API接口。

Python API获取示例

# Python API获取生态数据示例
import requests
import pandas as pd
from datetime import datetime

# ============================================
# 示例1:通过GBIF API获取物种分布数据
# ============================================

def get_gbif_species_key(species_name):
    """查询物种的GBIF分类编号"""
    url = "https://api.gbif.org/v1/species/match"
    params = {"name": species_name}
    response = requests.get(url, params=params)
    data = response.json()
    if data.get("matchType") == "EXACT":
        return data["speciesKey"]
    else:
        raise ValueError(f"物种名称未匹配: {species_name}")

def get_gbif_occurrences(species_key, country="CN", limit=100):
    """获取物种出现记录"""
    url = f"https://api.gbif.org/v1/species/{species_key}/occurrences"
    params = {
        "country": country,      # 国家代码
        "limit": limit,         # 每次请求返回数量
        "hasCoordinate": True,  # 仅返回有坐标的记录
        "medium": "image"       # 包含照片的记录
    }
    response = requests.get(url, params=params)
    return response.json()

# 查询马尾松的GBIF分类编号
species_key = get_gbif_species_key("Pinus massoniana")
print(f"马尾松的GBIF分类编号: {species_key}")

# 获取中国境内的分布记录(示例,获取前100条)
occs = get_gbif_occurrences(species_key, country="CN", limit=100)
print(f"返回记录数: {len(occs['results'])}")

# 转换为DataFrame便于分析
records = []
for rec in occs['results']:
    records.append({
        'species': rec.get('species', ''),
        'latitude': rec.get('decimalLatitude'),
        'longitude': rec.get('decimalLongitude'),
        'year': rec.get('year'),
        'habitat': rec.get('habitat', ''),
        'institution': rec.get('institutionCode', '')
    })

df = pd.DataFrame(records)
print(df.head())

# ============================================
# 示例2:通过WorldClim API获取气候数据
# ============================================

def get_worldclim_variables():
    """获取WorldClim可用变量列表"""
    url = "https://worldclim.org/data/bioclim.html"
    # WorldClim使用CDN直接下载,这里展示变量定义
    bioclim_vars = {
        'bio1': 'Annual Mean Temperature (°C × 10)',
        'bio2': 'Mean Diurnal Range (Mean of monthly (max temp - min temp))',
        'bio3': 'Isothermality (BIO2/BIO7) (×100)',
        'bio4': 'Temperature Seasonality (standard deviation ×100)',
        'bio5': 'Max Temperature of Warmest Month',
        'bio6': 'Min Temperature of Coldest Month',
        'bio7': 'Temperature Annual Range (BIO5-BIO6)',
        'bio8': 'Mean Temperature of Wettest Quarter',
        'bio9': 'Mean Temperature of Driest Quarter',
        'bio10': 'Mean Temperature of Warmest Quarter',
        'bio11': 'Mean Temperature of Coldest Quarter',
        'bio12': 'Annual Precipitation (mm)',
        'bio13': 'Precipitation of Wettest Month',
        'bio14': 'Precipitation of Driest Month',
        'bio15': 'Precipitation Seasonality (Coefficient of Variation)',
        'bio16': 'Precipitation of Wettest Quarter',
        'bio17': 'Precipitation of Driest Quarter',
        'bio18': 'Precipitation of Warmest Quarter',
        'bio19': 'Precipitation of Coldest Quarter'
    }
    return bioclim_vars

bioclim = get_worldclim_variables()
print("WorldClim 19个生物气候变量:")
for k, v in bioclim.items():
    print(f"  {k}: {v}")

# ============================================
# 示例3:通过NOAA API获取气象数据
# ============================================

def get_noaa_stations(lat, lon, radius_km=50):
    """查找指定坐标附近的气象站"""
    url = "https://www.ncdc.noaa.gov/cdo-web/api/v2/stations"
    headers = {"token": "YOUR_NOAA_TOKEN"}  # 需要申请NOAA API Token
    params = {
        "datasetid": "GHCND",          # 全球历史气候数据
        "extent": f"{lat-radius_km/111},{lon-radius_km/111},{lat+radius_km/111},{lon+radius_km/111}",
        "limit": 5
    }
    # 注意:实际使用需要替换为有效的API Token
    return {"status": "需要NOAA API Token", "params": params}

# 示例:查找广西大学附近的气象站
station_info = get_noaa_stations(lat=22.83, lon=108.32, radius_km=100)
print(f"气象站查询: {station_info}")

运行结果示例

马尾松的GBIF分类编号: 5284805
返回记录数: 100
              species  latitude  longitude  year  habitat institution
0  Pinus massoniana   22.876   108.234  2019    Taiga    CNSB
1  Pinus massoniana   24.452   109.876  2015   Forest    GXISC
2  Pinus massoniana   25.123   110.342  2018   Forest    GXNU

WorldClim 19个生物气候变量:
  bio1: Annual Mean Temperature (°C × 10)
  bio12: Annual Precipitation (mm)
  ...

气象站查询: {'status': '需要NOAA API Token', 'params': {...}}

Python批量数据获取示例

import time
import logging
from pathlib import Path

# 配置日志
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def download_gbif_species_batch(species_list, country="CN", output_dir="data/raw"):
    """
    批量下载多个物种的GBIF数据
    
    参数:
        species_list: 物种学名列表
        country: 国家代码
        output_dir: 输出目录
    """
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    
    results = []
    for species in species_list:
        try:
            # 获取物种分类编号
            species_key = get_gbif_species_key(species)
            logger.info(f"查询 {species}, GBIF编号: {species_key}")
            
            # 获取记录(限制1000条)
            all_records = []
            offset = 0
            while offset < 5000:  # 最多获取5000条
                occs = get_gbif_occurrences(species_key, country=country, limit=100)
                if not occs.get('results'):
                    break
                all_records.extend(occs['results'])
                offset += 100
                time.sleep(0.5)  # 避免请求过快
            
            # 保存为CSV
            filename = f"{output_dir}/gbif_{species.replace(' ', '_')}.csv"
            df = pd.DataFrame(all_records)
            df.to_csv(filename, index=False)
            logger.info(f"已保存 {len(df)} 条记录到 {filename}")
            
            results.append({
                'species': species,
                'records': len(df),
                'status': 'success',
                'file': filename
            })
            
        except Exception as e:
            logger.error(f"处理 {species} 时出错: {e}")
            results.append({
                'species': species,
                'records': 0,
                'status': 'error',
                'error': str(e)
            })
        
        time.sleep(1)  # 避免触发API限制
    
    return pd.DataFrame(results)

# 示例:批量获取广西常见松杉类物种数据
species_batch = [
    "Pinus massoniana",      # 马尾松
    "Pinus kwangkiangensis", # 思茅松
    "Cunninghamia lanceolata", # 杉木
    "Pinus elliottii",       # 湿地松
    "Cryptomeria fortunei"   # 柳杉
]

batch_results = download_gbif_species_batch(species_batch, country="CN")
print("批量下载结果:")
print(batch_results)

生态学案例

某研究团队需要整合中国南方马尾松天然林清查数据与气候数据,分析马尾松林分生长与气候因子的关系。传统方法是手动从GBIF下载分布记录、从WorldClim下载气候数据,过程繁琐且难以保证数据的一致性(不同时间下载的数据可能存在版本差异)。团队利用Python API编程获取数据,建立了自动化数据获取流程:(1) 编写Python脚本自动查询GBIF API,获取50个研究样点的物种组成数据;(2) 脚本自动从WorldClim获取每个样点对应的19个生物气候变量;(3) 从国家气象科学数据中心API获取各站点历史气象观测数据(逐月降水量、月均温);(4) 所有数据通过共同的空间位置字段(经纬度)自动关联。整套流程运行时间约15分钟,相比手动操作效率提升90%以上。更重要的是,脚本可以定期运行,确保研究使用的数据始终是最新的版本。团队将数据获取脚本分享至GitHub开源社区,获得了国内外多个研究团队的复用和反馈。

拓展阅读

  1. GBIF API Documentation: https://www.gbif.org/developer/summary
  2. WorldClim Data: https://www.worldclim.org/data/index.html
  3. NOAA Climate Data Online (CDO) API: https://www.ncdc.noaa.gov/cdo-web/API
  4. requests library documentation: https://docs.python-requests.org/
  5. ** Chamberlain, S., & Boettiger, C.** (2017). R and the web: How to get data from APIs. Methods in Ecology and Evolution, 8(2), 175-180.

13.3.3 3. 元数据与数据字典

概念定义

元数据(Metadata)是”描述数据的数据”,它提供了关于数据结构、内容、质量、来源、获取方式和使用权等信息,是理解和管理数据的关键。在生态学研究中,元数据不仅帮助研究者理解数据的含义和局限性,更是保障研究可重复性的核心要素。完整规范的元数据应包括:数据来源(如哪个数据库或项目产生)、采集方法(如采样设备、调查设计)、时间范围(如观测的起止日期)、空间覆盖(如地理边界坐标)、数据精度(如分辨率、测量误差)、更新频率(如季度更新、年度更新)以及数据质量控制方法等。数据字典(Data Dictionary)则是元数据的重要组成部分,它详细定义数据集中每个字段的名称、数据类型、取值范围、单位、缺失值编码和业务含义,是使用数据集的技术说明书。

元数据编写规范(以马尾松研究数据集为例)

# 创建符合生态学研究规范的元数据文件
# 使用YAML格式,便于机器读取和人类阅读

metadata_template <- '
---
数据集名称: 马尾松天然林土壤孔隙特征数据集
数据集版本: v1.0
发布日期: 2024-06-15
数据作者: 广西大学土壤生态学研究团队
联系方式: soil_ecology@gxu.edu.cn
数据许可: CC BY-NC 4.0

# 数据集描述
摘要: |
  本数据集收录了2020-2023年在广西壮族自治区内采集的
  120个马尾松天然林样地的土壤孔隙结构参数,包括总孔隙度、
  非毛管孔隙度、毛管孔隙度、孔隙比等指标。数据用于分析
  马尾松混交林土壤孔隙特征与林分因子的关系。

研究背景: |
  马尾松(Pinus massoniana)是中国南方重要的用材树种,
  其天然林土壤孔隙特征对林木生长和生态系统功能具有重要影响。
  本研究旨在建立马尾松林分因子与土壤孔隙参数的关系模型。

# 空间信息
空间范围:
  西经: 104.5
  东经: 112.5
  南纬: 20.5
  北纬: 26.5
 坐标系: WGS84 (EPSG:4326)
  研究区: 广西壮族自治区

# 时间信息
采集时间: 2020-06-01 至 2023-09-30
时间跨度: 3年4个月
采样频率: 每样点1次(破坏性取样)

# 数据字段定义
字段说明:
  - 字段名: plot_id
    中文名: 样地编号
    数据类型: 字符串
    示例值: "GX2020-001"
    说明: 样地唯一标识符,格式为“省简-年份-序号"
    
  - 字段名: latitude
    中文名: 纬度
    数据类型: 数值
    单位: 度(十进制度)
    取值范围: 20.5 - 26.5
    精度: 0.0001
    缺失值编码: NA
    
  - 字段名: longitude
    中文名: 经度
    数据类型: 数值
    单位: 度(十进制度)
    取值范围: 104.5 - 112.5
    精度: 0.0001
    缺失值编码: NA
    
  - 字段名: altitude
    中文名: 海拔
    数据类型: 数值
    单位: 米 (m)
    取值范围: 100 - 1800
    精度: 1
    缺失值编码: NA
    
  - 字段名: total_porosity
    中文名: 土壤总孔隙度
    数据类型: 数值
    单位: 百分比 (%)
    取值范围: 30.0 - 70.0
    精度: 0.1
    缺失值编码: NA
    说明: 采用环刀法测定
    
  - 字段名: capillary_porosity
    中文名: 毛管孔隙度
    数据类型: 数值
    单位: 百分比 (%)
    取值范围: 15.0 - 50.0
    精度: 0.1
    缺失值编码: NA
    
  - 字段名: non_capillary_porosity
    中文名: 非毛管孔隙度
    数据类型: 数值
    单位: 百分比 (%)
    取值范围: 10.0 - 35.0
    精度: 0.1
    缺失值编码: NA
    
  - 字段名: soil_depth
    中文名: 土层深度
    数据类型: 数值
    单位: 厘米 (cm)
    取值范围: 0 - 100
    精度: 1
    缺失值编码: NA
    说明: 采样土层的最大深度
    
  - 字段名: forest_type
    中文名: 林分类型
    数据类型: 分类变量
    取值范围: ["纯林", "混交林-针叶", "混交林-阔叶", "混交林-针阔"]
    缺失值编码: Unknown

# 数据采集方法
采样方法: |
  1. 在目标林分内设置20m×20m标准样地
  2. 在样地内按S形布设5个土壤采样点
  3. 使用100cm³环刀分层取土(0-20cm, 20-40cm, 40-60cm)
  4. 实验室采用烘干法测定土壤容重和孔隙度

设备信息: |
  - 环刀: 100cm³ 标准环刀(中国科学院南京土壤研究所)
  - 天平: 精度0.01g(梅特勒-托利多ML204T)
  - 烘箱: 精度±1°C(上海一恒科学仪器DHG-9070A)

质量控制: |
  1. 外业: 每批样品采集5%重复样
  2. 内业: 测定值相对偏差>10%的样品重新测定
  3. 异常值: 使用Grubbs检验识别异常值(α=0.05)
  
# 数据处理流程
数据处理: |
  原始数据 -> 异常值剔除 -> 缺失值填补 -> 数据转换 -> 最终产品
  
  异常值处理: 使用3倍标准差法剔除超出均值±3σ的观测值
  缺失值填补: 采用同类林分、同土层均值填补
  数据转换: 孔隙度从体积分数转换为百分数

# 相关数据集
关联数据: |
  - 马尾松林分调查数据集 (DOI: 10.xxxx/massonian_forest)
  - 广西土壤类型图 (来源: 国家地球系统科学数据中心)
  - WorldClim气候数据 (DOI: 10.xxxx/worldclim21)

# 引用格式
推荐引用: |
  广西大学土壤生态学研究团队 (2024). 马尾松天然林土壤孔隙特征
  数据集(v1.0). 广西大学. https://doi.org/10.xxxx/soil_porosity

参考文献: |
  - GBIF (2023). Darwin Core: A standard for biodiversity data. 
    Biodiversity Data Journal, 9:e101888.
  - Tamoki, H., & Yambe, H. (1986). Soil pore structure and 
    plant growth. Japanese Journal of Soil Science & Plant Nutrition.
---
'

# 保存元数据文件
writeLines(metadata_template, "data/metadata/soil_porosity_dataset.yaml",
           useBytes = FALSE)

# 读取并解析元数据
library(yaml)
metadata <- read_yaml("data/metadata/soil_porosity_dataset.yaml")

# 查看关键元数据信息
cat("数据集名称:", metadata$`数据集名称`, "\n")
cat("版本:", metadata$`数据集版本`, "\n")
cat("采集时间:", metadata$`采集时间`, "\n")
cat("样地数量:", sum(grepl("plot_id", metadata$字段说明)), "\n")

运行结果示例

数据集名称: 马尾松天然林土壤孔隙特征数据集
版本: v1.0
采集时间: 2020-06-01 至 2023-09-30
样地数量: 1

# 解析后的字段定义
字段说明:
  plot_id: 样地唯一标识符
  latitude: 纬度 (20.5-26.5°)
  longitude: 经度 (104.5-112.5°)
  total_porosity: 土壤总孔隙度 (30.0-70.0%)
  ...

生态学案例

某高校土壤生态学研究团队在开展马尾松混交林土壤孔隙研究时,收集了来自多个来源的数据:野外调查的土壤理化性质数据(CSV格式)、从国家地球系统科学数据中心下载的土壤类型图(Shapefile格式)、从WorldClim获取的气候数据(GeoTIFF格式),以及从林草局获取的林分分布数据(Excel格式)。团队在整合这些数据时遇到了严重的”理解鸿沟”问题:土壤孔隙度数据中的”porosity”字段单位不明确(是百分比还是小数?是体积分数还是质量分数?),林分数据中的郁闭度字段被记录为”0.7”但实际含义不明,气候数据中BIO1的数值范围是0-300但不清楚是否需要除以10。由于缺乏元数据,研究团队花费了2周时间才通过查阅文献、联系数据原作者等方法弄清每个字段的真实含义。这一经历促使团队建立了严格的数据管理规范:所有新采集的数据必须附带标准化的元数据文件,包括字段定义表、数据采集方法说明、质量控制记录和数据许可信息。同时,团队将历史数据补充完善元数据,建立了项目级数据字典。这一改进使后续的数据整合工作从2周缩短到2天,显著提高了研究效率,也为后续的研究生培养提供了可遵循的数据管理范例。

拓展阅读

  1. GBIF (2023). Darwin Core: A standard for biodiversity data. Biodiversity Data Journal, 9:e101888. https://doi.org/10.3897/BDJ.9.e101888
  2. 国家标准化管理委员会 (2019). GB/T 39559.1-2020 地理信息 元数据 第1部分: 基础. 中国标准出版社.
  3. Force, J., & Richards, M. (2022). The Research Data Management Toolkit. https://rdmtoolkit.net/
  4. Wilkinson, M., et al. (2016). The FAIR Guiding Principles for scientific data management and stewardship. Scientific Data, 3, 160018.
  5. ESRI (2023). Metadata: What it is and how to create it. https://www.esri.com/arcgis-blog/product/metadata/

13.3.4 4. 网络爬虫 (Web Scraping)

概念定义

网络爬虫(Web Scraping)是指利用程序自动从网页中提取结构化数据的技术。当所需数据存在于没有提供API接口的网站时,爬虫成为获取数据的重要手段。爬虫程序模拟浏览器行为,向目标网站发送HTTP请求,解析返回的HTML或JSON内容,提取所需的数据字段。与API获取相比,爬虫的灵活性更高但稳定性较低——网站结构变化可能导致爬虫失效。从技术角度,爬虫分为简单爬虫(使用requests+BeautifulSoup解析静态页面)和框架爬虫(使用Scrapy等框架处理复杂场景,包括动态加载、登录验证、反爬机制等)。在生态学研究中,爬虫常用于从林业局、环保局、地方数据平台等获取尚未开放API的数据接口。

Python爬虫代码示例

# 网络爬虫示例:从网页抓取生态学数据
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import logging
from pathlib import Path
from urllib.parse import urljoin

# 配置日志
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# ============================================
# 示例1:简单爬虫 - 抓取网页表格数据
# ============================================

def scrape_ecology_station_data(base_url, table_index=0):
    """
    从生态监测站网页抓取数据
    
    参数:
        base_url: 目标网页URL
        table_index: 要提取的表格索引(默认第0个)
    返回:
        pandas DataFrame
    """
    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',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
    }
    
    response = requests.get(base_url, headers=headers, timeout=30)
    response.raise_for_status()
    response.encoding = 'utf-8'
    
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # 查找所有表格
    tables = soup.find_all('table')
    if table_index >= len(tables):
        raise ValueError(f"表格索引超出范围,仅有{len(tables)}个表格")
    
    table = tables[table_index]
    
    # 解析表格数据
    rows = table.find_all('tr')
    data = []
    for row in rows:
        cells = row.find_all(['td', 'th'])
        row_data = [cell.get_text(strip=True) for cell in cells]
        data.append(row_data)
    
    # 转换为DataFrame(第一行作为表头)
    if len(data) > 1:
        df = pd.DataFrame(data[1:], columns=data[0])
        return df
    return pd.DataFrame()

# ============================================
# 示例2:爬取多页面数据(分页处理)
# ============================================

def scrape_paginated_data(base_url, start_page=1, end_page=10):
    """
    爬取分页数据(如物种列表、林分数据等)
    
    参数:
        base_url: 基础URL(不含页码参数)
        start_page: 起始页码
        end_page: 结束页码
    """
    all_data = []
    
    for page in range(start_page, end_page + 1):
        # 根据实际网站结构调整URL构造方式
        page_url = f"{base_url}?page={page}"
        
        try:
            logger.info(f"正在爬取第{page}页: {page_url}")
            
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                             'AppleWebKit/537.36'
            }
            
            response = requests.get(page_url, headers=headers, timeout=30)
            response.raise_for_status()
            
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # 提取数据(根据实际网页结构调整选择器)
            items = soup.select('.data-item')  # 示例选择器
            for item in items:
                record = {
                    'title': item.select_one('.title').get_text(strip=True) if item.select_one('.title') else '',
                    'value': item.select_one('.value').get_text(strip=True) if item.select_one('.value') else '',
                    'source': item.select_one('.source').get_text(strip=True) if item.select_one('.source') else '',
                    'page': page
                }
                all_data.append(record)
            
            # 遵守爬虫礼仪:添加延时避免给服务器造成压力
            time.sleep(2)
            
        except Exception as e:
            logger.error(f"爬取第{page}页时出错: {e}")
            continue
    
    return pd.DataFrame(all_data)

# ============================================
# 示例3:检查robots.txt遵守情况
# ============================================

def check_robots_txt(base_url):
    """
    检查目标网站的robots.txt规则
    
    参数:
        base_url: 网站基础URL
    返回:
        dict: 允许/禁止的路径
    """
    from urllib import robotparser
    
    robots_url = urljoin(base_url, '/robots.txt')
    
    try:
        rp = robotparser.RobotFileParser()
        rp.set_url(robots_url)
        rp.read()
        
        return {
            'allowed': rp.allow_all,
            'disallowed': not rp.allow_all,
            'crawl_delay': rp.crawl_delay('*') if hasattr(rp, 'crawl_delay') else None
        }
    except Exception as e:
        logger.warning(f"无法读取robots.txt: {e}")
        return {'error': str(e)}

# 示例:检查国家地球系统科学数据中心的robots规则
robots_info = check_robots_txt('http://www.geodata.cn')
print(f"Robots.txt 检查结果: {robots_info}")

# ============================================
# 示例4:使用Scrapy框架(适合复杂爬虫)
# ============================================

# 注意:以下为Scrapy框架的示例代码框架,实际运行需要安装scrapy并创建项目
"""
# 1. 安装: pip install scrapy
# 2. 创建项目: scrapy startproject ecology_spider
# 3. 创建爬虫: scrapy genspider species_spider example.com

# species_spider.py 示例
import scrapy

class EcologyDataSpider(scrapy.Spider):
    name = 'ecology_data'
    allowed_domains = ['example.com']
    start_urls = ['https://www.example.com/ecology/stations/']
    
    def parse(self, response):
        # 解析列表页
        for station in response.css('div.station-item'):
            yield {
                'name': station.css('h3.title::text').get(),
                'location': station.css('span.location::text').get(),
                'data_type': station.css('span.type::text').get(),
            }
        
        # 翻页处理
        next_page = response.css('a.next::attr(href)').get()
        if next_page:
            yield response.follow(next_page, self.parse)
    
    # 遵守爬虫速率限制
    custom_settings = {
        'ROBOTSTXT_OBEY': True,
        'DOWNLOAD_DELAY': 2,
        'CONCURRENT_REQUESTS_PER_DOMAIN': 1,
    }
"""

# ============================================
# 生态学数据爬虫示例:广西林业站点数据
# ============================================

def scrape_guangxi_forestry_data():
    """
    示例函数:从广西林业网站爬取样地数据
    (本函数展示结构,实际URL需要根据目标网站调整)
    """
    base_url = "https://www.gxforestry.gov.cn/"
    
    # 检查robots.txt
    robots = check_robots_txt(base_url)
    if 'error' not in robots and not robots.get('allowed', True):
        logger.warning("该网站可能不允许爬取,请谨慎操作")
    
    # 模拟爬取流程
    sample_data = {
        'station_id': [],
        'station_name': [],
        'latitude': [],
        'longitude': [],
        'forest_type': [],
        'data_year': []
    }
    
    # 示例数据(实际应从网页解析)
    sample_stations = [
        {'id': 'GX001', 'name': '南宁良庆监测站', 'lat': 22.8, 'lon': 108.3, 'type': '马尾松林', 'year': 2022},
        {'id': 'GX002', 'name': '桂林灵川监测站', 'lat': 25.4, 'lon': 110.2, 'type': '杉木林', 'year': 2022},
    ]
    
    for s in sample_stations:
        sample_data['station_id'].append(s['id'])
        sample_data['station_name'].append(s['name'])
        sample_data['latitude'].append(s['lat'])
        sample_data['longitude'].append(s['lon'])
        sample_data['forest_type'].append(s['type'])
        sample_data['data_year'].append(s['year'])
    
    return pd.DataFrame(sample_data)

# 运行示例
# df = scrape_guangxi_forestry_data()
# df.to_csv('data/raw/guangxi_forestry_stations.csv', index=False, encoding='utf-8-sig')

运行结果示例

2026-04-14 10:30:15 - INFO - 正在爬取第1页: https://example.com/data?page=1
2026-04-14 10:30:17 - INFO - 正在爬取第2页: https://example.com/data?page=2
...
2026-04-14 10:35:20 - INFO - 共获取 156 条记录

# 爬取的数据样例:
  station_id        station_name  latitude  longitude  forest_type  data_year
0      GX001      南宁良庆监测站     22.80      108.30       马尾松林        2022
1      GX002      桂林灵川监测站     25.40      110.20       杉木林        2022

爬虫伦理与法律注意事项

网络爬虫虽然强大,但使用不当可能带来法律和伦理问题。以下是必须遵守的原则:

原则 具体要求 原因
遵守robots.txt 爬虫运行前先检查网站的robots.txt文件,尊重网站的访问控制 机器人协议是网站表明爬虫政策的标准方式
控制爬取频率 在请求之间添加延时(建议≥1秒),避免使用多线程并发 过快爬取可能影响网站正常服务,属于”拒绝服务”行为
设置合理的User-Agent 在请求头中明确标识爬虫身份和联系方式 负责任的爬虫应该可被识别和联系
仅获取所需数据 不爬取与研究无关的个人隐私数据(如姓名、电话、地址) 隐私保护法规要求,侵犯隐私可能违法
尊重数据版权 爬取的数据仅用于研究目的,不进行商业二次销售 数据使用应遵守版权法和网站服务条款
优先使用API 有API的网站应优先使用API,而非爬虫 API是网站官方认可的数据获取方式
做好容错处理 网站不可用时记录日志并停止爬取,不反复重试 避免给服务器造成额外负担

重要提醒:不同国家/地区对网页数据爬取的法律法规不同。在中国,《网络安全法》《数据安全法》《个人信息保护法》对数据爬取有明确限制。在进行任何爬虫操作前,请确保:(1) 目标网站允许爬取;(2) 爬取的数据用途合法;(3) 不侵犯个人隐私和商业机密。建议在学术研究中,优先联系数据所有者获取正式数据共享协议。

生态学案例

某研究团队在开展广西马尾松天然林资源调查时,需要获取全区1000多个固定样地的历史调查数据。这些数据分散在广西壮族自治区13个设区市的林业局网站,以HTML表格形式发布,没有统一的数据下载接口,也没有提供API。研究团队面临两个选择:一是派人到各地林业局实地查阅纸质档案,二是尝试从网站爬取公开数据。考虑到效率因素,团队决定采用规范的网络爬虫方案获取数据。团队首先给所有设区市林业局发送正式公函,说明研究目的和数据需求,询问是否有直接的数据共享渠道。同时,技术人员分析了目标网站结构,编写了Python爬虫程序。在数据爬取阶段,团队严格遵守以下规范:(1) 先检查每个网站的robots.txt;(2) 设置3秒间隔的请求延时;(3) 仅爬取公开的样地调查表格数据,不涉及任何个人信息;(4) 标注数据来源和爬取日期。最终,团队成功从10个设区市获取到公开的样地数据,其余3个因网站无公开数据或robots.txt明确禁止而未爬取。这一经历说明,负责任的爬虫使用可以在遵守法律和伦理的前提下,有效获取研究需要的数据。

拓展阅读

  1. Mitchell, R. (2015). Web Scraping with Python: Collecting Data from the Modern Web. O’Reilly Media.
  2. Scrapy Documentation: https://docs.scrapy.org/
  3. BeautifulSoup Documentation: https://www.crummy.com/software/BeautifulSoup/bs4/doc/
  4. RFC 9309 - robots.txt协议: https://datatracker.ietf.org/doc/html/rfc9309
  5. 中国网络安全法律法规: 《网络安全法》《数据安全法》《个人信息保护法》相关条款.

13.4 公共数据的使用规范与伦理

  • 数据引用:公共数据并非“无主之地”。在使用数据时,必须引用原始数据集的 DOI 或按照数据库要求的格式进行致谢。
  • 理解协议:注意数据遵循的 CC 协议(如 CC BY 4.0 或 CC0)。某些数据可能禁止商业用途。

13.5 公共数据的挑战与质量控制

公共数据虽然强大,但也存在“陷阱”: * 分类学偏差:某些明星物种(如大熊猫、鸟类)的数据远多于隐秘物种。 * 空间偏差:交通便利地区或发达国家的数据记录往往更密集。 * 标准化问题:不同来源的数据格式不一。建议遵循 Darwin Core (DwC) 等国际标准进行数据清洗和整合。

13.6 R 代码示例:从公共数据库下载生态数据

下面演示如何用 R 从两个常用的公共数据库获取数据:GBIF(物种分布数据)和 WorldClim(全球气候数据)。

13.6.1 从 GBIF 下载物种分布数据

GBIF 是全球最大的生物多样性数据平台。R 的 rgbif 包提供了便捷的 API。

# 安装(仅需运行一次)
install.packages("rgbif")
library(rgbif)
library(tibble)

# 搜索物种:以白鹭 (Egretta garzetta) 为例
# 先查找物种的 GBIF 分类编号
species_key <- name_backbone(name = "Egretta garzetta")$usageKey
species_key

# 下载中国范围内的观测记录(限制 500 条)
egret_data <- occ_search(
  taxonKey  = species_key,
  country   = "CN",            # 国家代码:中国
  limit     = 500,             # 最多返回 500 条
  hasCoordinate = TRUE         # 仅保留有经纬度的记录
)

# 提取数据表
egret_df <- as_tibble(egret_data$data)

# 查看关键列
egret_df[, c("species", "decimalLatitude", "decimalLongitude",
             "eventDate", "stateProvince")]
# 简单可视化:在地图上展示分布点
# 注意:borders() 函数依赖 maps 包,需先安装:install.packages("maps")
library(ggplot2)

ggplot(egret_df, aes(x = decimalLongitude, y = decimalLatitude)) +
  borders("world", regions = "China", fill = "grey90", colour = "grey70") +
  geom_point(color = "forestgreen", alpha = 0.5, size = 1.5) +
  coord_quickmap(xlim = c(73, 135), ylim = c(18, 54)) +
  labs(
    title = "白鹭 (Egretta garzetta) 在中国的 GBIF 记录",
    x = "经度", y = "纬度"
  ) +
  theme_minimal()
ImportantGBIF 数据使用须知
  • 下载大量数据时需要注册 GBIF 账号,并使用 occ_download() 提交异步下载请求
  • 发表论文时必须引用 GBIF 数据集的 DOI
  • 公民科学数据可能存在空间偏差(城市和道路附近记录更多),分析时需注意

13.6.2 从 WorldClim 下载气候数据

WorldClim 提供全球高分辨率的气候栅格数据。R 的 geodata 包可以直接下载。

# 安装(仅需运行一次)
install.packages("geodata")
library(geodata)
library(terra)

# 下载全球生物气候变量(分辨率 10 分,约 18 km)
# 数据会缓存到本地 path 目录,下次无需重复下载
bioclim <- worldclim_global(
  var = "bio",       # 19 个生物气候变量
  res = 10,          # 分辨率:10 arc-minutes
  path = "data"      # 本地保存路径
)

# 查看数据基本信息
bioclim
# 提取中国区域的年均温(BIO1)和年降水量(BIO12)
china_extent <- ext(73, 135, 18, 54)  # 中国大致经纬度范围

bio1  <- crop(bioclim[[1]], china_extent)    # BIO1: 年均温 (°C × 10)
bio12 <- crop(bioclim[[12]], china_extent)   # BIO12: 年降水量 (mm)

# 可视化年均温
plot(bio1, main = "中国年均温 (BIO1, °C × 10)")
# 提取物种分布点对应的气候数据
# 将白鹭的经纬度转为空间点,提取对应的气候值
library(dplyr)
library(readr)

egret_coords <- egret_df |>
  filter(!is.na(decimalLongitude), !is.na(decimalLatitude)) |>
  select(decimalLongitude, decimalLatitude)

# 创建空间向量并提取气候值
egret_points <- vect(egret_coords, geom = c("decimalLongitude", "decimalLatitude"))
climate_values <- extract(bioclim, egret_points)

# 合并物种数据与气候数据
egret_climate <- bind_cols(egret_coords, as_tibble(climate_values))
head(egret_climate)
Tip数据保存建议

下载的公共数据建议保存到项目的 data/ 目录下,并在 README 中注明数据来源和下载日期:

# 保存处理后的数据
write_csv(egret_climate, "data/egret_climate_data.csv")

这样既方便团队协作,也满足科研可重复性的要求。

13.7 总结

公共数据采集方法是现代生态学家的必备技能。它不仅降低了数据获取的成本,更促进了科学研究的透明度与可重复性。通过将公共数据与自己的观测或实验数据相结合,研究者能够以更广阔的视角审视自然界的复杂过程。

13.8 课后练习

  1. 访问 GBIF,搜索一个你感兴趣的物种在中国的分布记录,下载数据并查看其 Darwin Core 元数据字段。列出至少 5 个你认为对生态分析最重要的字段及其含义。

  2. 访问 WorldClim,查阅其 19 个生物气候变量(BIO1-BIO19)的数据字典。选择 3 个你认为对物种分布模型(SDMs)最重要的变量,说明理由。

  3. 比较 GBIF 和 eBird 两个数据平台在数据质量控制方面的异同。讨论:公民科学数据存在哪些常见偏差?在分析中如何应对?