Scrapy爬虫框架如何应用于GIS数据采集?(附:国土空间规划数据实战案例)

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

Scrapy爬虫框架如何应用于GIS数据采集?(附:国土空间规划数据实战案例)这个问题,适合用来理解“网络数据采集”和“GIS数据整理”之间的完整链路。很多 GIS 同学不是不会下载数据,而是遇到国土空间规划、自然资源公示、行政区划、项目选址公告这类网页时,不知道怎样批量提取表格、附件链接、坐标描述、行政区字段,并把结果整理成可用于 QGIS、ArcGIS Pro 或 PostGIS 的空间数据。

本文以 Scrapy 爬虫框架为主线,演示一个国土空间规划数据采集的实战思路:从网页列表抓取、详情页解析、附件下载、字段清洗,到最终生成 CSV、GeoJSON 或入库 PostGIS。重点不是“爬得越多越好”,而是让 GIS 数据采集过程可复现、可校验、可维护。

引言:为什么 GIS 数据采集适合使用 Scrapy 爬虫框架

GIS 数据采集经常面对三类网页数据:

  • 政府部门公开发布的规划公示、项目批前公示、用地预审、规划许可信息。
  • 列表页加详情页结构,例如标题、发布时间、行政区、项目名称、附件 PDF 或图片。
  • 半结构化文本,例如“项目位于某某街道以东、某某路以南”,需要后续地理编码或人工核验。

如果只是临时下载几个网页,浏览器复制粘贴就够了。但当数据跨多个分页、多个栏目、多个年份时,Scrapy 爬虫框架的优势就比较明显:它能管理请求队列、解析页面、控制下载速度、失败重试、数据管道输出,适合做稳定的 GIS 数据采集工作流。

注意:GIS 数据采集必须遵守目标网站的 robots 协议、版权声明、接口使用条款和数据安全要求。本文仅讨论公开网页数据的合规采集、整理与空间化方法,不建议绕过登录、验证码、访问控制或批量抓取敏感数据。

Scrapy爬虫框架 GIS数据采集 国土空间规划数据流程图
Scrapy 爬虫框架用于 GIS 数据采集时,建议把网页解析、附件下载、字段清洗和空间化输出拆成独立步骤。

背景:国土空间规划数据采集通常会遇到什么问题

以国土空间规划数据为例,公开网页中常见的数据并不总是标准 GIS 格式。很多信息分散在公告正文、表格、PDF 附件、图片扫描件中。即使网页上有“项目位置”字段,也不一定包含经纬度坐标。

常见问题包括:

  • 列表页只显示标题和日期,关键字段在详情页中。
  • 分页参数不明显,需要观察 URL 或浏览器网络请求。
  • 附件中包含规划范围图、控制线图、选址红线图,但文件名不规范。
  • 行政区、项目名称、建设单位等字段写法不统一。
  • 空间位置只有文字描述,无法直接生成点、线、面。
  • 网页编码、日期格式、空格换行、全角半角字符混杂。

因此,Scrapy 爬虫框架在 GIS 数据采集中的作用,不只是“抓网页”,更重要的是把非结构化网页内容整理成结构化字段,为后续 QGIS 制图、ArcGIS Pro 分析、PostGIS 查询打基础。

原理:Scrapy 如何服务于 GIS 数据采集

Scrapy 的核心工作流可以理解为四个部分:

  • Spider:定义从哪些 URL 开始抓取,如何解析列表页和详情页。
  • Request:生成新的网页请求,例如从列表页进入详情页,或翻到下一页。
  • Item:定义要提取的数据字段,例如标题、行政区、发布时间、附件链接、位置描述。
  • Pipeline:对数据进行清洗、去重、下载附件、输出 CSV、GeoJSON 或写入数据库。

和普通表格采集相比,GIS 数据采集还需要多考虑三件事:

  1. 空间字段:是否有经纬度、地址、行政区代码、规划范围描述或附件中的红线图。
  2. 坐标系统:如果后续生成 GeoJSON 或 Shapefile,要明确坐标参考系统,常见是 WGS 84,经纬度 EPSG:4326。
  3. 空间化方式:没有坐标时,可以先输出地址字段,再用地理编码、行政区匹配或人工矢量化补充空间几何。

一个比较稳妥的原则是:Scrapy 负责采集和结构化,GIS 软件负责空间校验和制图,PostGIS 负责批量存储和空间查询。不要把所有工作都塞进爬虫脚本里。

步骤:用 Scrapy 采集国土空间规划公开数据

步骤 1:确认数据源和采集边界

在写代码前,先记录目标网站的栏目结构。以一个典型国土空间规划公示栏目为例,需要确认:

  • 列表页 URL 和分页规则。
  • 详情页链接所在的 HTML 标签。
  • 是否有标题、发布时间、来源单位、行政区字段。
  • 附件链接是否为 PDF、DOC、JPG、PNG 或压缩包。
  • 网站是否允许公开访问和合理频率采集。

建议先手动打开 3 到 5 条详情页,判断页面结构是否稳定。GIS 数据采集最怕一开始就写大规模爬虫,结果发现不同年份、不同栏目模板完全不同。

步骤 2:创建 Scrapy 项目

在 Python 环境中安装 Scrapy,并创建项目:

pip install scrapy
scrapy startproject planning_crawler
cd planning_crawler
scrapy genspider planning example.gov.cn

项目结构大致如下:

planning_crawler/
  scrapy.cfg
  planning_crawler/
    items.py
    pipelines.py
    settings.py
    spiders/
      planning.py

如果后续要把数据写入 PostGIS,可以再安装 psycopg2 或 SQLAlchemy;如果要输出 GeoJSON,可以使用 GeoPandas 或 Python 标准 json 模块处理。

步骤 3:定义国土空间规划数据字段

items.py 中定义字段。字段不要一开始设计得太复杂,先覆盖网页中能稳定提取的内容。

import scrapy

class PlanningItem(scrapy.Item):
    title = scrapy.Field()
    publish_date = scrapy.Field()
    district = scrapy.Field()
    source_url = scrapy.Field()
    project_name = scrapy.Field()
    location_text = scrapy.Field()
    content_text = scrapy.Field()
    attachment_urls = scrapy.Field()
    attachment_names = scrapy.Field()
    longitude = scrapy.Field()
    latitude = scrapy.Field()

这里的 longitudelatitude 可以先为空。国土空间规划数据经常没有现成坐标,后续可以通过地址匹配、地理编码、行政区质心或人工矢量化补充。

步骤 4:解析列表页和详情页

下面是一个示意 Spider。实际项目中需要根据网页 HTML 结构修改 CSS 选择器或 XPath。

import scrapy
from planning_crawler.items import PlanningItem

class PlanningSpider(scrapy.Spider):
    name = "planning"
    allowed_domains = ["example.gov.cn"]
    start_urls = [
        "https://www.example.gov.cn/ghgs/index.html"
    ]

    custom_settings = {
        "DOWNLOAD_DELAY": 1.5,
        "CONCURRENT_REQUESTS": 4
    }

    def parse(self, response):
        links = response.css("ul.news-list li a::attr(href)").getall()

        for link in links:
            detail_url = response.urljoin(link)
            yield scrapy.Request(detail_url, callback=self.parse_detail)

        next_page = response.css("a.next::attr(href)").get()
        if next_page:
            yield scrapy.Request(response.urljoin(next_page), callback=self.parse)

    def parse_detail(self, response):
        item = PlanningItem()

        item["title"] = response.css("h2::text").get(default="").strip()
        item["publish_date"] = response.css(".date::text").get(default="").strip()
        item["source_url"] = response.url

        content_parts = response.css(".article *::text").getall()
        content_text = " ".join([t.strip() for t in content_parts if t.strip()])
        item["content_text"] = content_text

        item["district"] = self.extract_district(content_text)
        item["project_name"] = self.extract_project_name(item["title"], content_text)
        item["location_text"] = self.extract_location(content_text)

        attachment_urls = response.css(".article a::attr(href)").getall()
        item["attachment_urls"] = [
            response.urljoin(u) for u in attachment_urls
            if u.lower().endswith((".pdf", ".doc", ".docx", ".xls", ".xlsx", ".jpg", ".png", ".zip"))
        ]
        item["attachment_names"] = response.css(".article a::text").getall()

        yield item

    def extract_district(self, text):
        districts = ["东城区", "西城区", "朝阳区", "海淀区", "丰台区"]
        for d in districts:
            if d in text:
                return d
        return ""

    def extract_project_name(self, title, text):
        if title:
            return title.replace("批前公示", "").replace("规划公示", "").strip()
        return ""

    def extract_location(self, text):
        keywords = ["位于", "项目位置", "建设地点", "用地位置"]
        for kw in keywords:
            pos = text.find(kw)
            if pos != -1:
                return text[pos:pos + 120]
        return ""

这个示例的重点是结构:列表页负责发现详情页,详情页负责提取字段。实际采集国土空间规划数据时,不建议只保存整段正文,而应尽量拆出项目名称、行政区、位置描述、附件链接等字段。

步骤 5:清洗字段并输出 CSV

先输出 CSV 是最稳妥的调试方式。可以在命令行运行:

scrapy crawl planning -O planning_publicity.csv

如果中文出现乱码,建议在 settings.py 中设置:

FEED_EXPORT_ENCODING = "utf-8-sig"

CSV 可以直接导入 QGIS 或 ArcGIS Pro 查看字段质量。如果经纬度字段为空,也可以先作为普通表格加载,再根据行政区或地址字段进行后续空间化。

步骤 6:下载规划附件并建立本地索引

国土空间规划公示中,附件往往比网页正文更有价值。Scrapy 可以使用 Files Pipeline 下载附件。简化配置如下:

ITEM_PIPELINES = {
    "scrapy.pipelines.files.FilesPipeline": 1
}

FILES_STORE = "downloads"

如果使用默认 Files Pipeline,需要把附件字段命名为 file_urls。也可以在 Pipeline 中把 attachment_urls 转换成 file_urls,同时保存原始 URL、文件名、项目名称和来源页面,方便后续追溯。

建议建立一个附件索引表,至少包含:

字段 用途
project_name 关联项目或公示标题
source_url 追溯原始网页
attachment_url 记录附件原始地址
local_path 记录本地下载路径
file_type 区分 PDF、图片、表格或压缩包

步骤 7:把采集结果空间化为 GeoJSON

如果网页中已经包含经纬度,可以直接生成 GeoJSON。注意 GeoJSON 默认使用 WGS 84 坐标,即经度、纬度顺序。

import csv
import json

features = []

with open("planning_publicity.csv", "r", encoding="utf-8-sig") as f:
    reader = csv.DictReader(f)
    for row in reader:
        lon = row.get("longitude")
        lat = row.get("latitude")

        if not lon or not lat:
            continue

        try:
            lon = float(lon)
            lat = float(lat)
        except ValueError:
            continue

        feature = {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [lon, lat]
            },
            "properties": {
                "title": row.get("title", ""),
                "publish_date": row.get("publish_date", ""),
                "district": row.get("district", ""),
                "project_name": row.get("project_name", ""),
                "location_text": row.get("location_text", ""),
                "source_url": row.get("source_url", "")
            }
        }
        features.append(feature)

geojson = {
    "type": "FeatureCollection",
    "features": features
}

with open("planning_publicity.geojson", "w", encoding="utf-8") as f:
    json.dump(geojson, f, ensure_ascii=False, indent=2)

如果只有位置文字,没有坐标,不要随便伪造经纬度。可以先用行政区字段匹配区县面数据,做分区统计;或者通过权威地理编码服务生成候选点,再在 QGIS 中抽样核验。

步骤 8:导入 QGIS 检查采集质量

打开 QGIS 后,可以按以下方式检查:

  1. 加载 planning_publicity.csv,查看字段是否完整。
  2. 如果有经纬度,使用“添加分隔文本图层”,X 字段选择经度,Y 字段选择纬度。
  3. 坐标参考系统选择 EPSG:4326 - WGS 84
  4. 叠加行政区边界,检查点位是否落在合理区域。
  5. 按发布时间、行政区、项目类型做分类渲染,发现异常值。

GIS 数据采集不能只看爬虫日志成功率,更要看空间结果是否合理。例如,一个本应位于某区的规划点落到海上,通常说明经纬度顺序、坐标系或地理编码结果有问题。

常见坑:Scrapy 做 GIS 数据采集时最容易出错的地方

1. 把网页文本当成可靠空间坐标

很多国土空间规划网页中的“位置”只是自然语言描述,不是坐标。比如“位于某路以东、某河以北”,这类信息不能直接生成精确点位。正确做法是保存原文,并标记空间化状态。

2. 忽略坐标系和经纬度顺序

GeoJSON 坐标顺序是 [longitude, latitude],也就是经度在前、纬度在后。很多 GIS 初学者会写反,导致点位偏到错误区域。若数据来自国内互联网地图,还要注意可能存在 GCJ-02、BD-09 与 WGS 84 的差异。

3. 没有保存 source_url

GIS 数据采集一定要保留原始来源链接。后续做数据核验、成果审查、更新维护时,source_url 是最重要的追溯字段之一。

4. 只抓标题,不抓附件

国土空间规划数据的关键信息常在 PDF 或图片附件中,例如规划范围图、红线范围、控制指标表。如果只抓网页标题,后续空间分析价值会很有限。

5. 采集频率过高

公开数据也不等于可以无限制高频访问。建议设置 DOWNLOAD_DELAY,降低并发,并优先在业务允许的时间段内运行。稳定、礼貌、可追溯,比短时间抓完更重要。

6. 用一个 Spider 硬套所有网站

不同自然资源部门网站的页面模板差异很大。建议一个栏目或一个网站写一个 Spider,共用 Item 和 Pipeline。这样维护成本更低。

方法比较:Scrapy、requests、Selenium 和接口采集怎么选

方法 适用场景 优点 限制
Scrapy 列表页、详情页、附件较多的公开网页采集 请求调度、并发控制、Pipeline 完整,适合长期维护 学习成本高于 requests,动态网页需要额外处理
requests + BeautifulSoup 少量静态网页或一次性采集 简单直接,代码量少 分页、重试、下载、去重需要自己实现
Selenium 强依赖浏览器渲染的动态页面 能模拟浏览器操作 速度慢,资源占用高,不适合大规模采集
官方 API 或开放数据平台 存在正式数据接口或下载服务 稳定、合规、字段结构清晰 接口权限、调用频率和字段范围可能受限制

对于国土空间规划数据实战,优先顺序建议是:先找官方开放数据或下载入口;没有接口时,再考虑 Scrapy 采集公开网页;只有在页面强依赖 JavaScript 渲染时,才考虑 Selenium 或分析浏览器网络请求。

检查清单:运行 Scrapy GIS 数据采集前后要核对什么

采集前检查

  • 是否确认目标数据为公开数据。
  • 是否阅读网站 robots 协议、版权声明和使用条款。
  • 是否记录栏目 URL、分页规则和样例详情页。
  • 是否明确要采集的字段,而不是盲目保存整页 HTML。
  • 是否设置合理的下载延迟和并发数。

采集中检查

  • Scrapy 日志中是否有大量 404、403、500 错误。
  • 详情页数量是否与列表页数量基本一致。
  • 附件链接是否被正确转为绝对 URL。
  • 中文编码是否正常。
  • 是否出现重复记录。

采集后检查

  • CSV 字段是否完整,标题、日期、行政区、来源链接是否为空过多。
  • 附件是否能够打开,是否能对应到原始项目。
  • 经纬度是否在合理范围内。
  • QGIS 中点位是否落在正确行政区。
  • 是否保留原始数据和清洗后数据两个版本。

FAQ:Scrapy 爬虫框架与 GIS 数据采集常见问题

Scrapy 爬虫框架适合采集哪些 GIS 数据?

Scrapy 爬虫框架适合采集公开网页中的 GIS 相关属性数据,例如国土空间规划公示、自然资源公告、项目选址信息、行政区划说明、地名地址列表、附件索引等。对于标准 WFS、WMS、ArcGIS REST 服务,通常应优先使用官方接口或 GIS 客户端访问。

国土空间规划数据没有坐标怎么办?

可以先保存行政区、项目名称、位置描述和附件链接。后续根据数据质量选择地理编码、行政区匹配、人工判读规划图、矢量化红线等方式补充空间几何。不要因为没有坐标就随意填充点位。

Scrapy 可以直接生成 Shapefile 吗?

可以,但不建议在爬虫阶段直接生成 Shapefile。更推荐先输出 CSV 或 GeoJSON,再用 QGIS、GeoPandas 或 ogr2ogr 转换。这样更容易检查字段、编码、坐标系和异常记录。

采集 PDF 附件后能自动提取规划范围吗?

要看附件质量。如果 PDF 是文本型,可以提取表格和文字;如果是扫描图或规划图,需要 OCR、图像配准或人工矢量化。规划范围属于高精度空间信息时,应以权威矢量数据或正式成果为准。

Scrapy 和 WebGIS 数据接口有什么区别?

Scrapy 主要解析网页内容,适合列表页、详情页和附件索引。WebGIS 数据接口通常返回 JSON、GeoJSON、矢量瓦片或服务图层,结构更接近空间数据。如果目标网站提供 ArcGIS REST、WFS 或开放数据 API,应优先使用接口方式。

如何判断 Scrapy 采集结果是否能用于 GIS 分析?

至少要检查三个方面:字段是否稳定,空间位置是否可靠,来源是否可追溯。对于严肃的国土空间规划分析,还需要记录采集时间、数据来源、清洗规则和空间化方法。

结论:把 Scrapy 当作 GIS 数据生产链条的一环

Scrapy 爬虫框架如何应用于 GIS 数据采集,关键不在于写一个能跑的爬虫,而在于建立一条清晰的数据生产链条:公开网页采集、字段结构化、附件归档、空间化处理、GIS 软件核验、数据库管理和成果更新。

在国土空间规划数据实战中,建议先用 Scrapy 稳定采集标题、日期、行政区、位置描述、附件链接和来源地址,再根据数据质量决定是否生成 GeoJSON、导入 QGIS 或写入 PostGIS。这样既能提高采集效率,也能减少坐标错误、字段混乱和来源不可追溯等问题。

对 GIS 学习者和初级工程师来说,最值得掌握的不是“爬虫技巧越复杂越好”,而是把 Scrapy、Python 数据清洗、QGIS 校验和 PostGIS 存储组合起来,形成可复用、可检查、可交付的 GIS 数据采集工作流。