GIS数据采集效率低?Scrapy爬虫实战教程(含:反爬策略与地理编码技巧)
如果你正在被“GIS数据采集效率低?Scrapy爬虫实战教程(含:反爬策略与地理编码技巧)”这个问题困扰,通常不是因为你不会写爬虫,而是因为采集目标、字段结构、坐标转换、地理编码和数据质量控制没有形成一条稳定流程。本文以 GIS 项目中常见的 POI、地址、行政区划属性采集为例,演示如何用 Scrapy 搭建可维护的数据采集流程,并说明合规的反爬应对、地理编码和结果校验方法。
引言:GIS数据采集为什么总是慢
在很多 GIS 项目里,数据采集往往比空间分析更耗时。你可能需要从公开网页中整理学校、医院、停车场、企业网点、政务设施等信息,再把地址转换成经纬度,最后导入 QGIS、ArcGIS Pro、PostGIS 或 WebGIS 系统。
如果只靠手工复制,问题会很快出现:
- 字段名称不统一,后期清洗成本高。
- 地址缺失或格式混乱,无法直接制图。
- 网页分页多,重复采集和漏采很难发现。
- 坐标系不明确,导入 GIS 软件后位置偏移。
- 请求频率过高,容易触发网站限制。
Scrapy 的价值不只是“更快地抓网页”,而是把采集任务拆成请求、解析、清洗、去重、地理编码、导出几个环节,让 GIS 数据采集变成可复用的工程流程。

背景:适合用 Scrapy 采集的 GIS 数据类型
Scrapy 适合采集具有稳定页面结构、分页规则清晰、数据字段重复出现的公开网页数据。对于 GIS 用户来说,最常见的是以下几类:
- POI 数据:名称、类别、地址、联系电话、营业时间。
- 公共服务设施:学校、医院、派出所、停车场、公园、公厕。
- 行政管理信息:街道社区、村委会、网格、片区责任单位。
- 项目公告信息:建设项目名称、位置描述、审批状态、发布时间。
- 地名地址信息:标准地址、门牌号、小区名称、楼栋信息。
但并不是所有数据都适合爬取。涉及登录权限、个人隐私、付费数据、明确禁止自动化访问的数据,应遵守网站条款和法律要求。GIS 数据采集的目标是提高公开信息整理效率,而不是绕过访问限制。
原理:Scrapy 如何服务 GIS 数据采集
Scrapy 是一个 Python 爬虫框架,核心思想是把网页采集分成几个职责明确的模块:
- Spider:定义从哪里开始访问、如何翻页、如何解析字段。
- Request:表示一次网页请求,可以携带 URL、请求头和回调函数。
- Item:定义结构化字段,例如名称、地址、经度、纬度。
- Pipeline:对采集结果进行清洗、去重、地理编码和保存。
- Settings:配置下载延迟、并发数、User-Agent、导出编码等。
对 GIS 项目而言,Scrapy 的关键不只是抓到 HTML,而是把网页内容转换成稳定的空间数据表。至少应包含以下字段:
| 字段 | 说明 | GIS 用途 |
|---|---|---|
| name | 设施或地点名称 | 地图标注、检索、去重 |
| category | 设施类别 | 分层制图、符号化 |
| address | 标准或半标准地址 | 地理编码、空间定位 |
| district | 区县、街道或行政区 | 行政区统计、空间连接 |
| lng | 经度 | 生成点图层 |
| lat | 纬度 | 生成点图层 |
| source_url | 来源页面 | 回溯、核验、更新 |
步骤:Scrapy爬虫实战采集 GIS 地址数据
步骤 1:准备 Python 环境与项目目录
建议使用虚拟环境,避免 Scrapy、GeoPandas、requests 等库与其他项目互相影响。
python -m venv venv
# Windows
venvScriptsactivate
# macOS / Linux
source venv/bin/activate
pip install scrapy pandas requests
创建 Scrapy 项目:
scrapy startproject gis_poi_spider
cd gis_poi_spider
scrapy genspider poi_example example.com
项目生成后,重点关注这几个文件:
- spiders/poi_example.py:写采集逻辑。
- items.py:定义字段。
- pipelines.py:写清洗、去重、地理编码逻辑。
- settings.py:控制请求频率、导出格式和管道启用。
步骤 2:定义 GIS 数据字段
在 items.py 中定义一个适合 GIS 项目的 Item。字段不需要一开始就很多,但要保证后续能定位、能核验、能导入 GIS 软件。
import scrapy
class GisPoiItem(scrapy.Item):
name = scrapy.Field()
category = scrapy.Field()
address = scrapy.Field()
district = scrapy.Field()
phone = scrapy.Field()
lng = scrapy.Field()
lat = scrapy.Field()
source_url = scrapy.Field()
步骤 3:编写 Spider 解析列表页和详情页
下面示例使用通用写法。实际项目中,你需要根据目标网页的 HTML 结构调整 CSS 选择器或 XPath。建议先在浏览器开发者工具中确认字段所在节点,再写解析规则。
import scrapy
from gis_poi_spider.items import GisPoiItem
class PoiExampleSpider(scrapy.Spider):
name = "poi_example"
allowed_domains = ["example.com"]
start_urls = [
"https://www.example.com/poi/list?page=1"
]
def parse(self, response):
cards = response.css(".poi-card")
for card in cards:
detail_url = card.css("a::attr(href)").get()
if detail_url:
yield response.follow(detail_url, callback=self.parse_detail)
next_page = response.css("a.next::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)
def parse_detail(self, response):
item = GisPoiItem()
item["name"] = response.css("h1::text").get()
item["category"] = response.css(".category::text").get()
item["address"] = response.css(".address::text").get()
item["district"] = response.css(".district::text").get()
item["phone"] = response.css(".phone::text").get()
item["source_url"] = response.url
yield item
如果网页中已经包含经纬度,优先直接提取。常见位置包括页面脚本、地图参数、接口返回 JSON 或隐藏字段。直接坐标通常比地址地理编码更稳定,但仍然要检查坐标系。
步骤 4:配置合规的反爬应对策略
这里的“反爬策略”不是绕过验证码、登录权限或付费限制,而是让 Scrapy 以更温和、可控、可追踪的方式访问公开页面,减少对网站造成压力,也降低被限制的概率。
在 settings.py 中建议配置:
ROBOTSTXT_OBEY = True
DOWNLOAD_DELAY = 2
RANDOMIZE_DOWNLOAD_DELAY = True
CONCURRENT_REQUESTS = 4
CONCURRENT_REQUESTS_PER_DOMAIN = 2
DEFAULT_REQUEST_HEADERS = {
"User-Agent": "GIS data collection research bot; contact: your_email@example.com",
"Accept-Language": "zh-CN,zh;q=0.9",
}
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 2
AUTOTHROTTLE_MAX_DELAY = 10
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
RETRY_ENABLED = True
RETRY_TIMES = 2
HTTPERROR_ALLOWED_CODES = [429, 500, 502, 503, 504]
如果遇到 429 Too Many Requests,应降低并发、增加延迟,或暂停任务。不要通过高频代理池、验证码识别、伪装登录等方式强行采集,这类做法不仅不稳定,也可能违反网站规则。
步骤 5:在 Pipeline 中清洗字段
GIS 数据采集效率低,很多时候是因为前期没有做字段清洗。建议至少清理空格、换行、重复符号和无效地址。
import re
class CleanPoiPipeline:
def process_item(self, item, spider):
for key in ["name", "category", "address", "district", "phone"]:
value = item.get(key)
if value:
value = re.sub(r"s+", " ", value).strip()
value = value.replace("地址:", "").replace("电话:", "")
item[key] = value
if not item.get("name") or not item.get("address"):
raise ValueError("缺少 name 或 address,跳过该记录")
return item
在 settings.py 启用 Pipeline:
ITEM_PIPELINES = {
"gis_poi_spider.pipelines.CleanPoiPipeline": 300,
}
步骤 6:加入地理编码技巧
地理编码是把地址转换为经纬度的过程。GIS 项目中,不建议把所有地址直接丢给地理编码服务,而应先做地址标准化。
推荐做法:
- 把省、市、区县字段与详细地址拼接,减少重名地址误匹配。
- 保留原始地址字段,不要只保存清洗后的地址。
- 记录地理编码服务返回的置信度、匹配级别或匹配地址。
- 对批量请求设置延迟,遵守服务商配额和使用条款。
- 同一地址先查本地缓存,避免重复请求。
下面给出一个通用地理编码函数结构。实际使用时,请替换为你有合法授权的地理编码服务,并按官方文档填写参数。
import time
import requests
def geocode_address(address, api_key):
url = "https://api.example-geocoder.com/geocode"
params = {
"address": address,
"key": api_key
}
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if not data.get("results"):
return None, None, None
result = data["results"][0]
lng = result["location"]["lng"]
lat = result["location"]["lat"]
matched_address = result.get("formatted_address")
time.sleep(0.2)
return lng, lat, matched_address
如果你采集的是中国大陆互联网地图服务返回的坐标,常见可能是 GCJ-02 坐标;GPS 和大多数国际 GIS 数据常用 WGS84;部分服务还可能使用 BD-09。导入 QGIS 或 ArcGIS Pro 前,必须确认坐标系,否则会出现几十米到数百米的位置偏移。
步骤 7:导出 CSV 并转换为空间数据
Scrapy 可以直接导出 CSV:
scrapy crawl poi_example -O output/poi.csv
CSV 中如果已经有 lng 和 lat 字段,可以在 QGIS 中按以下步骤加载:
- 打开 QGIS。
- 选择“图层”中的“添加分隔文本图层”。
- 选择
poi.csv。 - X 字段选择
lng,Y 字段选择lat。 - 根据坐标来源选择正确 CRS,例如 WGS84 对应
EPSG:4326。 - 加载后叠加底图或行政区边界检查位置是否合理。
如果要用 Python 转成 GeoJSON,可以使用 GeoPandas:
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
df = pd.read_csv("output/poi.csv")
df = df.dropna(subset=["lng", "lat"])
df["lng"] = df["lng"].astype(float)
df["lat"] = df["lat"].astype(float)
geometry = [Point(xy) for xy in zip(df["lng"], df["lat"])]
gdf = gpd.GeoDataFrame(df, geometry=geometry, crs="EPSG:4326")
gdf.to_file("output/poi.geojson", driver="GeoJSON")
步骤 8:导入 PostGIS 进行空间查询
如果数据后续要给 WebGIS 或空间分析使用,建议导入 PostGIS。可先用 GeoPandas 写入数据库,也可以用 ogr2ogr。导入后要建立空间索引。
CREATE INDEX poi_geom_gix
ON public.poi
USING GIST (geom);
常用核验 SQL:
-- 检查空几何
SELECT COUNT(*) FROM public.poi WHERE geom IS NULL;
-- 检查重复名称和地址
SELECT name, address, COUNT(*)
FROM public.poi
GROUP BY name, address
HAVING COUNT(*) > 1;
-- 检查是否落在目标行政区范围内
SELECT p.*
FROM public.poi p
LEFT JOIN public.district d
ON ST_Within(p.geom, d.geom)
WHERE d.id IS NULL;
常见坑:Scrapy 采集 GIS 数据最容易出错的地方
坑 1:只采集名称,不保留来源链接
GIS 数据经常需要复核。如果没有 source_url,后期发现某个点位置异常时,很难回到原网页确认原因。建议任何采集结果都保留来源 URL 和采集时间。
坑 2:地址可以显示在地图上,不代表坐标是对的
地理编码可能返回同名道路、同名小区或城市中心点。尤其是“某某学校”“某某医院”这类名称,在不同区县可能重复。建议拼接行政区字段,并把匹配地址、匹配级别一起保存。
坑 3:忽略坐标系导致整体偏移
GIS 项目里最常见的坐标问题是 WGS84、GCJ-02、BD-09 混用。表现为点位和底图有固定偏移,或者在不同底图上位置不一致。处理方式是先确认数据来源坐标系,再决定是否转换。
坑 4:并发设置过高
Scrapy 默认性能很强,但 GIS 数据采集通常不追求极限速度。过高并发会增加失败率,也不利于长期更新。对于公开网页,建议低并发、带延迟、可恢复。
坑 5:不做去重
分页、分类页和搜索页可能指向同一个详情页。如果不去重,后续统计会偏大。可以用 source_url、name + address 或空间距离阈值做多级去重。
方法比较:Scrapy、requests、浏览器插件和手工采集怎么选
| 方法 | 适合场景 | 优点 | 限制 |
|---|---|---|---|
| 手工采集 | 几十条以内、一次性任务 | 简单、无需开发 | 慢、易漏、难复现 |
| 浏览器插件 | 表格结构简单的网页 | 上手快、适合临时提取 | 复杂翻页和详情页较弱 |
| requests + BeautifulSoup | 小型静态页面采集 | 代码轻、依赖少 | 工程化、调度和重试能力有限 |
| Scrapy | 分页多、字段多、需要定期更新 | 结构清晰、支持 Pipeline、扩展性好 | 需要理解框架结构 |
| 官方 API | 有正规数据接口的项目 | 稳定、字段规范、合规性好 | 可能有配额、费用或权限要求 |
如果目标网站提供官方 API,优先使用 API。只有在没有合适接口、且数据为公开可访问内容时,再考虑 Scrapy。对于 GIS 工程项目,稳定性和合规性通常比短时间采集速度更重要。
检查清单:采集完成后如何确认数据可用于 GIS
- 是否保留了 name、address、source_url 等基本字段?
- 地址字段是否去除了换行、标签、无效前缀?
- 是否保存了原始地址和地理编码后的匹配地址?
- 经纬度字段是否为数值类型,而不是字符串?
- 是否确认坐标系是 WGS84、GCJ-02 还是 BD-09?
- 是否检查了点位是否落在目标城市或目标行政区内?
- 是否去除了重复记录?
- 是否记录了采集时间,方便后续增量更新?
- 是否遵守 robots.txt、网站条款和地理编码服务配额?
- 是否能在 QGIS 或 ArcGIS Pro 中正常加载并叠加底图检查?
FAQ:GIS数据采集与 Scrapy 常见问题
Scrapy 适合采集所有 GIS 数据吗?
不适合。Scrapy 适合采集公开网页中结构较稳定的信息。如果数据有官方 API、开放数据平台或标准下载入口,应优先使用这些渠道。涉及权限、隐私、付费或禁止自动化访问的数据,不建议用爬虫采集。
地理编码后的点位偏移怎么办?
先确认坐标系。WGS84、GCJ-02、BD-09 混用会造成偏移。其次检查地址是否完整,是否缺少城市、区县、街道信息。最后抽样与权威底图或已有控制点对比,判断是单点错误还是整体坐标系错误。
Scrapy 爬到的数据如何导入 QGIS?
最简单的方法是导出 CSV,确保包含 lng 和 lat 字段,然后在 QGIS 中使用“添加分隔文本图层”。如果要保留空间格式,可以用 GeoPandas 转为 GeoJSON、GeoPackage 或 Shapefile。
如何处理网站的反爬限制?
合规做法是遵守 robots.txt,降低并发,设置下载延迟,使用清晰的 User-Agent,必要时联系数据方申请接口或授权。不要绕过验证码、登录权限、付费墙或明确禁止的访问限制。
GIS 数据采集时要不要直接入库 PostGIS?
小任务可以先导出 CSV 或 GeoJSON,检查无误后再入库。定期更新、多人协作或 WebGIS 服务建议直接进入 PostGIS,并建立空间索引、唯一约束和更新时间字段。
地址没有门牌号还能地理编码吗?
可以,但精度会下降。没有门牌号时,地理编码可能返回道路中心、街道中心或行政区中心。对于精度要求高的项目,应记录匹配级别,并对低置信度结果进行人工复核。
结论:把 Scrapy 当成 GIS 数据工程的一环
GIS数据采集效率低,根本原因往往不是采集速度慢,而是缺少从网页字段到空间数据的完整流程。Scrapy 可以帮助你把公开网页数据结构化,再通过清洗、去重、地理编码和坐标校验,变成可以进入 QGIS、ArcGIS Pro、PostGIS 或 WebGIS 的数据资产。
实际项目中,建议坚持三个原则:第一,优先使用官方 API 或开放数据;第二,采集过程保持低频、合规、可追踪;第三,任何经纬度结果都必须经过坐标系和空间位置校验。这样做,Scrapy 才能真正提升 GIS 数据采集效率,而不是制造更多难以修复的数据质量问题。