🕷️ 爬虫工程师学习路径 · 阶段五:数据存储与清洗(完整学习文档)
-
-
- [📌 阶段目标](#📌 阶段目标)
- [📦 第1章 数据存储概览](#📦 第1章 数据存储概览)
-
- [1.1 为什么需要数据库?](#1.1 为什么需要数据库?)
- [1.2 常见数据库选型](#1.2 常见数据库选型)
- [🐬 第2章 MySQL 基础与实战](#🐬 第2章 MySQL 基础与实战)
-
- [2.1 MySQL 安装与配置](#2.1 MySQL 安装与配置)
- [2.2 Python 连接 MySQL](#2.2 Python 连接 MySQL)
- [2.3 创建数据库和表](#2.3 创建数据库和表)
- [2.4 增删改查(CRUD)](#2.4 增删改查(CRUD))
- [2.5 事务处理](#2.5 事务处理)
- [2.6 实现增量更新](#2.6 实现增量更新)
- [🍃 第3章 MongoDB 基础与实战](#🍃 第3章 MongoDB 基础与实战)
-
- [3.1 安装 MongoDB](#3.1 安装 MongoDB)
- [3.2 Python 连接 MongoDB](#3.2 Python 连接 MongoDB)
- [3.3 插入文档](#3.3 插入文档)
- [3.4 查询文档](#3.4 查询文档)
- [3.5 更新文档](#3.5 更新文档)
- [3.6 删除文档](#3.6 删除文档)
- [3.7 建立索引](#3.7 建立索引)
- [3.8 去重策略](#3.8 去重策略)
- [⚡ 第4章 Redis 缓存与去重](#⚡ 第4章 Redis 缓存与去重)
-
- [4.1 安装 Redis](#4.1 安装 Redis)
- [4.2 Python 连接 Redis](#4.2 Python 连接 Redis)
- [4.3 常用数据结构:集合(Set)用于去重](#4.3 常用数据结构:集合(Set)用于去重)
- [4.4 用于去重的爬虫示例](#4.4 用于去重的爬虫示例)
- [4.5 其他常用命令](#4.5 其他常用命令)
- [🧹 第5章 数据清洗](#🧹 第5章 数据清洗)
-
- [5.1 使用 Pandas 进行清洗](#5.1 使用 Pandas 进行清洗)
- [5.2 处理缺失值](#5.2 处理缺失值)
- [5.3 格式统一](#5.3 格式统一)
- [5.4 处理异常值](#5.4 处理异常值)
- [5.5 去重](#5.5 去重)
- [5.6 保存清洗后的数据](#5.6 保存清洗后的数据)
- [⏰ 第6章 定时任务](#⏰ 第6章 定时任务)
-
- [6.1 cron(Linux/Mac)](#6.1 cron(Linux/Mac))
- [6.2 Windows 任务计划程序](#6.2 Windows 任务计划程序)
- [6.3 APScheduler(跨平台)](#6.3 APScheduler(跨平台))
- [🎯 第7章 实战一:将豆瓣电影数据存入 MySQL(增量更新)](#🎯 第7章 实战一:将豆瓣电影数据存入 MySQL(增量更新))
-
- [7.1 目标](#7.1 目标)
- [7.2 步骤](#7.2 步骤)
- [7.3 代码框架](#7.3 代码框架)
- [7.4 验证](#7.4 验证)
- [🎯 第8章 实战二:爬取链家在售房源并存入 MongoDB](#🎯 第8章 实战二:爬取链家在售房源并存入 MongoDB)
-
- [8.1 目标](#8.1 目标)
- [8.2 分析页面](#8.2 分析页面)
- [8.3 代码框架](#8.3 代码框架)
- [8.4 建立索引](#8.4 建立索引)
- [8.5 验证](#8.5 验证)
- [📚 学习资源推荐](#📚 学习资源推荐)
- [⚠️ 法律与道德提醒](#⚠️ 法律与道德提醒)
- [🎯 下一步](#🎯 下一步)
-
欢迎进入阶段五!在前四个阶段,你已经学会了从各种网站上抓取数据,并能够应对反爬虫机制。然而,原始数据往往是杂乱无章的,且直接保存为文件(如 JSON、CSV)难以进行高效查询和管理。本阶段将带你掌握数据存储与清洗的核心技能,学会使用关系型和非关系型数据库持久化数据,并通过清洗、去重、格式化等操作提升数据质量。完成本阶段后,你将能够构建完整的数据管道,为后续的数据分析和应用打下坚实基础。
📌 阶段目标
- 理解关系型数据库(MySQL/PostgreSQL)和非关系型数据库(MongoDB)的适用场景
- 掌握 Python 操作 MySQL 的基本方法(连接、增删改查、事务)
- 掌握 Python 操作 MongoDB 的基本方法(连接、插入、查询、索引)
- 学会使用 Redis 实现数据去重和缓存
- 掌握数据清洗的常用技巧(缺失值处理、格式统一、异常值过滤)
- 能够使用 Pandas 进行高效的数据清洗和转换
- 了解定时任务(cron、APScheduler)实现自动化爬取
- 实战:完成两个练习------将豆瓣电影数据存入 MySQL 并实现增量更新,以及爬取链家在售房源存入 MongoDB 并建立索引
📦 第1章 数据存储概览
1.1 为什么需要数据库?
- 结构化存储:数据库提供表、字段、索引等结构,便于高效查询和管理。
- 数据持久化:文件存储(如 JSON)在数据量大时读写慢,且不支持复杂查询。
- 并发支持:数据库支持多用户同时读写,保证数据一致性。
- 数据完整性:通过约束(如唯一键、外键)保证数据正确。
1.2 常见数据库选型
| 数据库类型 | 代表产品 | 适用场景 | 特点 |
|---|---|---|---|
| 关系型数据库 | MySQL、PostgreSQL | 结构化数据,需要复杂查询、事务 | 表格存储,支持 SQL,强一致性 |
| 非关系型数据库(NoSQL) | MongoDB | 半结构化/非结构化数据,快速开发 | 文档存储,类 JSON 格式,灵活扩展 |
| 键值存储 | Redis | 缓存、去重、实时计数 | 内存存储,读写极快,支持多种数据结构 |
爬虫项目通常组合使用多种数据库:用 MySQL 存储核心业务数据,用 MongoDB 存储网页快照或半结构化数据,用 Redis 实现去重和任务队列。
🐬 第2章 MySQL 基础与实战
2.1 MySQL 安装与配置
- 下载 MySQL Community Server(https://dev.mysql.com/downloads/mysql/)
- 安装时设置 root 密码,记住。
- 可选安装图形化工具:MySQL Workbench 或 Navicat。
启动 MySQL 服务后,可以用命令行连接:
bash
mysql -u root -p
2.2 Python 连接 MySQL
安装 pymysql 或 mysql-connector-python:
bash
pip install pymysql
连接示例:
python
import pymysql
# 建立连接
conn = pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='test_db',
charset='utf8mb4'
)
# 创建游标
cursor = conn.cursor()
# 执行 SQL
cursor.execute('SELECT VERSION()')
result = cursor.fetchone()
print('MySQL 版本:', result)
# 关闭连接
cursor.close()
conn.close()
2.3 创建数据库和表
以豆瓣电影为例,设计表结构:
sql
CREATE DATABASE IF NOT EXISTS douban CHARACTER SET utf8mb4;
USE douban;
CREATE TABLE IF NOT EXISTS movie (
id INT PRIMARY KEY AUTO_INCREMENT,
rank INT NOT NULL COMMENT '排名',
title VARCHAR(255) NOT NULL COMMENT '电影名',
director VARCHAR(255) COMMENT '导演',
year INT COMMENT '上映年份',
rating DECIMAL(3,1) COMMENT '评分',
votes INT COMMENT '评价人数',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
UNIQUE KEY unique_rank (rank) -- 排名唯一,用于增量更新
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.4 增删改查(CRUD)
插入数据:
python
sql = "INSERT INTO movie (rank, title, director, year, rating, votes) VALUES (%s, %s, %s, %s, %s, %s)"
data = (1, '肖申克的救赎', '弗兰克·德拉邦特', 1994, 9.7, 2000000)
cursor.execute(sql, data)
conn.commit() # 必须提交事务
批量插入:
python
data_list = [
(2, '霸王别姬', '陈凯歌', 1993, 9.6, 1800000),
(3, '阿甘正传', '罗伯特·泽米吉斯', 1994, 9.5, 1700000),
]
cursor.executemany(sql, data_list)
conn.commit()
查询数据:
python
cursor.execute("SELECT rank, title, rating FROM movie WHERE rating > 9.0")
rows = cursor.fetchall()
for row in rows:
print(row)
更新数据:
python
sql = "UPDATE movie SET rating = %s WHERE rank = %s"
cursor.execute(sql, (9.8, 1))
conn.commit()
删除数据:
python
cursor.execute("DELETE FROM movie WHERE rank = %s", (3,))
conn.commit()
2.5 事务处理
MySQL 的 InnoDB 引擎支持事务。使用 conn.commit() 提交,conn.rollback() 回滚。
python
try:
cursor.execute("INSERT ...")
cursor.execute("UPDATE ...")
conn.commit()
except Exception as e:
conn.rollback()
print('事务回滚:', e)
2.6 实现增量更新
在爬虫中,我们往往需要定期更新数据,避免重复插入。MySQL 提供了 INSERT ... ON DUPLICATE KEY UPDATE 语法,当插入的记录导致唯一键冲突时,执行更新操作。
假设我们在 rank 字段上设置了唯一索引,那么:
sql
INSERT INTO movie (rank, title, director, year, rating, votes)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
director = VALUES(director),
year = VALUES(year),
rating = VALUES(rating),
votes = VALUES(votes);
在 Python 中执行:
python
sql = """
INSERT INTO movie (rank, title, director, year, rating, votes)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
director = VALUES(director),
year = VALUES(year),
rating = VALUES(rating),
votes = VALUES(votes)
"""
data = (1, '肖申克的救赎', '弗兰克·德拉邦特', 1994, 9.7, 2000000)
cursor.execute(sql, data)
conn.commit()
这样,如果排名已存在,则更新其他字段;如果不存在,则插入新记录。非常适合定时爬取更新数据。
🍃 第3章 MongoDB 基础与实战
MongoDB 是一种文档型数据库,数据以 BSON(类似 JSON)格式存储,非常适合爬虫抓取的半结构化数据(如房源信息、网页快照)。
3.1 安装 MongoDB
- 官网下载 Community Server:https://www.mongodb.com/try/download/community
- 安装后启动服务(默认端口 27017)。
- 可选安装 Compass 图形化工具。
3.2 Python 连接 MongoDB
安装 pymongo:
bash
pip install pymongo
连接示例:
python
from pymongo import MongoClient
# 连接 MongoDB
client = MongoClient('localhost', 27017)
# 或 client = MongoClient('mongodb://localhost:27017/')
# 选择数据库(没有则自动创建)
db = client['lianjia']
# 选择集合(类似表)
collection = db['house']
3.3 插入文档
MongoDB 中的文档是字典形式。
插入单条:
python
house = {
'title': '两室一厅 南北通透',
'price': 450,
'unit': '万',
'area': 89.5,
'community': '阳光小区',
'tags': ['满五唯一', '近地铁']
}
result = collection.insert_one(house)
print('插入的ID:', result.inserted_id)
插入多条:
python
houses = [
{'title': '...', 'price': 500, ...},
{'title': '...', 'price': 550, ...}
]
result = collection.insert_many(houses)
print('插入的IDs:', result.inserted_ids)
3.4 查询文档
查询所有:
python
for doc in collection.find():
print(doc)
条件查询:
python
# 价格大于 500 万
for doc in collection.find({'price': {'$gt': 500}}):
print(doc)
# 小区为 '阳光小区'
for doc in collection.find({'community': '阳光小区'}):
print(doc)
# 且条件
for doc in collection.find({'price': {'$gt': 500}, 'community': '阳光小区'}):
print(doc)
排序、限制、跳过:
python
# 按价格降序,取前10条
docs = collection.find().sort('price', -1).limit(10)
for doc in docs:
print(doc)
3.5 更新文档
更新一条:
python
collection.update_one(
{'title': '两室一厅 南北通透'},
{'$set': {'price': 460}}
)
更新多条:
python
collection.update_many(
{'community': '阳光小区'},
{'$set': {'district': '朝阳区'}}
)
3.6 删除文档
python
# 删除一条
collection.delete_one({'title': '...'})
# 删除多条
collection.delete_many({'price': {'$lt': 300}})
3.7 建立索引
索引可以大幅提升查询效率。MongoDB 支持多种索引。
创建单字段索引:
python
collection.create_index('price')
创建复合索引:
python
collection.create_index([('community', 1), ('price', -1)])
唯一索引(用于去重):
python
collection.create_index('house_id', unique=True)
3.8 去重策略
MongoDB 中可以通过唯一索引保证数据唯一。在插入时设置 ordered=False 可以忽略重复错误继续插入其他数据。
python
# 假设 house_id 字段已建立唯一索引
houses = [
{'house_id': '101', 'title': '...'},
{'house_id': '102', 'title': '...'},
{'house_id': '101', 'title': '...'}, # 重复,会引发错误
]
try:
collection.insert_many(houses, ordered=False)
except pymongo.errors.BulkWriteError as e:
print('部分数据已存在,已忽略')
或者使用 replace_one 配合 upsert=True 实现增量更新:
python
collection.replace_one(
{'house_id': '101'},
{'house_id': '101', 'title': '...', 'price': 500},
upsert=True
)
⚡ 第4章 Redis 缓存与去重
Redis 是一个内存数据库,读写速度极快,常用于缓存、去重、消息队列。
4.1 安装 Redis
- Windows:下载 Redis for Windows(https://github.com/microsoftarchive/redis/releases)
- Linux:
sudo apt install redis-server - Mac:
brew install redis
启动 Redis 服务后,默认端口 6379。
4.2 Python 连接 Redis
安装 redis 库:
bash
pip install redis
连接示例:
python
import redis
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# decode_responses=True 使返回的字节自动解码为字符串
4.3 常用数据结构:集合(Set)用于去重
Redis 的 Set 是字符串的无序集合,可以非常高效地判断元素是否存在,适合用于 URL 去重。
添加元素:
python
r.sadd('visited_urls', 'http://example.com/page1')
判断是否存在:
python
if r.sismember('visited_urls', 'http://example.com/page1'):
print('已爬取')
else:
print('新 URL')
获取所有元素:
python
urls = r.smembers('visited_urls')
集合大小:
python
count = r.scard('visited_urls')
4.4 用于去重的爬虫示例
python
import requests
from bs4 import BeautifulSoup
import redis
import time
r = redis.Redis(db=0, decode_responses=True)
base_url = 'http://example.com/news'
def parse_page(url):
if r.sismember('visited', url):
print(f'跳过已爬取: {url}')
return
print(f'爬取: {url}')
try:
resp = requests.get(url, timeout=5)
# 解析、提取数据...
# 假设提取到新的链接
new_links = extract_links(resp.text)
for link in new_links:
if not r.sismember('visited', link):
r.sadd('todo', link) # 添加到待爬取队列
# 标记当前 URL 为已爬取
r.sadd('visited', url)
r.srem('todo', url) # 从待爬取移除
except Exception as e:
print(f'爬取失败: {e}')
# 初始化待爬取队列
r.sadd('todo', base_url)
while r.scard('todo') > 0:
url = r.spop('todo') # 随机弹出一个 URL
if url:
parse_page(url)
time.sleep(1)
4.5 其他常用命令
- 字符串 :
r.set('key', 'value')、r.get('key') - 哈希 :
r.hset('user:1', 'name', 'Alice')、r.hgetall('user:1') - 列表 :
r.lpush('queue', 'task')、r.rpop('queue') - 过期时间 :
r.expire('key', 60)60 秒后自动删除
🧹 第5章 数据清洗
原始爬取数据往往存在各种问题:
- 缺失值(如某些字段为空)
- 格式不统一(如价格有"万"、"元/平米"等)
- 异常值(如负数、超大值)
- 重复数据
数据清洗就是通过一系列规则和方法,将原始数据转换为干净、规范、可用的数据。
5.1 使用 Pandas 进行清洗
Pandas 是 Python 的数据分析库,提供了强大的数据清洗功能。
安装:
bash
pip install pandas
读取数据(以 JSON 为例):
python
import pandas as pd
df = pd.read_json('lianjia.json')
查看数据概览:
python
print(df.head())
print(df.info())
print(df.describe())
5.2 处理缺失值
- 删除含有缺失值的行:
df.dropna() - 填充缺失值:
df.fillna(value)或使用前向填充df.fillna(method='ffill')
示例:
python
# 将价格缺失的行删除
df = df.dropna(subset=['price'])
# 将小区缺失的填充为'未知'
df['community'] = df['community'].fillna('未知')
5.3 格式统一
将价格统一为数字(如从"450万"提取 450):
python
import re
def extract_price(price_str):
match = re.search(r'(\d+)', str(price_str))
return int(match.group(1)) if match else None
df['price_num'] = df['price'].apply(extract_price)
将面积统一为浮点数(如"89.5㎡"提取 89.5):
python
def extract_area(area_str):
match = re.search(r'(\d+\.?\d*)', str(area_str))
return float(match.group(1)) if match else None
df['area_num'] = df['area'].apply(extract_area)
日期格式转换:
python
df['publish_date'] = pd.to_datetime(df['publish_date'], format='%Y-%m-%d')
5.4 处理异常值
例如,过滤价格不在合理区间的记录:
python
df = df[(df['price_num'] > 0) & (df['price_num'] < 5000)]
5.5 去重
根据某些字段(如房源 ID)去重:
python
df = df.drop_duplicates(subset=['house_id'], keep='first')
5.6 保存清洗后的数据
python
df.to_csv('lianjia_cleaned.csv', index=False, encoding='utf-8-sig')
df.to_json('lianjia_cleaned.json', orient='records', force_ascii=False)
⏰ 第6章 定时任务
爬虫通常需要定期运行,以获取最新数据。可以使用操作系统的定时任务(cron)或 Python 的调度库(APScheduler)。
6.1 cron(Linux/Mac)
编辑 crontab:
bash
crontab -e
添加一行,每天凌晨 2 点运行爬虫:
0 2 * * * cd /path/to/project && /usr/bin/python3 crawler.py >> log.txt 2>&1
6.2 Windows 任务计划程序
可以使用"任务计划程序"创建基本任务,设置触发器执行 Python 脚本。
6.3 APScheduler(跨平台)
APScheduler 是 Python 的定时任务库,可以在程序内部设置定时任务。
安装:
bash
pip install apscheduler
示例:每 3 小时运行一次爬虫
python
from apscheduler.schedulers.blocking import BlockingScheduler
import datetime
def job():
print(f'爬虫运行: {datetime.datetime.now()}')
# 调用你的爬虫函数
# crawl()
scheduler = BlockingScheduler()
scheduler.add_job(job, 'interval', hours=3)
scheduler.start()
使用 cron 表达式:
python
scheduler.add_job(job, 'cron', hour=2, minute=0) # 每天凌晨2点
APScheduler 还支持持久化任务、错过任务重跑等功能,适合复杂的调度需求。
🎯 第7章 实战一:将豆瓣电影数据存入 MySQL(增量更新)
7.1 目标
- 爬取豆瓣电影 Top250(可复用阶段一的代码)
- 将数据存入 MySQL,表结构参考 2.3 节
- 实现增量更新:如果排名已存在,则更新评分、评价人数等;否则插入新记录
7.2 步骤
- 准备数据库 :在 MySQL 中创建
douban数据库和movie表。 - 编写爬虫:提取每部电影的排名、标题、导演、年份、评分、评价人数。
- 连接数据库 :使用
pymysql。 - 执行插入/更新 :使用
INSERT ... ON DUPLICATE KEY UPDATE语句。
7.3 代码框架
python
import requests
from bs4 import BeautifulSoup
import pymysql
import time
# 数据库连接
conn = pymysql.connect(host='localhost', user='root', password='123456', database='douban', charset='utf8mb4')
cursor = conn.cursor()
# 爬虫函数
def fetch_page(start):
url = f'https://movie.douban.com/top250?start={start}'
headers = {'User-Agent': 'Mozilla/5.0'}
resp = requests.get(url, headers=headers)
soup = BeautifulSoup(resp.text, 'lxml')
items = soup.select('.item')
page_data = []
for item in items:
rank = item.find('em').text
title = item.find('span', class_='title').text
# 提取导演、年份等(需解析)
info = item.find('p', class_='').text.strip()
director = info.split('导演:')[1].split('主演')[0].strip() if '导演:' in info else ''
year = info.split('主演')[0].strip()[-5:-1] if '主演' in info else '' # 简化提取
rating = item.find('span', class_='rating_num').text
votes = item.find('div', class_='star').find_all('span')[-1].text[:-3]
page_data.append((int(rank), title, director, int(year) if year.isdigit() else 0, float(rating), int(votes)))
return page_data
# 增量插入 SQL
sql = """
INSERT INTO movie (rank, title, director, year, rating, votes)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
title = VALUES(title),
director = VALUES(director),
year = VALUES(year),
rating = VALUES(rating),
votes = VALUES(votes)
"""
# 爬取所有页
for i in range(0, 250, 25):
data = fetch_page(i)
cursor.executemany(sql, data)
conn.commit()
print(f'已处理 {i+25}/250 条')
time.sleep(2)
cursor.close()
conn.close()
print('完成')
7.4 验证
查询数据库,确认数据已正确插入,再次运行脚本,检查是否更新了数据(可故意修改某个电影的评分,观察更新)。
🎯 第8章 实战二:爬取链家在售房源并存入 MongoDB
8.1 目标
- 爬取链家上海在售二手房信息(https://sh.lianjia.com/ershoufang/)
- 提取标题、价格、户型、面积、小区、关注人数等
- 存入 MongoDB,并为常用查询字段建立索引
- 注意反爬:控制频率,使用 User-Agent 轮换,必要时添加延时
8.2 分析页面
- 列表页:
https://sh.lianjia.com/ershoufang/pg{page}/ - 每个房源卡片包含所需信息,需分析 HTML 结构(使用开发者工具)
- 注意链家有反爬,可能需要使用代理或增加延时,本练习以学习为主,请控制请求频率。
8.3 代码框架
python
import requests
from bs4 import BeautifulSoup
import time
import random
from pymongo import MongoClient, errors
from fake_useragent import UserAgent
# 连接 MongoDB
client = MongoClient('localhost', 27017)
db = client['lianjia']
collection = db['house']
# 创建索引(可选)
collection.create_index('house_id', unique=True) # 假设有唯一标识
collection.create_index('price')
collection.create_index('community')
ua = UserAgent()
def fetch_page(page):
url = f'https://sh.lianjia.com/ershoufang/pg{page}/'
headers = {'User-Agent': ua.random}
try:
resp = requests.get(url, headers=headers, timeout=10)
if resp.status_code == 200:
return resp.text
else:
print(f'页面 {page} 返回状态码 {resp.status_code}')
return None
except Exception as e:
print(f'请求异常: {e}')
return None
def parse_house(html):
soup = BeautifulSoup(html, 'lxml')
items = soup.select('.info.clear')
houses = []
for item in items:
try:
title = item.select('.title a')[0].text
house_id = item.select('.title a')[0]['href'].split('/')[-1].replace('.html', '')
# 价格:如 '450万'
price_str = item.select('.totalPrice span')[0].text
price = int(price_str.replace('万', ''))
# 单价
unit_price = item.select('.unitPrice span')[0].text
# 位置信息
position_info = item.select('.positionInfo a')[0].text if item.select('.positionInfo a') else ''
# 小区
community = item.select('.communityName a')[0].text if item.select('.communityName a') else ''
# 关注人数
follow = item.select('.followInfo')[0].text if item.select('.followInfo') else ''
follow_num = follow.split('/')[0].strip() if follow else ''
house = {
'house_id': house_id,
'title': title,
'price': price,
'unit_price': unit_price,
'community': community,
'position': position_info,
'follow': follow_num,
'crawl_time': time.strftime('%Y-%m-%d %H:%M:%S')
}
houses.append(house)
except Exception as e:
print(f'解析出错: {e}')
continue
return houses
def save_to_mongo(houses):
if not houses:
return
for house in houses:
try:
collection.replace_one({'house_id': house['house_id']}, house, upsert=True)
except errors.DuplicateKeyError:
# 唯一索引冲突,说明已存在,update 会处理,这里忽略
pass
def main():
for page in range(1, 11): # 先爬 10 页
print(f'正在爬取第 {page} 页')
html = fetch_page(page)
if html:
houses = parse_house(html)
save_to_mongo(houses)
print(f'第 {page} 页,保存 {len(houses)} 条')
else:
print(f'第 {page} 页失败')
# 随机延时
time.sleep(random.uniform(2, 5))
if __name__ == '__main__':
main()
8.4 建立索引
在 Compass 中或通过代码创建索引:
python
collection.create_index([('community', 1), ('price', -1)])
collection.create_index('follow')
8.5 验证
查询 MongoDB,确认数据已正确存储。可以尝试按小区搜索,看是否快速返回。
📚 学习资源推荐
- MySQL:《MySQL 必知必会》Ben Forta 著
- MongoDB:官方文档 https://docs.mongodb.com/
- Redis:官方文档 https://redis.io/documentation
- Pandas:官方文档 https://pandas.pydata.org/docs/
- APScheduler:文档 https://apscheduler.readthedocs.io/
⚠️ 法律与道德提醒
- 爬取链家等商业网站时,务必遵守
robots.txt(如https://sh.lianjia.com/robots.txt),控制请求频率,避免对服务器造成压力。 - 本练习仅供学习,请勿将爬取的数据用于商业用途或大规模抓取。
- 尊重数据版权,不要随意公开爬取的数据。
🎯 下一步
完成本阶段后,你已经能够将爬取的数据持久化到数据库,并进行清洗和管理。接下来可以进入阶段六:框架与分布式,学习使用 Scrapy 提高开发效率,以及搭建分布式爬虫。如果你对数据分析感兴趣,也可以进一步学习数据分析与可视化。
如果在实战中遇到问题,欢迎随时交流!