全量抓取还是增量采集?二手房数据采集实战解析

爬虫代理

项目背景

很多做数据采集的同学都会遇到一个老问题:到底是一次性把网站的数据全部抓取下来,还是定期只更新新增和变化的部分?
我之前在做二手房市场监测的时候,就碰到过这个选择。当时目标是对比不同城市、不同小区的挂牌房源,看看价格走势和交易活跃度。如果抓取策略不对,不仅会浪费资源,还可能导致数据质量不高。

所以,本文就结合「链家二手房」这个实际站点,聊聊全量抓取和增量采集的取舍,并通过一个实战小项目,展示如何结合代理 IP 技术去实现定期的数据获取和统计。

数据目标

目标字段(示例)

  • 基础识别:house_id(从 URL 或页面特征提取)、titleurl
  • 位置维度:citydistrict(区)、bizcircle(商圈/板块)、community(小区名)
  • 核心指标:total_price(万元)、unit_price(元/㎡)、area(㎡)、room_type(几室几厅)
  • 时间戳:first_seen_at(首次发现时间)、last_seen_at(最后一次看到)
  • 变更检测:content_hash(用于判断记录是否变更)

存储设计

  • 使用 SQLite(轻量)/ PostgreSQL(生产可选)持久化记录;
  • house_id 作为主键,配合 content_hash 实现 幂等写入增量更新
  • 定期产出 统计汇总,如“按区/小区的挂牌数量、均价、面积分布”。

统计示例

  • district 维度:挂牌量、平均单价、价格分位
  • community 维度:挂牌量 Top N、均价 Top N
  • 趋势维度(可扩展):每日新增挂牌量、下架量(需引入“消失检测”)

技术选型

  • 在数据获取方式上,常见有两种:
  1. 全量抓取
    • 每次任务都从头到尾抓一遍。
    • 优点:不会漏数据。
    • 缺点:压力大,耗时耗流量,重复数据多。
  2. 增量采集
    • 每次只采集“新增”或“变化”的部分,比如根据发布时间筛选。
    • 优点:节省资源,数据更新快。
    • 缺点:需要额外逻辑来判断哪些是新数据,哪些是修改过的数据。

我的经验是:

  • 前期数据基线不足时,用全量抓取先把底子打好
  • 后期维护阶段,采用增量采集,避免重复抓取大量无效信息

在网络层面,由于链家有一定的访问频率限制,所以必须结合代理池。这里我选用了爬虫代理服务,支持用户名密码认证,可以减少封禁风险。

模块实现(代码可直接运行/改造)

运行环境:Python 3.10+
安装依赖:pip install requests curl_cffi lxml beautifulsoup4 fake-useragent sqlalchemy pandas apscheduler

0)统一配置(目标入口、代理、数据库)

# -*- coding: utf-8 -*-
"""
项目:贝壳二手房抓取 - 全量 vs 增量
说明:示例代码仅作教学演示,请遵守目标站点条款与 robots.txt。
"""

import os, re, time, random, hashlib, json, datetime as dt
from typing import List, Dict, Optional, Tuple

import requests
from curl_cffi import requests as cffi_requests  # 更拟真TLS栈,可在受限站点兜底
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
from sqlalchemy import create_engine, text
import pandas as pd
from apscheduler.schedulers.blocking import BlockingScheduler

# -- 代理(参考:亿牛云爬虫代理 www.16yun.cn)--------
# 请替换为你的真实配置(域名、端口、用户名、密码)
PROXY_HOST = os.getenv("YINIU_HOST", "proxy.16yun.cn")     # 示例域名
PROXY_PORT = os.getenv("YINIU_PORT", "3100")                  # 示例端口
PROXY_USER = os.getenv("YINIU_USER", "16YUN")
PROXY_PASS = os.getenv("YINIU_PASS", "16IP")

PROXY = f"http://{
     
     PROXY_USER}:{
     
     PROXY_PASS}@{
     
     PROXY_HOST}:{
     
     PROXY_PORT}"
PROXIES = {
   
   "http": PROXY, "https": PROXY}

# ------------------ 目标与数据库 ------------------
BASE_URL = "https://www.ke.com/ershoufang/"
CITY = "sh"  # 城市简码可按需切换,如:bj、gz、sz;或直接用根入口配合筛选
DB_URL = os.getenv("DB_URL", "sqlite:///houses.db")
engine = create_engine(DB_URL, echo=False, future=True)

# ------------------ 抓取模式开关 ------------------
MODE = os.getenv("MODE", "incremental")  # 可选:'full' / 'incremental'

1)建表 & 工具函数(主键、哈希、幂等)

DDL = """
CREATE TABLE IF NOT EXISTS house (
    house_id TEXT PRIMARY KEY,
    title TEXT,
    url TEXT,
    city TEXT,
    district TEXT,
    bizcircle TEXT,
    community TEXT,
    total_price REAL,
    unit_price REAL,
    area REAL,
    room_type TEXT,
    content_hash TEXT,
    first_seen_at TEXT,
    last_seen_at TEXT
);

CREATE TABLE IF NOT EXISTS cursor_state (
    key TEXT PRIMARY KEY,
    value TEXT
);
"""

with engine.begin() as conn:
    for stmt in DDL.strip().split(";"):
        s = stmt.strip()
        if s:
            conn.execute(text(s))

def sha1(obj: Dict) -> str:
    """对核心字段做内容哈希,用于变更检测。"""
    payload = json.dumps(obj, sort_keys=True, ensure_ascii=False)
    return hashlib.sha1(payload.encode("utf-8")).hexdigest()

def now_iso():
    return dt.datetime.utcnow().replace(microsecond=0).isoformat() + "Z"

def load_state(key: str, default: str="") -> str:
    with engine.begin() as conn:
        r = conn.execute(text("SELECT value FROM cursor_state WHERE key=:k"), {
   
   "k": key}).fetchone()
        return r[0] if r else default

def save_state(key: str, value: str):
    with engine.begin() as conn:
        conn.execute(text("""
            INSERT INTO cursor_state(key, value) VALUES(:k, :v)
            ON CONFLICT(key) DO UPDATE SET value=excluded.value
        """), {
   
   "k": key, "v": value})

2)请求层

ua = UserAgent()

def get_session(use_cffi: bool=False)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值