1. 项目概述:当Django遇上爬虫与租房数据分析
去年帮朋友找房时,我深刻体会到手动对比不同平台房源信息的痛苦。价格波动、图片真实性、中介话术...这些信息噪音让租房决策变得异常困难。于是我用Django+爬虫构建了一个能自动聚合分析房源数据的系统,今天就把这个实战项目的完整方案分享给大家。
这个系统核心解决三个痛点:一是通过分布式爬虫抓取主流租房平台的结构化数据;二是利用Django ORM建立统一数据仓库;三是实现价格波动监测、房源真实性评分等分析功能。整套方案涉及Python网络爬虫进阶技巧、Django模型优化、Celery异步任务等关键技术栈,特别适合需要处理异构数据源的Web应用场景。
2. 系统架构设计解析
2.1 技术选型决策过程
选择Django而非Flask主要基于三点考量:
- 原生Admin后台能快速搭建数据管理界面
- ORM对多源数据整合更友好
- 内置的用户认证系统便于后续扩展权限管理
爬虫框架选用Scrapy而非Requests+BeautifulSoup组合,原因在于:
- 内置的Item Pipeline与Django模型天然契合
- 分布式爬取扩展性更好(配合Scrapy-Redis)
- 自动的请求去重和重试机制
python复制# 典型Scrapy项目结构示例
scrapy_project/
├── scrapy.cfg
└── my_spider/
├── __init__.py
├── items.py # 定义Django模型对应的字段
├── middlewares.py # 自定义User-Agent轮换
├── pipelines.py # 数据清洗与Django模型保存
├── settings.py # 并发数/下载延迟配置
└── spiders/ # 各平台爬虫实现
2.2 数据库模型设计要点
房源数据的异构性是最大挑战。经过多次迭代,最终模型设计采用核心字段标准化+扩展字段JSON化的方案:
python复制class HouseListing(models.Model):
PLATFORM_CHOICES = [
('lianjia', '链家'),
('ziroom', '自如'),
('58', '58同城')
]
platform = models.CharField(max_length=20, choices=PLATFORM_CHOICES)
external_id = models.CharField(max_length=50, db_index=True) # 平台原始ID
title = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
area = models.PositiveIntegerField() # 面积(㎡)
# 标准化地址信息
district = models.CharField(max_length=50)
street = models.CharField(max_length=100)
longitude = models.FloatField(null=True)
latitude = models.FloatField(null=True)
# 原始平台数据快照
raw_data = models.JSONField()
class Meta:
unique_together = ('platform', 'external_id') # 防止重复入库
关键设计决策:raw_data字段保存原始平台数据,既避免信息丢失,又为后续新增分析维度留出空间。通过external_id+platform建立联合唯一索引,实现增量更新而非全量覆盖。
3. 爬虫系统实现细节
3.1 反爬对抗实战方案
各租房平台的反爬策略日益严格,我们采用多维度防御突破方案:
-
流量特征伪装
- 使用scrapy-user-agents轮换User-Agent
- 每个请求随机添加1-3秒延迟
- 通过代理中间件实现IP轮换(建议使用付费代理API)
-
页面解析容错
python复制def parse_detail(self, response): # 使用XPath+CSS选择器双引擎 title = response.xpath('//h1/text()').get() or \ response.css('div.title::text').get() # 价格提取容错逻辑 price_text = response.css('span.price::text').get('') try: price = float(''.join(filter(str.isdigit, price_text))) except ValueError: self.crawler.stats.inc_value('price_parse_failed') price = None -
验证码处理流程
- 识别简单数字验证码使用Tesseract OCR
- 复杂验证码触发企业微信机器人告警,人工介入处理
- 累计触发验证码达阈值自动暂停爬虫
3.2 数据清洗管道设计
清洗管道需要处理各平台数据格式差异,主要步骤包括:
-
单位统一化
- 面积单位统一为平方米
- 价格统一为元/月
-
地址解析
python复制import jieba.analyse def parse_address(text): # 使用结巴提取地址关键词 tags = jieba.analyse.extract_tags(text, topK=3) # 结合正则匹配具体路名 ... -
异常值过滤
- 价格低于500元/月或高于10万元/月
- 面积小于5㎡或大于500㎡
- 无具体地址信息的房源
4. 数据分析模块实现
4.1 价格波动监测算法
核心是计算同一区域房源的同比/环比变化:
python复制from django.db.models import Avg, F
from datetime import timedelta
def get_price_trend(district):
now = timezone.now()
last_month = now - timedelta(days=30)
current_avg = HouseListing.objects.filter(
district=district,
created_at__gte=last_month
).aggregate(avg_price=Avg('price'))
# 对比三个月前数据
three_months_ago = now - timedelta(days=90)
old_avg = HouseListing.objects.filter(
district=district,
created_at__range=(three_months_ago, last_month)
).aggregate(avg_price=Avg('price'))
if old_avg['avg_price']:
change_percent = (current_avg['avg_price'] - old_avg['avg_price']) / old_avg['avg_price'] * 100
else:
change_percent = 0
return {
'current': round(current_avg['avg_price'], 2),
'change': round(change_percent, 1)
}
4.2 房源真实性评分模型
基于以下特征构建评分体系(0-100分):
- 图片数量与质量(使用OpenCV检测模糊度)
- 描述文本长度与信息密度
- 历史价格变动频率
- 经纪人响应速度(测试电话接通率)
python复制def calculate_authentic_score(listing):
score = 0
# 图片维度
if listing.image_count > 5:
score += 20
elif listing.image_count > 2:
score += 10
# 文本分析
word_count = len(listing.description.split())
if word_count > 100:
score += 30
elif word_count > 50:
score += 15
# 价格稳定性
if listing.price_change_count == 0:
score += 20
# 经纪人维度
if listing.agent_response_rate > 0.8:
score += 30
return min(100, score) # 确保不超过满分
5. 系统部署与优化
5.1 Celery异步任务配置
为避免爬虫阻塞Web请求,使用Celery实现异步调度:
python复制# tasks.py
from celery import shared_task
from django.core.cache import cache
@shared_task(bind=True, max_retries=3)
def run_spider_task(self, spider_name):
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
lock_key = f'spider_lock_{spider_name}'
if cache.get(lock_key):
raise self.retry(countdown=60)
try:
cache.set(lock_key, 1, timeout=3600)
process = CrawlerProcess(get_project_settings())
process.crawl(spider_name)
process.start()
finally:
cache.delete(lock_key)
5.2 性能优化实践
-
数据库层面
- 为常用查询字段添加索引(如district, price)
- 使用select_related/prefetch_related减少查询次数
- 对大文本字段(如raw_data)启用压缩存储
-
缓存策略
python复制from django.core.cache import cache def get_district_stats(district): cache_key = f'district_stats_{district}' result = cache.get(cache_key) if not result: result = calculate_district_stats(district) cache.set(cache_key, result, timeout=6*3600) # 6小时缓存 return result -
前端优化
- 使用Django REST framework实现分页
- 对地图类数据采用GeoJSON格式
- 静态资源走CDN加速
6. 典型问题排查实录
6.1 爬虫被封禁的应急处理
现象:爬虫运行一段时间后返回403状态码
排查步骤:
- 检查请求头是否完整(特别是Referer和Cookie)
- 验证代理IP是否被列入黑名单
- 分析目标网站是否有行为验证(如鼠标轨迹检测)
解决方案:
python复制# settings.py
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy_user_agents.middlewares.RandomUserAgentMiddleware': 400,
'scrapy_proxy_pool.middlewares.ProxyPoolMiddleware': 610,
'scrapy_proxy_pool.middlewares.BanDetectionMiddleware': 620,
}
# 自定义Ban检测中间件
class BanDetectionMiddleware:
def process_response(self, request, response, spider):
if response.status == 403:
spider.logger.warning(f'Ban detected on {request.url}')
# 自动切换代理并重试
request.meta['proxy'] = get_new_proxy()
return request
return response
6.2 数据库写入冲突处理
现象:并发爬取时出现IntegrityError
根因分析:多爬虫实例同时写入相同external_id记录
解决方案:
python复制# pipelines.py
from django.db import IntegrityError
from twisted.internet.defer import inlineCallbacks
class DjangoWriterPipeline:
@inlineCallbacks
def process_item(self, item, spider):
try:
yield self._save_item(item)
except IntegrityError:
# 已存在则更新记录
existing = yield HouseListing.objects.aget(
platform=item['platform'],
external_id=item['external_id']
)
for field, value in item.items():
setattr(existing, field, value)
yield existing.asave()
7. 项目演进方向
当前系统已稳定运行半年,积累了约50万条房源数据。后续计划从三个方向深化:
-
机器学习应用
- 使用历史数据训练价格预测模型
- 基于NLP的虚假房源识别
-
实时报警系统
- 当目标区域出现高性价比房源时触发通知
- 建立黑中介识别规则引擎
-
数据可视化增强
- 集成Echarts实现多维分析图表
- 开发移动端小程序版本
这个项目让我深刻体会到:技术工具的价值在于解决真实世界的问题。下次当你面对租房难题时,不妨试试自己动手构建这样一个分析系统——既能提升技术水平,又能解决实际生活痛点。
