Python 实现高德地图找房(一):环境搭建与数据爬虫

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:

bash 复制代码
python3 -m venv /opt/venv
source /opt/venv/bin/activate
pip install requests beautifulsoup4 lxml

坑2:pip 安装超时

华为云可用区7 访问 PyPI 偶尔超时。使用国内镜像:

bash 复制代码
pip3 install --break-system-packages -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 lxml

坑3:多 pip 进程冲突

同时启动多个 pip install 会争抢锁文件。串行安装或使用 --no-cache-dir

bash 复制代码
pip3 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 张可视化图表。


相关链接

相关推荐
杜子不疼.1 小时前
【Qt初识】信号槽(三):机制意义、断开连接与 Lambda 表达式
开发语言·数据库·qt
VIP_CQCRE1 小时前
用 Ace Data Cloud 快速接入 OpenAI Chat Completions:对话、流式、多轮和多模态一篇打通
python·ai·openai·api·教程
老迟到的茉莉2 小时前
Hermes 是谁?跟 Claude Code 差在哪
开发语言·python
学逆向的2 小时前
汇编——内存
开发语言·汇编·算法·网络安全
Day(AKA Elin)3 小时前
【Day】MTP(Multi Token Prediction)技术学习
python·深度学习·学习·llama
lbb 小魔仙3 小时前
MuMu 模拟器 6.0 硬核测评:这次升级确实不一样
java·开发语言·模拟器·mumu·硬核测评
r_oo_ki_e_3 小时前
Java 线程池
java·开发语言
杜子不疼.4 小时前
【Qt初识】信号槽(二):自定义信号函数与槽函数
开发语言·qt
程序员小远4 小时前
接口测试之文件上传
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试