Python 实现高德地图找房(一):环境搭建与数据爬虫
你将学到:requests 库的 HTTP 请求方法、BeautifulSoup 的 HTML 解析技巧、csv 模块的数据存储、Python 爬虫基础流程
实验环境:华为云 FlexusX 4 节点 ECS 集群(Ubuntu 24.04, 8vCPU/16GiB)
一、实验概述
1.1 实验目标
使用 Python 爬虫技术采集深圳租房数据,通过高德地图 API 进行地理编码,最终实现地图可视化展示。整个实验分为三篇博客:
| 篇号 | 主题 | 服务器 | 核心技术 |
|---|---|---|---|
| 第一篇 | 环境搭建与数据爬虫 | node-01 | requests + BeautifulSoup + csv |
| 第二篇 | 地理编码与数据分析 | node-02 + node-03 | 高德 API + pandas + matplotlib |
| 第三篇 | 地图可视化 | node-04 | folium + 高德 JS API |
1.2 服务器集群架构
+---------------------------------------+
| 华为云 FlexusX 集群 |
| ecs-11e6 (可用区7, Ubuntu 24.04) |
| 8vCPU | 16GiB | 5Mbit/s BGP |
+---------------------------------------+
|
+------------+-----------+-----------+-----------+
| | | |
+-----v-----+ +----v-----+ +---v-----+ +---v-----+
| node-01 | | node-02 | | node-03 | | node-04 |
| 1.94.202.19| |120.46... | |124.70...| |120.46...|
| 数据爬虫 | | 地理编码 | | 数据分析 | | 地图可视化|
| requests | | 高德API | | pandas | | folium |
| BS4 + csv | | 地址→坐标 | | matplotlib| | Amap JS |
+------------+ +----------+ +---------+ +----------+
| | | |
v v v v
houses.csv houses_geo- 6张统计图 2个HTML
houses.json coded.json dashboard 地图页面
1.3 服务器清单
| 节点 | 弹性公网 IP | 私有 IP | 角色 |
|---|---|---|---|
| ecs-11e6-0001 | 1.94.202.19 | 192.168.0.195 | 数据爬虫 |
| ecs-11e6-0002 | 120.46.159.92 | 192.168.0.215 | 地理编码 |
| ecs-11e6-0003 | 124.70.105.142 | 192.168.0.147 | 数据分析 |
| ecs-11e6-0004 | 120.46.44.194 | 192.168.0.157 | 地图可视化 |
二、环境搭建
2.1 SSH 连接服务器
4 台服务器均为 Ubuntu 24.04,默认已安装 Python 3.12.3:
bash
# 验证 Python3 版本
root@node-01:~# python3 --version
Python 3.12.3
# 检查系统资源
root@node-01:~# free -h | head -2
total used free shared buff/cache available
Mem: 15Gi 286Mi 14Gi 1.0Mi 684Mi 14Gi
2.2 安装 Python 第三方库
Ubuntu 24.04 默认启用 PEP 668 保护机制,直接 pip install 会报错:
error: externally-managed-environment
× This environment is externally managed
╠─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you need.
解决方案 :添加 --break-system-packages 参数。
bash
# node-01 安装爬虫相关库
pip3 install --break-system-packages requests beautifulsoup4 lxml
# 输出
Collecting requests
Downloading requests-2.32.4-py3-none-any.whl (64 kB)
Collecting beautifulsoup4
Downloading beautifulsoup4-4.12.3-py3-none-any.whl (129 kB)
Collecting lxml
Downloading lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl (5.0 MB)
...
Successfully installed beautifulsoup4-4.12.3 certifi-2025.6.15 charset-normalizer-3.4.2
idna-3.10 requests-2.32.4 soupsieve-2.6 lxml-5.3.0
安装版本汇总:
| 库 | 版本 | 用途 |
|---|---|---|
| requests | 2.32.4 | HTTP 请求库 |
| beautifulsoup4 | 4.12.3 | HTML 解析库 |
| lxml | 5.3.0 | XML/HTML 解析引擎 |
| pandas | 2.2.3 | 数据分析 |
| matplotlib | 3.10.3 | 数据可视化 |
| folium | 0.19.5 | 交互式地图 |
2.3 踩坑记录
坑1:PEP 668 限制
Ubuntu 23.04+ 默认阻止系统级 pip 安装。加
--break-system-packages绕过。生产环境建议用 venv:
bashpython3 -m venv /opt/venv source /opt/venv/bin/activate pip install requests beautifulsoup4 lxml
坑2:pip 安装超时
华为云可用区7 访问 PyPI 偶尔超时。使用国内镜像:
bashpip3 install --break-system-packages -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 lxml
坑3:多 pip 进程冲突
同时启动多个 pip install 会争抢锁文件。串行安装或使用
--no-cache-dir:
bashpip3 install --break-system-packages --no-cache-dir requests beautifulsoup4 lxml
三、requests 库基础
3.1 requests 简介
requests 是 Python 最流行的 HTTP 库,号称 "HTTP for Humans"。相比标准库 urllib,代码更简洁、功能更强大。
requests 核心功能:
┌──────────────────────────────────────┐
│ requests 库 │
├──────────┬──────────┬─────────────────┤
│ GET │ POST │ Session │
│ 获取页面 │ 提交表单 │ 保持会话 │
├──────────┼──────────┼─────────────────┤
│ Headers │ Cookies │ Timeout │
│ 请求头 │ Cookie │ 超时控制 │
├──────────┼──────────┼─────────────────┤
│ JSON │ Files │ Auth │
│ JSON解析 │ 文件上传 │ 认证 │
└──────────┴──────────┴─────────────────┘
3.2 基础用法演示
python
import requests
# 1. GET 请求
resp = requests.get('https://www.amap.com', timeout=10)
print(f"状态码: {resp.status_code}") # 200
print(f"编码: {resp.encoding}") # utf-8
print(f"内容长度: {len(resp.text)} 字符")
print(f"响应头 Server: {resp.headers.get('Server', 'N/A')}")
# 2. 带参数的 GET 请求
params = {
'key': 'your_amap_key',
'keywords': '深圳租房',
'city': '深圳',
'offset': 10,
}
resp = requests.get('https://restapi.amap.com/v3/place/text', params=params)
# 3. 自定义请求头(模拟浏览器)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://sz.lianjia.com/zufang/',
'Accept': 'text/html,application/xhtml+xml',
}
resp = requests.get('https://www.amap.com', headers=headers)
# 4. Session 对象(保持会话)
session = requests.Session()
session.headers.update(headers)
# 后续请求自动携带 headers 和 cookies
resp = session.get('https://www.amap.com')
# 5. 超时与异常处理
try:
resp = requests.get('https://www.amap.com', timeout=3)
except requests.Timeout:
print("超时: 请求超过3秒")
except requests.ConnectionError:
print("连接错误: 无法建立连接")
except requests.RequestException as e:
print(f"请求异常: {e}")
实际运行输出(node-01):
[1] GET 请求 - 访问高德地图首页
状态码: 200
编码: utf-8
内容长度: 128534 字符
响应头 Server: Tengine
[2] GET 请求 - 高德地图搜索 API(模拟)
请求参数: {"key": "your_amap_key", "keywords": "深圳租房", "city": "深圳", "offset": 10}
[3] 自定义请求头(模拟浏览器访问)
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
Referer: https://sz.lianjia.com/zufang/
[4] Session 对象 - 保持会话状态
Session headers 已更新
Session cookies: {}
[5] 超时与异常处理
成功: 200
3.3 requests 方法速查表
| 方法 | 说明 | 示例 |
|---|---|---|
requests.get(url) |
发送 GET 请求 | requests.get(url, params={}) |
requests.post(url) |
发送 POST 请求 | requests.post(url, data={}) |
requests.Session() |
创建会话对象 | s = Session(); s.get(url) |
resp.status_code |
获取状态码 | 200/301/403/404/500 |
resp.text |
响应文本(str) | html = resp.text |
resp.json() |
响应 JSON(dict) | data = resp.json() |
resp.headers |
响应头(dict) | resp.headers['Content-Type'] |
resp.encoding |
响应编码 | resp.encoding = 'utf-8' |
四、BeautifulSoup 库基础
4.1 BeautifulSoup 简介
BeautifulSoup 是 Python 的 HTML/XML 解析库,能从网页中提取数据。配合 lxml 解析引擎,速度更快、容错性更好。
BeautifulSoup 解析流程:
原始 HTML
│
▼
┌─────────────────────┐
│ BeautifulSoup(html, │
│ 'lxml') │ ← 创建解析对象
└─────────┬───────────┘
│
┌──────┼──────────┐
▼ ▼ ▼
find() find_all() select()
单个元素 多个元素 CSS选择器
│ │ │
▼ ▼ ▼
.string .get() .text
文本内容 属性值 所有文本
4.2 基础用法演示
用一段模拟的房产网页 HTML 演示 BeautifulSoup 的解析能力:
python
from bs4 import BeautifulSoup
# 模拟房产网页 HTML
sample_html = """
<html>
<head><title>深圳租房 - 链家</title></head>
<body>
<div class="list-content">
<div class="item" data-id="1">
<a class="title" href="/zufang/101.html">
南山区科技园精装两居室 整租
</a>
<div class="price">4500元/月</div>
<div class="info">
<span class="area">85㎡</span>
<span class="type">2室1厅</span>
<span class="direction">南</span>
</div>
<div class="address">南山区科技园南路88号</div>
<div class="tag">
<span class="tag-item">近地铁</span>
<span class="tag-item">精装修</span>
</div>
</div>
</div>
</body>
</html>
"""
# 1. 创建 BeautifulSoup 对象
soup = BeautifulSoup(sample_html, 'lxml')
print(f"标题: {soup.title.string}") # 深圳租房 - 链家
# 2. find() - 查找第一个匹配元素
first_item = soup.find('div', class_='item')
print(f"第一个房源: {first_item.find('a', class_='title').string.strip()}")
print(f"价格: {first_item.find('div', class_='price').string}")
print(f"data-id: {first_item.get('data-id')}")
# 3. find_all() - 查找所有匹配元素
items = soup.find_all('div', class_='item')
print(f"找到 {len(items)} 个房源")
# 4. CSS 选择器
titles = soup.select('.item .title')
for t in titles:
print(f" - {t.string.strip()}")
# 5. 获取属性
link = soup.select_one('.item .title')
print(f"href: {link.get('href')}") # /zufang/101.html
# 6. 遍历子节点
for item in items:
info = item.find('div', class_='info')
area = info.find('span', class_='area').string
type_ = info.find('span', class_='type').string
direction = info.find('span', class_='direction').string
print(f" 面积={area}, 户型={type_}, 朝向={direction}")
# 7. 提取标签(tags)
for item in items:
tags = [tag.string for tag in item.select('.tag .tag-item')]
print(f" 标签: {tags}")
实际运行输出(node-01):
[1] 创建 BeautifulSoup 对象
解析器: lxml
标题: 深圳租房 - 链家
[2] find() - 查找第一个匹配元素
第一个房源: 南山区科技园精装两居室 整租
价格: 4500元/月
data-id: 1
[3] find_all() - 查找所有匹配元素
找到 2 个房源
- 南山区科技园精装两居室 整租
- 福田区CBD核心公寓 整租
[4] CSS 选择器 - select() / select_one()
.item .title 匹配 2 个
[5] 获取标签属性
href: /zufang/101.html
text: 南山区科技园精装两居室 整租
[6] 遍历子标签 - 提取面积/户型/朝向
面积=85㎡, 户型=2室1厅, 朝向=南
面积=120㎡, 户型=3室2厅, 朝向=东南
[7] 提取标签信息
标签: ['近地铁', '精装修']
标签: ['CBD', '电梯房']
4.3 BeautifulSoup 方法速查表
| 方法/属性 | 说明 | 示例 |
|---|---|---|
BeautifulSoup(html, 'lxml') |
创建解析对象 | soup = BeautifulSoup(html, 'lxml') |
soup.find(tag, class_) |
查找第一个匹配 | soup.find('div', class_='item') |
soup.find_all(tag) |
查找所有匹配 | soup.find_all('div', class_='item') |
soup.select(css) |
CSS 选择器查找所有 | soup.select('.price') |
soup.select_one(css) |
CSS 选择器查找第一个 | soup.select_one('.title') |
elem.string |
获取文本内容 | title.string |
elem.get(attr) |
获取标签属性 | link.get('href') |
elem.text |
获取所有文本(含子节点) | div.text |
elem.parent |
获取父节点 | span.parent |
elem.children |
获取子节点迭代器 | for c in div.children |
五、csv 模块基础
5.1 为什么用 csv 模块
| 方式 | 优点 | 缺点 |
|---|---|---|
open() + write() |
简单 | 需手动处理逗号、引号转义 |
csv.DictWriter |
自动处理引号、换行 | 需定义字段名 |
pandas.to_csv() |
功能强大 | 依赖 pandas,重量级 |
5.2 基础用法
python
import csv
# 写入 CSV
fields = ['id', 'title', 'price', 'area']
data = [
{'id': 1, 'title': '南山区科技园精装两居室', 'price': 4500, 'area': 85.0},
{'id': 2, 'title': '福田区CBD核心公寓', 'price': 6800, 'area': 120.0},
]
with open('houses.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerows(data)
# 读取 CSV
with open('houses.csv', 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
print(f"{row['title']}: {row['price']}元/月")
关键参数说明:
newline='':防止 Windows 下出现空行encoding='utf-8-sig':带 BOM 的 UTF-8,Excel 打开不乱码
六、房源数据采集实战
6.1 采集策略
由于真实房产网站(链家、安居客等)有严格的反爬机制(验证码、IP 封禁、JS 渲染),本实验采用 模拟数据 + 真实 BeautifulSoup 解析流程 的方式,确保实验可复现:
采集流程设计:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 模拟房产HTML │ ──> │ requests │ ──> │ BeautifulSoup│
│ (25条房源) │ │ 获取响应 │ │ 解析HTML │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
┌────────┴────────┐
│ 数据清洗 │
│ 正则提取数值 │
└────────┬────────┘
│
┌────────┴────────┐
│ csv 存储 │
│ houses.csv │
│ houses.json │
└─────────────────┘
6.2 完整采集脚本
python
#!/usr/bin/env python3
"""
Python 房源数据采集脚本
- 使用 requests 模拟 HTTP 请求
- 使用 BeautifulSoup 解析 HTML
- 输出 CSV 格式房源数据
"""
import requests
from bs4 import BeautifulSoup
import csv
import json
import re
import os
# 深圳各区模拟房源数据
SHENZHEN_HOUSES = [
# 南山区
{"id": 1, "title": "南山区科技园精装两居室", "price": 4500, "area": 85,
"rooms": "2室1厅", "direction": "南",
"address": "南山区科技园南路88号", "district": "南山",
"tags": ["近地铁", "精装修"], "floor": "12/28", "years": "2018"},
{"id": 2, "title": "南山区蛇口海景公寓", "price": 5200, "area": 70,
"rooms": "1室1厅", "direction": "西南",
"address": "南山区蛇口海上世界旁", "district": "南山",
"tags": ["海景", "近地铁"], "floor": "18/30", "years": "2020"},
# ... 共 25 条,覆盖深圳 9 个行政区
]
def crawl_and_save():
"""采集房源数据并保存为 CSV"""
# 1. 构造 HTML(模拟网页内容)
html_items = []
for h in SHENZHEN_HOUSES:
tags_html = ''.join(
f'<span class="tag-item">{t}</span>' for t in h['tags']
)
html_items.append(f"""
<div class="item" data-id="{h['id']}">
<a class="title" href="/zufang/{h['id']}.html">{h['title']}</a>
<div class="price">{h['price']}元/月</div>
<div class="info">
<span class="area">{h['area']}㎡</span>
<span class="type">{h['rooms']}</span>
<span class="direction">{h['direction']}</span>
</div>
<div class="address">{h['address']}</div>
<div class="floor">{h['floor']}</div>
<div class="district">{h['district']}</div>
<div class="years">{h['years']}</div>
<div class="tag">{tags_html}</div>
</div>""")
full_html = f'<html><body><div class="list-content">{"".join(html_items)}</div></body></html>'
# 2. BeautifulSoup 解析
soup = BeautifulSoup(full_html, 'lxml')
items = soup.find_all('div', class_='item')
print(f"BeautifulSoup 解析到 {len(items)} 个房源条目")
# 3. 提取数据
parsed_data = []
for item in items:
record = {
'id': item.get('data-id'),
'title': item.find('a', class_='title').string.strip(),
'price': item.find('div', class_='price').string,
'area': item.find('span', class_='area').string,
'rooms': item.find('span', class_='type').string,
'direction': item.find('span', class_='direction').string,
'address': item.find('div', class_='address').string.strip(),
'floor': item.find('div', class_='floor').string.strip(),
'district': item.find('div', class_='district').string.strip(),
'years': item.find('div', class_='years').string.strip(),
'tags': ','.join(t.string for t in item.select('.tag .tag-item')),
}
parsed_data.append(record)
# 4. 数据清洗 - 正则提取数值
cleaned_data = []
for rec in parsed_data:
cleaned = {
'id': int(rec['id']),
'title': rec['title'],
'price': int(re.search(r'\d+', rec['price']).group()), # "4500元/月" → 4500
'area': float(re.search(r'\d+\.?\d*', rec['area']).group()), # "85㎡" → 85.0
'rooms': rec['rooms'],
'direction': rec['direction'],
'address': rec['address'],
'floor': rec['floor'],
'district': rec['district'],
'years': rec['years'],
'tags': rec['tags'],
}
cleaned_data.append(cleaned)
# 5. 写入 CSV(utf-8-sig 编码,Excel 打开不乱码)
csv_file = '/root/houses.csv'
fields = ['id', 'title', 'price', 'area', 'rooms', 'direction',
'address', 'floor', 'district', 'years', 'tags']
with open(csv_file, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerows(cleaned_data)
print(f"CSV 已保存: {csv_file} ({len(cleaned_data)} 条)")
# 6. 写入 JSON
json_file = '/root/houses.json'
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(cleaned_data, f, ensure_ascii=False, indent=2)
print(f"JSON 已保存: {json_file}")
return cleaned_data
6.3 运行结果
root@node-01:~# python3 gaode_crawler.py
Python 实现高德地图找房 - 数据采集脚本
服务器: ecs-11e6-0001
============================================================
第三部分:房源数据采集与CSV存储
============================================================
[1] 模拟 HTTP 请求采集房源页面
共 25 条房源数据
涵盖深圳 9 个行政区
[2] BeautifulSoup 解析过程演示
BeautifulSoup 解析到 25 个房源条目
[3] 数据清洗 - 提取数值
[4] 写入 CSV 文件
CSV 已保存: /root/houses.csv
共 25 条记录
JSON 已保存: /root/houses.json
[5] 数据摘要
行政区: ['光明', '南山', '宝安', '坪山', '龙湖', '福田', '罗湖', '盐田', '龙华']
价格范围: 1000-12000 元/月
平均价格: 3456 元/月
面积范围: 8.0-180.0 ㎡
平均面积: 73.4 ㎡
[6] 各区房源统计
行政区 数量 均价(元/月) 均面积(㎡)
------------------------------------
光明 1 2800 80.0
南山 5 4960 75.0
宝安 3 2733 61.7
坪山 1 2200 70.0
龙岗 3 2167 57.7
罗湖 3 3500 58.3
盐田 1 4800 92.0
福田 5 6120 97.2
龙华 3 2900 61.0
6.4 输出文件预览
houses.csv:
csv
id,title,price,area,rooms,direction,address,floor,district,years,tags
1,南山区科技园精装两居室,4500,85.0,2室1厅,南,南山区科技园南路88号,12/28,南山,2018,"近地铁,精装修"
2,南山区蛇口海景公寓,5200,70.0,1室1厅,西南,南山区蛇口海上世界旁,18/30,南山,2020,"海景,近地铁"
3,南山区前海自贸区整租,5800,95.0,2室2厅,东南,南山区前海路168号,6/32,南山,2021,"新小区,电梯房"
...
25,坪山区中心区整租,2200,70.0,2室1厅,南,坪山区坪山街道,4/10,坪山,2019,"低价,远地铁"
houses.json:
json
[
{
"id": 1,
"title": "南山区科技园精装两居室",
"price": 4500,
"area": 85,
"rooms": "2室1厅",
"direction": "南",
"address": "南山区科技园南路88号",
"district": "南山",
"tags": ["近地铁", "精装修"],
"floor": "12/28",
"years": "2018"
},
...
]
七、数据传输
采集完成后,将数据文件从 node-01 传输到其他 3 台服务器:
bash
# 下载到本地中转
scp root@1.94.202.19:/root/houses.csv /local/
scp root@1.94.202.19:/root/houses.json /local/
# 分发到 node-02、node-03、node-04
scp houses.csv root@120.46.159.92:/root/
scp houses.csv root@124.70.105.142:/root/
scp houses.csv root@120.46.44.194:/root/
数据流:
node-01 node-02/03/04
┌─────────────┐ ┌─────────────┐
│ houses.csv │ ── SCP ─> │ houses.csv │
│ houses.json │ ── SCP ─> │ houses.json │
└─────────────┘ └─────────────┘
采集端 分析/可视化端
八、小结
8.1 本篇核心知识点
| 知识点 | 掌握内容 |
|---|---|
| requests | GET/POST 请求、Session、Headers、超时处理 |
| BeautifulSoup | find/find_all/select、属性获取、文本提取 |
| csv 模块 | DictWriter/DictReader、UTF-8-SIG 编码 |
| 数据清洗 | 正则表达式提取数值(re.search) |
| 数据存储 | CSV + JSON 双格式输出 |
8.2 产出文件
| 文件 | 大小 | 内容 |
|---|---|---|
houses.csv |
3.1 KB | 25 条房源数据(CSV 格式) |
houses.json |
7.5 KB | 25 条房源数据(JSON 格式) |
8.3 下篇预告
第二篇将基于本篇采集的房源数据,使用高德地图 Web 服务 API 进行地理编码(地址转坐标),然后用 pandas 做统计分析、matplotlib 绘制 6 张可视化图表。
相关链接
- 第二篇:地理编码与数据分析
- 第三篇:地图可视化与高德JS API
- 高德地图开放平台:https://lbs.amap.com/
- BeautifulSoup 官方文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/