爬虫工程师学习路径 · 阶段五:数据存储与清洗(完整学习文档)

🕷️ 爬虫工程师学习路径 · 阶段五:数据存储与清洗(完整学习文档)

      • [📌 阶段目标](#📌 阶段目标)
    • [📦 第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 服务后,可以用命令行连接:

bash 复制代码
mysql -u root -p

2.2 Python 连接 MySQL

安装 pymysqlmysql-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

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

启动 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 步骤

  1. 准备数据库 :在 MySQL 中创建 douban 数据库和 movie 表。
  2. 编写爬虫:提取每部电影的排名、标题、导演、年份、评分、评价人数。
  3. 连接数据库 :使用 pymysql
  4. 执行插入/更新 :使用 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,确认数据已正确存储。可以尝试按小区搜索,看是否快速返回。


📚 学习资源推荐


⚠️ 法律与道德提醒

  • 爬取链家等商业网站时,务必遵守 robots.txt(如 https://sh.lianjia.com/robots.txt),控制请求频率,避免对服务器造成压力。
  • 本练习仅供学习,请勿将爬取的数据用于商业用途或大规模抓取。
  • 尊重数据版权,不要随意公开爬取的数据。

🎯 下一步

完成本阶段后,你已经能够将爬取的数据持久化到数据库,并进行清洗和管理。接下来可以进入阶段六:框架与分布式,学习使用 Scrapy 提高开发效率,以及搭建分布式爬虫。如果你对数据分析感兴趣,也可以进一步学习数据分析与可视化。

如果在实战中遇到问题,欢迎随时交流!

相关推荐
飞Link2 小时前
深度解析多维时序数据异常检测:原理、挑战与架构之道
python·数据挖掘·回归
l1t2 小时前
利用omnicoder-9b模型编写把扫描版pdf转成文字版pdf的程序
人工智能·python·pdf
KevinGuo4572 小时前
【前后端开发知识 - 边开发边学习】什么的单元测试、集成测试和E2E测试?
学习·单元测试·集成测试
王小义笔记2 小时前
解决 uvloop 编译失败问题
python
red_redemption2 小时前
自由学习记录(136)
学习
进击的雷神2 小时前
AJAX动态参数反爬、HTML嵌套网站提取、UPSERT增量更新、空值智能处理——沙特塑料展爬虫四大技术难关攻克纪实
爬虫·python·ajax·html
1941s2 小时前
05-Agent 智能体开发实战指南(五):中间件系统与动态提示词
人工智能·python·中间件·langchain
EnglishJun2 小时前
ARM嵌入式学习(二) --- 入门51(中断)
arm开发·学习
2401_883035462 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python