Scrapy框架真的过时了吗?GIS数据采集实战指南(附:逆向与清洗技巧)
很多做空间数据采集的同学都会问:Scrapy框架真的过时了吗?GIS数据采集实战指南(附:逆向与清洗技巧)这个问题到底该怎么判断?如果你的目标是批量采集POI、行政区划、地名地址、公开目录数据、空间元数据或接口返回的GeoJSON,Scrapy并没有过时;真正过时的是“只会写一个简单爬虫、不理解GIS数据结构和反爬限制”的采集方式。
引言:Scrapy在GIS数据采集里还值得用吗
在GIS项目中,数据采集往往不是“抓几个网页”这么简单。你可能需要从公开网站批量获取空间要素、从接口分页下载JSON、把坐标字段转换成点图层、清洗重复POI、校验行政区划归属,最后输出为GeoJSON、Shapefile、GeoPackage或PostGIS表。
Scrapy的优势正好在这些环节:任务调度稳定、并发控制清晰、失败重试方便、管道机制适合做数据清洗和入库。对于GIS数据采集来说,它不是万能工具,但仍然是一个可靠的工程化框架。
提醒:本文讨论的是公开数据、授权数据和合规接口的数据采集流程。不要绕过登录权限、付费墙、验证码、访问控制或违反网站服务条款进行采集。

背景:GIS数据采集为什么比普通网页爬虫更复杂
普通网页爬虫通常关注标题、正文、图片链接等文本信息。GIS数据采集还要处理空间含义,例如经纬度、坐标系、行政区划编码、几何对象、地址标准化、空间范围筛选等。
常见GIS采集场景包括:
- 采集公开POI列表,并整理为点图层。
- 从政务公开平台下载行政区划、规划公示、项目位置数据。
- 从地图服务或业务接口中解析GeoJSON、WKT、经纬度字段。
- 批量获取空间元数据,例如图层名称、服务地址、范围、坐标系。
- 抓取公开目录页面,再下载对应的CSV、Excel、GeoJSON或压缩包。
这些任务的难点通常不在“发请求”,而在以下几个地方:
- 接口分页规则不明确。
- 坐标字段名称不统一,例如lng、lon、x、longitude混用。
- 坐标系可能是WGS84、GCJ-02、BD-09或地方投影坐标。
- 同一POI在多个页面重复出现。
- 字段缺失、空值、乱码、地址不规范。
- 采集结果需要进入QGIS、ArcGIS Pro或PostGIS继续分析。
原理:Scrapy适合GIS数据采集的核心机制
Scrapy不是一个简单的requests封装,而是一个异步爬虫框架。它的核心工作流可以理解为:Spider生成请求,下载器获取响应,解析器提取数据,Item承载结构化结果,Pipeline负责清洗、去重、转换和入库。
放到GIS数据采集中,可以这样对应:
| Scrapy组件 | 在GIS采集中的作用 | 典型处理内容 |
|---|---|---|
| Spider | 定义采集入口和分页逻辑 | 列表页、接口URL、区域编码、页码参数 |
| Request | 控制请求参数 | bbox、keyword、page、city_code、token |
| Item | 保存结构化字段 | 名称、地址、经度、纬度、分类、来源URL |
| Pipeline | 清洗和输出数据 | 坐标校验、字段标准化、去重、写入GeoJSON或PostGIS |
| Middleware | 处理请求头、代理、重试 | User-Agent、Referer、限速、失败重试 |
因此,Scrapy框架是否“过时”,不能只看它是否流行,而要看你的任务是否需要可维护的采集流程。如果只是临时下载一个接口,requests可能更轻;如果要长期采集、断点重试、批量清洗、入库,Scrapy依然很合适。
步骤:用Scrapy完成一次GIS数据采集实战
步骤1:先确认数据来源是否合法且可复现
开始写代码前,先确认数据源。GIS数据经常来自政府公开平台、开放数据平台、公开地图服务、企业内部授权接口或项目合作方提供的API。
建议记录以下信息:
- 数据页面或接口地址。
- 数据许可或使用说明。
- 字段含义和更新时间。
- 空间范围,例如城市、行政区、bbox。
- 坐标系说明,如果没有说明,需要后续验证。
- 是否允许批量访问,是否有频率限制。
如果数据源没有明确授权,或者需要登录、验证码、签名密钥、付费权限,不建议用爬虫绕过限制。对于GIS项目,数据合规性和可追溯性往往比技术实现更重要。
步骤2:创建Scrapy项目和Spider
假设我们要采集一个公开接口返回的POI JSON数据,字段包含name、address、lng、lat和category。可以先创建项目:
scrapy startproject gis_poi_crawler
cd gis_poi_crawler
scrapy genspider poi_spider example.com
项目结构中重点关注三个文件:
- spiders/poi_spider.py:写请求和解析逻辑。
- items.py:定义POI字段。
- pipelines.py:做坐标校验、去重和输出。
步骤3:定义GIS字段结构
在GIS数据采集中,字段设计不要只照搬网页字段。建议提前定义清晰的标准字段,方便后续导入QGIS、ArcGIS Pro或PostGIS。
import scrapy
class PoiItem(scrapy.Item):
name = scrapy.Field()
address = scrapy.Field()
category = scrapy.Field()
longitude = scrapy.Field()
latitude = scrapy.Field()
source_url = scrapy.Field()
source_id = scrapy.Field()
city = scrapy.Field()
这里把lng和lat统一成longitude和latitude,是为了避免后续不同数据源字段混乱。source_url和source_id也很重要,它们可以帮助你追溯数据来源和去重。
步骤4:编写接口采集逻辑
很多GIS数据页面背后并不是纯HTML,而是通过接口返回JSON。可以在浏览器开发者工具中查看Network面板,找到公开请求地址、分页参数和返回字段。
一个简化版Spider如下:
import json
import scrapy
from gis_poi_crawler.items import PoiItem
class PoiSpider(scrapy.Spider):
name = "poi_spider"
allowed_domains = ["example.com"]
def start_requests(self):
base_url = "https://example.com/api/poi"
for page in range(1, 6):
url = f"{base_url}?city=hangzhou&page={page}&size=20"
yield scrapy.Request(url=url, callback=self.parse_api)
def parse_api(self, response):
data = json.loads(response.text)
records = data.get("records", [])
for row in records:
item = PoiItem()
item["name"] = row.get("name")
item["address"] = row.get("address")
item["category"] = row.get("category")
item["longitude"] = row.get("lng")
item["latitude"] = row.get("lat")
item["source_id"] = row.get("id")
item["source_url"] = response.url
item["city"] = "杭州"
yield item
这段代码只适合作为结构示例。真实项目中,你需要根据目标数据源的字段、分页规则、返回状态码和数据许可进行调整。
步骤5:在Pipeline中做坐标清洗和去重
GIS数据采集不能只保存原始文本。经纬度字段必须经过基本校验,否则导入GIS软件后很容易出现点位跑到海里、坐标落在国外、图层无法显示等问题。
class PoiCleanPipeline:
def __init__(self):
self.seen = set()
def process_item(self, item, spider):
name = self.clean_text(item.get("name"))
address = self.clean_text(item.get("address"))
lon = self.to_float(item.get("longitude"))
lat = self.to_float(item.get("latitude"))
if not name:
raise DropItem("缺少名称字段")
if lon is None or lat is None:
raise DropItem("缺少坐标字段")
if not self.is_valid_wgs84(lon, lat):
raise DropItem("经纬度范围异常")
unique_key = f"{name}_{address}_{round(lon, 6)}_{round(lat, 6)}"
if unique_key in self.seen:
raise DropItem("重复POI")
self.seen.add(unique_key)
item["name"] = name
item["address"] = address
item["longitude"] = lon
item["latitude"] = lat
return item
def clean_text(self, value):
if value is None:
return ""
return str(value).strip().replace("n", " ").replace("t", " ")
def to_float(self, value):
try:
return float(value)
except (TypeError, ValueError):
return None
def is_valid_wgs84(self, lon, lat):
return -180 <= lon <= 180 and -90 <= lat <= 90
如果在Pipeline中使用DropItem,需要在文件顶部导入:
from scrapy.exceptions import DropItem
并在settings.py启用Pipeline:
ITEM_PIPELINES = {
"gis_poi_crawler.pipelines.PoiCleanPipeline": 300,
}
步骤6:输出为GeoJSON,方便直接进入GIS软件
Scrapy可以直接导出JSON、CSV等格式。如果要在QGIS或WebGIS中使用,GeoJSON更方便。可以在Pipeline中把POI写成FeatureCollection。
import json
class GeoJsonWriterPipeline:
def open_spider(self, spider):
self.features = []
def process_item(self, item, spider):
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [item["longitude"], item["latitude"]]
},
"properties": {
"name": item.get("name"),
"address": item.get("address"),
"category": item.get("category"),
"city": item.get("city"),
"source_id": item.get("source_id"),
"source_url": item.get("source_url")
}
}
self.features.append(feature)
return item
def close_spider(self, spider):
geojson = {
"type": "FeatureCollection",
"features": self.features
}
with open("poi_result.geojson", "w", encoding="utf-8") as f:
json.dump(geojson, f, ensure_ascii=False, indent=2)
settings.py中可以同时启用清洗和输出:
ITEM_PIPELINES = {
"gis_poi_crawler.pipelines.PoiCleanPipeline": 300,
"gis_poi_crawler.pipelines.GeoJsonWriterPipeline": 400,
}
运行采集:
scrapy crawl poi_spider
生成poi_result.geojson后,可以直接拖入QGIS检查点位分布。如果点位整体偏移,优先怀疑坐标系或加密坐标问题。
步骤7:用QGIS验证采集结果
数据采集完成后,不要只看文件是否生成。GIS数据必须做空间验证。
- 打开QGIS。
- 将poi_result.geojson拖入地图窗口。
- 设置项目坐标参考系为EPSG:4326或常用Web墨卡托底图环境。
- 叠加公开底图或行政区边界。
- 检查点位是否落在目标城市范围内。
- 随机抽查名称、地址、分类字段是否对应。
- 使用“按位置选择”或空间连接检查行政区归属是否异常。
如果点位整体偏移几十米到几百米,可能是GCJ-02或BD-09坐标。如果点位完全跑到另一个国家,可能是经纬度顺序反了,或者投影坐标被当成经纬度使用。
常见坑:Scrapy做GIS数据采集最容易出错的地方
坑1:把x、y当成经纬度
很多接口字段叫x和y,但它们不一定是经度和纬度。x、y可能是投影坐标,也可能是屏幕坐标,还可能是瓦片坐标。判断方法很简单:如果数值类似120.123、30.456,通常是经纬度;如果数值是几十万或几百万,通常是投影坐标。
坑2:经纬度顺序写反
GeoJSON的点坐标顺序是[longitude, latitude],不是[latitude, longitude]。这是GIS初学者非常常见的问题。写反后,点位会出现在错误位置,甚至完全不显示。
坑3:只清洗文本,不清洗空间字段
GIS数据清洗至少包括字段清洗和空间清洗两部分。字段清洗处理空格、乱码、重复值;空间清洗处理坐标范围、坐标系、几何合法性和空间范围。
坑4:忽略分页终止条件
有些接口最后一页会返回空数组,有些会返回total字段,有些会固定返回成功状态但没有records。写分页逻辑时要根据实际响应判断终止条件,否则容易重复请求或漏采数据。
坑5:过度并发导致请求失败
GIS公开数据平台有时服务器性能有限。并发太高会导致请求失败、封禁IP或影响对方服务。应设置合理下载延迟和并发数。
CONCURRENT_REQUESTS = 4
DOWNLOAD_DELAY = 1
RETRY_TIMES = 3
AUTOTHROTTLE_ENABLED = True
坑6:没有保留原始来源字段
做GIS数据项目时,source_url、source_id、采集时间、数据来源名称非常关键。后期如果发现某批点位异常,需要靠这些字段回溯问题来源。
方法比较:Scrapy、requests、Selenium和地图服务下载怎么选
| 方法 | 适合场景 | 优点 | 局限 |
|---|---|---|---|
| Scrapy | 批量采集公开网页、接口、目录数据 | 结构清晰、并发稳定、适合清洗入库 | 学习成本高于简单脚本 |
| requests | 少量接口请求、一次性下载 | 简单直接、调试方便 | 任务复杂后代码容易混乱 |
| Selenium或Playwright | 页面强依赖浏览器渲染 | 能处理动态页面交互 | 速度慢、资源消耗大,不适合大量数据 |
| GDAL或OGR | 标准地图服务和空间文件下载 | GIS格式支持强 | 不适合复杂网页解析 |
| PostGIS导入管道 | 采集后直接空间分析 | 适合去重、索引和空间查询 | 需要数据库环境 |
如果你的GIS数据采集任务只是下载一个公开GeoJSON文件,不需要Scrapy。如果你要采集多个城市、多个分类、多个分页接口,并且要清洗坐标、去重、入库,Scrapy框架仍然非常实用。
检查清单:GIS数据采集上线前必须确认
- 数据来源是否公开、授权或符合使用条款。
- 是否记录了source_url、source_id、采集时间等追溯字段。
- 分页逻辑是否会漏采或重复采。
- 经纬度顺序是否符合GeoJSON规范。
- 坐标范围是否落在合理空间范围内。
- 坐标系是否明确,是否存在GCJ-02、BD-09或投影坐标问题。
- 字段编码是否为UTF-8,中文是否正常。
- 重复POI是否已经处理。
- 输出格式是否能被QGIS、ArcGIS Pro或PostGIS正确读取。
- 是否设置了合理并发、延迟和失败重试。
- 是否保留原始响应样本,方便后续排错。
- 是否用地图叠加方式抽查了空间位置。
FAQ:Scrapy框架与GIS数据采集常见问题
Scrapy框架真的过时了吗?
没有。对于简单接口请求,Scrapy可能显得偏重;但对于批量GIS数据采集、分页抓取、失败重试、字段清洗、坐标校验和入库流程,Scrapy仍然很适合。它的问题不是过时,而是需要按照工程化方式使用。
GIS数据采集一定要用Scrapy吗?
不一定。如果只是下载一个公开文件,用浏览器、wget、requests或GDAL就够了。如果你要长期采集多个来源,并且要做数据清洗、去重和结构化输出,Scrapy更容易维护。
如何判断采集到的坐标是不是WGS84?
先看字段说明。如果没有说明,可以检查数值范围、叠加底图验证位置,并抽查几个已知点。如果点位整体有固定偏移,可能不是标准WGS84;如果数值是几十万或几百万,可能是投影坐标。
Scrapy能不能直接写入PostGIS?
可以。常见做法是在Pipeline中使用psycopg或SQLAlchemy连接PostgreSQL,把经纬度生成geometry字段,再写入PostGIS表。写入前建议先做坐标校验和唯一键去重。
接口需要逆向分析时要注意什么?
只分析公开页面正常加载时产生的请求,重点看URL、请求方法、分页参数、返回JSON结构和字段含义。不要绕过登录、验证码、签名权限、付费限制或访问控制。逆向的目的应是理解公开数据加载方式,而不是突破限制。
采集POI后为什么在QGIS里不显示?
常见原因包括:GeoJSON格式错误、coordinates顺序写反、坐标为空、坐标范围异常、图层坐标系识别错误,或者地图缩放范围不在点位附近。建议先打开属性表,再用“缩放到图层”检查。
Scrapy采集速度越快越好吗?
不是。GIS公开平台往往不是为高频爬取设计的。速度过快可能导致失败、数据缺失或影响对方服务。应设置合理并发、下载延迟和自动限速,并遵守数据源访问规则。
结论:Scrapy没有过时,过时的是粗糙的数据采集流程
对于GIS读者来说,Scrapy框架的价值不在于“能不能抓网页”,而在于能否把公开数据采集、接口分页、字段解析、坐标清洗、去重、GeoJSON输出和PostGIS入库组织成稳定流程。
如果你的任务只是一次性下载少量数据,requests或GDAL可能更轻便;如果你的任务涉及长期维护、多数据源、多城市、多分类和空间质量控制,Scrapy依然是GIS数据采集中的实用选择。
真正可靠的GIS数据采集,不只是把数据抓下来,还要说明数据从哪里来、坐标是否正确、字段是否可用、结果能否在地图上验证。做到这些,Scrapy就不是过时工具,而是一套可维护的数据工程入口。