Scrapy爬虫框架如何应用于GIS数据采集?(附:国土空间规划数据实战案例)
Scrapy爬虫框架如何应用于GIS数据采集?(附:国土空间规划数据实战案例)这个问题,适合用来理解“网络数据采集”和“GIS数据整理”之间的完整链路。很多 GIS 同学不是不会下载数据,而是遇到国土空间规划、自然资源公示、行政区划、项目选址公告这类网页时,不知道怎样批量提取表格、附件链接、坐标描述、行政区字段,并把结果整理成可用于 QGIS、ArcGIS Pro 或 PostGIS 的空间数据。
本文以 Scrapy 爬虫框架为主线,演示一个国土空间规划数据采集的实战思路:从网页列表抓取、详情页解析、附件下载、字段清洗,到最终生成 CSV、GeoJSON 或入库 PostGIS。重点不是“爬得越多越好”,而是让 GIS 数据采集过程可复现、可校验、可维护。
引言:为什么 GIS 数据采集适合使用 Scrapy 爬虫框架
GIS 数据采集经常面对三类网页数据:
- 政府部门公开发布的规划公示、项目批前公示、用地预审、规划许可信息。
- 列表页加详情页结构,例如标题、发布时间、行政区、项目名称、附件 PDF 或图片。
- 半结构化文本,例如“项目位于某某街道以东、某某路以南”,需要后续地理编码或人工核验。
如果只是临时下载几个网页,浏览器复制粘贴就够了。但当数据跨多个分页、多个栏目、多个年份时,Scrapy 爬虫框架的优势就比较明显:它能管理请求队列、解析页面、控制下载速度、失败重试、数据管道输出,适合做稳定的 GIS 数据采集工作流。
注意:GIS 数据采集必须遵守目标网站的 robots 协议、版权声明、接口使用条款和数据安全要求。本文仅讨论公开网页数据的合规采集、整理与空间化方法,不建议绕过登录、验证码、访问控制或批量抓取敏感数据。

背景:国土空间规划数据采集通常会遇到什么问题
以国土空间规划数据为例,公开网页中常见的数据并不总是标准 GIS 格式。很多信息分散在公告正文、表格、PDF 附件、图片扫描件中。即使网页上有“项目位置”字段,也不一定包含经纬度坐标。
常见问题包括:
- 列表页只显示标题和日期,关键字段在详情页中。
- 分页参数不明显,需要观察 URL 或浏览器网络请求。
- 附件中包含规划范围图、控制线图、选址红线图,但文件名不规范。
- 行政区、项目名称、建设单位等字段写法不统一。
- 空间位置只有文字描述,无法直接生成点、线、面。
- 网页编码、日期格式、空格换行、全角半角字符混杂。
因此,Scrapy 爬虫框架在 GIS 数据采集中的作用,不只是“抓网页”,更重要的是把非结构化网页内容整理成结构化字段,为后续 QGIS 制图、ArcGIS Pro 分析、PostGIS 查询打基础。
原理:Scrapy 如何服务于 GIS 数据采集
Scrapy 的核心工作流可以理解为四个部分:
- Spider:定义从哪些 URL 开始抓取,如何解析列表页和详情页。
- Request:生成新的网页请求,例如从列表页进入详情页,或翻到下一页。
- Item:定义要提取的数据字段,例如标题、行政区、发布时间、附件链接、位置描述。
- Pipeline:对数据进行清洗、去重、下载附件、输出 CSV、GeoJSON 或写入数据库。
和普通表格采集相比,GIS 数据采集还需要多考虑三件事:
- 空间字段:是否有经纬度、地址、行政区代码、规划范围描述或附件中的红线图。
- 坐标系统:如果后续生成 GeoJSON 或 Shapefile,要明确坐标参考系统,常见是 WGS 84,经纬度 EPSG:4326。
- 空间化方式:没有坐标时,可以先输出地址字段,再用地理编码、行政区匹配或人工矢量化补充空间几何。
一个比较稳妥的原则是: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()
这里的 longitude 和 latitude 可以先为空。国土空间规划数据经常没有现成坐标,后续可以通过地址匹配、地理编码、行政区质心或人工矢量化补充。
步骤 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 后,可以按以下方式检查:
- 加载
planning_publicity.csv,查看字段是否完整。 - 如果有经纬度,使用“添加分隔文本图层”,X 字段选择经度,Y 字段选择纬度。
- 坐标参考系统选择
EPSG:4326 - WGS 84。 - 叠加行政区边界,检查点位是否落在合理区域。
- 按发布时间、行政区、项目类型做分类渲染,发现异常值。
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 数据采集工作流。