Scrapy爬虫抓取的数据如何快速转为GIS矢量图层?(附:空间坐标自动匹配脚本)

编程与开发
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

引言

Scrapy爬虫抓取的数据如何快速转为GIS矢量图层?(附:空间坐标自动匹配脚本)这个问题,常见于把网站POI、公告地址、监测点、项目清单等网页数据采集下来之后,下一步却卡在“怎么放到QGIS或ArcGIS Pro里成图”。

很多Scrapy结果只是CSV、JSON或数据库表,里面可能有经纬度,也可能只有地址字段;坐标字段名称还可能是lnglonxlongitude等不同写法。本文给你一套实用流程:先识别字段,再自动匹配空间坐标,最后输出GeoJSON、Shapefile或GeoPackage,直接加载为GIS矢量图层。

Scrapy爬虫抓取的数据转GIS矢量图层与空间坐标自动匹配流程
Scrapy采集结果转为GIS矢量图层的典型流程:字段清洗、坐标识别、坐标系检查、矢量文件输出。

背景

Scrapy适合批量抓取结构化网页数据,但它本身不是GIS工具。它输出的数据通常是:

  • CSV:适合表格数据交换,QGIS和ArcGIS Pro都能直接读取。
  • JSON:适合保留嵌套结构,但需要进一步转换为地理数据格式。
  • 数据库表:如SQLite、MySQL、PostgreSQL,用于持续采集和增量更新。

如果爬虫结果中已经包含经纬度字段,把Scrapy数据转GIS矢量图层并不复杂。真正容易出错的是以下几类情况:

  • 经度字段名称不统一,例如lnglonx经度
  • 纬度字段名称不统一,例如latylatitude纬度
  • 坐标值被保存为字符串,带有空格、中文符号或单位。
  • 经纬度顺序颠倒,导致点位落到错误国家或海上。
  • 数据是GCJ-02、BD-09或Web墨卡托坐标,但被误认为WGS84。

因此,快速转图层的关键不只是“导出文件”,而是先判断字段、坐标值和坐标系是否可信。

原理

GIS矢量图层至少需要两部分信息:一是属性字段,二是几何对象。对Scrapy采集的点数据来说,几何对象通常由经度和纬度生成。

在Python GIS处理中,常用做法是:

  1. pandas读取Scrapy导出的CSV或JSON。
  2. 自动识别经度字段和纬度字段。
  3. 把坐标字段转换为数值类型。
  4. 过滤无效坐标,例如空值、超出范围值。
  5. GeoPandas生成点几何。
  6. 指定坐标参考系,通常是EPSG:4326
  7. 导出为GeoJSON、GeoPackage或Shapefile。

其中EPSG:4326指WGS84地理坐标系,是大多数GPS经纬度数据和GeoJSON常用的坐标系。如果你的数据来源是国内互联网地图接口,可能不是WGS84,而是GCJ-02或BD-09,这一点要特别核查。

步骤

步骤一:让Scrapy输出结构化数据

建议先让Scrapy导出为CSV或JSON Lines。对于GIS转换来说,字段越扁平越好,避免把坐标藏在复杂嵌套对象里。

scrapy crawl poi_spider -O output.csv
scrapy crawl poi_spider -O output.jsonl

一个适合转GIS矢量图层的Scrapy结果,至少应包含名称和空间位置字段,例如:

name address lng lat category
某某学校 某市某区某路 116.3912 39.9075 教育

步骤二:安装Python GIS依赖

推荐使用独立环境安装,避免和爬虫项目依赖冲突。

pip install pandas geopandas pyogrio shapely

如果你在Windows环境中安装GeoPandas遇到依赖问题,可以优先使用Conda环境:

conda create -n scrapy_gis python=3.11
conda activate scrapy_gis
conda install -c conda-forge pandas geopandas pyogrio shapely

步骤三:使用空间坐标自动匹配脚本

下面脚本适合处理Scrapy导出的CSV或JSON Lines文件。它会自动查找常见经纬度字段名,清洗坐标值,并输出GeoJSON和GeoPackage。

import re
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
from pathlib import Path

INPUT_FILE = "output.csv"
OUTPUT_GEOJSON = "scrapy_points.geojson"
OUTPUT_GPKG = "scrapy_points.gpkg"
LAYER_NAME = "scrapy_points"

LON_CANDIDATES = [
    "lon", "lng", "longitude", "x", "经度", "lon_wgs84", "lng_wgs84",
    "location_lon", "location_lng", "gcj_lng", "bd_lng"
]

LAT_CANDIDATES = [
    "lat", "latitude", "y", "纬度", "lat_wgs84",
    "location_lat", "gcj_lat", "bd_lat"
]

def normalize_col(col):
    return str(col).strip().lower().replace(" ", "_")

def read_scrapy_file(path):
    path = Path(path)
    if path.suffix.lower() == ".csv":
        return pd.read_csv(path)
    if path.suffix.lower() in [".jsonl", ".jl"]:
        return pd.read_json(path, lines=True)
    if path.suffix.lower() == ".json":
        return pd.read_json(path)
    raise ValueError("暂不支持该文件格式,请使用 CSV、JSON 或 JSONL。")

def find_coordinate_fields(df):
    original_cols = list(df.columns)
    normalized_map = {normalize_col(c): c for c in original_cols}

    lon_field = None
    lat_field = None

    for name in LON_CANDIDATES:
        key = normalize_col(name)
        if key in normalized_map:
            lon_field = normalized_map[key]
            break

    for name in LAT_CANDIDATES:
        key = normalize_col(name)
        if key in normalized_map:
            lat_field = normalized_map[key]
            break

    if lon_field and lat_field:
        return lon_field, lat_field

    numeric_cols = []
    for col in original_cols:
        s = pd.to_numeric(df[col], errors="coerce")
        valid_ratio = s.notna().mean()
        if valid_ratio > 0.6:
            numeric_cols.append(col)

    for lon_col in numeric_cols:
        lon_values = pd.to_numeric(df[lon_col], errors="coerce")
        if not lon_values.between(-180, 180).mean() > 0.8:
            continue

        for lat_col in numeric_cols:
            if lon_col == lat_col:
                continue
            lat_values = pd.to_numeric(df[lat_col], errors="coerce")
            if lat_values.between(-90, 90).mean() > 0.8:
                return lon_col, lat_col

    raise ValueError("未能自动识别经纬度字段,请检查字段名或手动指定。")

def clean_number(value):
    if pd.isna(value):
        return None
    text = str(value).strip()
    text = text.replace(",", ".")
    match = re.search(r"-?d+(.d+)?", text)
    if not match:
        return None
    return float(match.group())

def build_point_layer(input_file):
    df = read_scrapy_file(input_file)

    lon_field, lat_field = find_coordinate_fields(df)
    print(f"识别到经度字段:{lon_field}")
    print(f"识别到纬度字段:{lat_field}")

    df["_lon"] = df[lon_field].apply(clean_number)
    df["_lat"] = df[lat_field].apply(clean_number)

    before_count = len(df)
    df = df.dropna(subset=["_lon", "_lat"]).copy()

    df = df[
        (df["_lon"] >= -180) &
        (df["_lon"] <= 180) &
        (df["_lat"] >= -90) &
        (df["_lat"] <= 90)
    ].copy()

    after_count = len(df)
    print(f"原始记录数:{before_count}")
    print(f"有效坐标记录数:{after_count}")

    geometry = [Point(xy) for xy in zip(df["_lon"], df["_lat"])]
    gdf = gpd.GeoDataFrame(df, geometry=geometry, crs="EPSG:4326")

    gdf.to_file(OUTPUT_GEOJSON, driver="GeoJSON")
    gdf.to_file(OUTPUT_GPKG, layer=LAYER_NAME, driver="GPKG")

    print(f"已输出:{OUTPUT_GEOJSON}")
    print(f"已输出:{OUTPUT_GPKG}")

if __name__ == "__main__":
    build_point_layer(INPUT_FILE)

运行脚本:

python scrapy_to_gis.py

运行成功后,你会得到两个文件:

  • scrapy_points.geojson:适合WebGIS、QGIS快速预览和数据交换。
  • scrapy_points.gpkg:适合长期保存,多图层管理能力更好。

步骤四:在QGIS中加载结果

  1. 打开QGIS。
  2. 点击“图层”菜单。
  3. 选择“添加图层”。
  4. 选择“添加矢量图层”。
  5. 加载scrapy_points.geojsonscrapy_points.gpkg
  6. 右键图层,查看“属性表”,确认爬虫字段是否完整。
  7. 添加底图,检查点位是否落在正确位置。

如果点位出现在非预期区域,优先检查经纬度顺序、坐标系来源和是否存在火星坐标偏移。

步骤五:在ArcGIS Pro中加载结果

  1. 打开ArcGIS Pro项目。
  2. 在Catalog中连接输出文件所在文件夹。
  3. scrapy_points.geojsonscrapy_points.gpkg拖入地图。
  4. 检查图层坐标系是否为WGS 1984。
  5. 打开属性表,检查字段编码和字段类型。

如果你需要把数据保存到文件地理数据库,可以在ArcGIS Pro中使用“要素类转要素类”工具进行转换。

常见坑

坑一:经纬度顺序写反

Scrapy数据转GIS矢量图层时,经度应该作为X,纬度应该作为Y。很多接口返回的是[lat, lon],但GIS点构造通常需要Point(lon, lat)

判断方法很简单:如果中国范围内的点,经度通常在73到135之间,纬度通常在18到54之间。如果经度字段出现大量30、40,而纬度字段出现大量110、120,就很可能写反了。

坑二:把GCJ-02或BD-09当成WGS84

国内不少互联网地图数据使用GCJ-02或BD-09坐标。QGIS、GeoJSON和大多数国际GIS工具通常默认使用WGS84。若坐标系不一致,点位会出现几十米到几百米偏移。

如果数据来自高德、腾讯等接口,常见是GCJ-02;如果来自百度地图,常见是BD-09。此时需要先进行坐标转换,再输出GIS矢量图层。

坑三:字段编码导致中文乱码

CSV在不同软件之间传递时容易出现中文乱码。建议优先使用UTF-8编码导出;如果给ArcGIS Pro或旧版软件使用,GeoPackage通常比CSV加Shapefile更稳妥。

坑四:Shapefile字段名被截断

Shapefile字段名长度有限,长字段会被截断。Scrapy抓取的数据字段往往较长,例如business_opening_hoursadministrative_division_name。如果字段较多,建议优先输出GeoPackage。

坑五:只有地址没有经纬度

如果Scrapy只抓到了地址,没有坐标,就不能直接生成点图层。你需要先做地理编码,也就是把地址转换为经纬度。地理编码结果还要检查匹配等级,不能把低置信度结果直接用于正式分析。

方法比较

方法 适用场景 优点 限制
CSV加经纬度直接加载 少量点数据、临时查看 操作简单,QGIS和ArcGIS Pro都支持 坐标字段和坐标系容易选错
Python脚本转GeoJSON WebGIS展示、QGIS快速预览 轻量、通用、便于自动化 大数据量时文件体积较大
Python脚本转GeoPackage 正式整理、长期保存、多字段数据 支持多图层,字段兼容性更好 部分Web前端不能直接加载
PostGIS入库 持续采集、空间查询、多人协作 适合空间索引和增量更新 需要数据库维护能力
地址地理编码后建图层 只有地址没有坐标 能把文本地址转换为空间点 依赖地理编码服务质量和配额

检查清单

在把Scrapy数据转GIS矢量图层之前,建议按下面清单检查一遍:

  • 是否有明确的经度字段和纬度字段?
  • 经度是否作为X,纬度是否作为Y?
  • 坐标值是否为数值,而不是带单位的字符串?
  • 坐标是否落在合理范围内?
  • 坐标系是否明确,是WGS84、GCJ-02、BD-09还是投影坐标?
  • 是否存在重复POI或重复记录?
  • 是否存在空坐标、零坐标或明显异常坐标?
  • 输出格式是否符合后续软件要求?
  • 中文字段和长字段是否能被目标格式正常保存?
  • 是否在QGIS或ArcGIS Pro中叠加底图验证过点位?

如果这十项都通过,Scrapy爬虫抓取的数据通常就可以比较可靠地进入GIS分析流程。

FAQ

Scrapy爬虫抓取的数据可以直接变成Shapefile吗?

可以,但不建议作为首选。Shapefile对字段名长度、字段类型和编码都有较多限制。更推荐先输出GeoPackage或GeoJSON。如果必须交付Shapefile,可以在GeoPandas中使用driver="ESRI Shapefile"导出。

空间坐标自动匹配脚本能识别所有字段名吗?

不能。脚本内置了常见字段名,并增加了数值范围判断,但如果你的字段名非常特殊,仍然需要手动添加到LON_CANDIDATESLAT_CANDIDATES中。自动识别只能提高效率,不能替代人工检查。

为什么导出的GeoJSON在地图上有偏移?

常见原因是坐标系不一致。比如原始数据是GCJ-02或BD-09,但导出时被标记为EPSG:4326。另一个原因是经纬度顺序写反。建议先确认数据来源,再叠加权威底图进行验证。

只有地址字段,没有经纬度,能不能转GIS矢量图层?

可以,但需要先地理编码。地理编码是把地址转换为坐标的过程。你可以使用合规的地理编码服务,得到经纬度后再运行本文脚本生成点图层。注意检查匹配结果,尤其是同名道路、同名小区和模糊地址。

GeoJSON和GeoPackage应该选哪个?

如果用于WebGIS展示或临时交换,GeoJSON更方便;如果用于GIS项目整理、长期保存或字段较多的数据,GeoPackage更稳妥。对于Scrapy采集的大批量POI数据,建议优先保存一份GeoPackage作为主数据。

Scrapy结果中有面或线数据怎么办?

如果网页数据中包含边界坐标串、轨迹点序列或WKT字段,就可以构造线或面几何。点数据使用Point,线数据使用LineString,面数据使用Polygon。但这需要原始数据结构足够规范,不能只靠单个经纬度字段完成。

结论

Scrapy爬虫抓取的数据要快速转为GIS矢量图层,核心流程是:整理结构化输出、识别经纬度字段、清洗坐标值、明确坐标系、生成几何对象、导出GIS格式。

本文提供的空间坐标自动匹配脚本适合处理常见POI、地址点、监测点和项目位置数据。实际项目中,不要只看脚本是否成功运行,更要在QGIS或ArcGIS Pro中叠加底图检查位置是否正确。

如果你的数据来自国内互联网地图接口,尤其要确认是否需要GCJ-02或BD-09到WGS84的转换。只有坐标字段、坐标顺序和坐标系都正确,Scrapy数据转GIS矢量图层才算真正完成。