1. 项目背景与核心需求招聘信息采集是数据分析、市场调研和人才服务领域的基础工作。传统爬虫全量抓取模式存在两个致命缺陷一是重复爬取未更新的职位造成资源浪费二是无法识别内容变更导致的重复数据。我在实际项目中曾遇到一个典型案例某招聘网站每天更新约30%的职位信息但使用常规爬虫会导致70%的重复请求不仅浪费带宽还增加了IP被封禁的风险。这个项目要解决三个核心痛点增量更新只抓取新增或修改过的职位降低服务器压力内容去重识别不同URL但实质相同的职位如跨平台发布的同个岗位数据持久化同时支持CSV快速导出和SQLite结构化存储2. 技术方案设计2.1 系统架构设计采用分层处理架构爬虫引擎 → 去重过滤器 → 数据清洗 → 存储引擎 (Hash比对) (XPath解析) (CSVSQLite)2.2 关键技术选型增量更新方案对比方案类型适用场景本项目选择理由时间戳比对带发布时间的数据招聘网站时间格式不统一内容哈希(MD5)任意网页内容可检测内容变更数据库版本比对结构化数据配合SQLite实现哈希算法选择MD5计算速度快实测处理HTML内容可达1.2GB/sSHA1更安全但速度慢30%最终选用MD5因招聘数据无需防碰撞攻击存储方案class Storage: def __init__(self): self.csv_writer CSVWriter() self.sqlite_conn sqlite3.connect(jobs.db) self._init_db()3. 核心实现细节3.1 增量爬取实现采用内容哈希URL双重校验def is_updated(url, html): content_hash hashlib.md5(html.encode()).hexdigest() cursor conn.execute(SELECT hash FROM jobs WHERE url?, (url,)) if not cursor.fetchone(): # 新URL return True if cursor.fetchone()[0] ! content_hash: # 内容变更 return True return False3.2 智能去重策略解决三种重复场景URL不同但内容相同常见于多平台发布的同个职位def get_content_fingerprint(html): # 提取关键字段生成指纹 title xpath(html, //h1/text()) company xpath(html, //div[classcompany]/text()) return hashlib.md5(f{title}-{company}.encode()).hexdigest()内容部分重复使用SimHash算法from simhash import Simhash def is_similar(text1, text2, threshold3): return Simhash(text1).distance(Simhash(text2)) threshold频繁更新检测设置最小更新间隔CREATE TABLE jobs ( ..., last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP CHECK (last_update datetime(now, -1 hours)) );3.3 数据存储实现双存储引擎设计class DualStorage: def save(self, job_data): # CSV存储 with open(jobs.csv, a) as f: writer csv.DictWriter(f) writer.writerow(job_data) # SQLite存储 self.conn.execute( INSERT OR REPLACE INTO jobs VALUES (?,?,?,?,?) , (job_data[id], job_data[title], ...))4. 完整实现代码import hashlib import sqlite3 import csv from datetime import datetime class JobSpider: def __init__(self): self.seen_urls set() self.storage DualStorage() def crawl(self, start_url): html self.download(start_url) if self._should_crawl(start_url, html): job_data self.parse(html) self.storage.save(job_data) def _should_crawl(self, url, html): # 增量更新判断 content_hash hashlib.md5(html.encode()).hexdigest() if url in self.seen_urls: return False if self.storage.check_hash(content_hash): return False return True class DualStorage: def __init__(self): self.conn sqlite3.connect(jobs.db) self._init_db() def _init_db(self): self.conn.execute( CREATE TABLE IF NOT EXISTS jobs ( id TEXT PRIMARY KEY, url TEXT UNIQUE, title TEXT, company TEXT, content_hash TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) def check_hash(self, content_hash): cursor self.conn.execute( SELECT 1 FROM jobs WHERE content_hash?, (content_hash,) ) return bool(cursor.fetchone()) def save(self, job_data): # CSV存储 with open(jobs.csv, a, newline) as f: writer csv.DictWriter(f, fieldnamesjob_data.keys()) writer.writerow(job_data) # SQLite存储 self.conn.execute( INSERT OR IGNORE INTO jobs (id, url, title, company, content_hash) VALUES (?, ?, ?, ?, ?) , ( job_data[id], job_data[url], job_data[title], job_data[company], hashlib.md5(job_data[description].encode()).hexdigest() )) self.conn.commit()5. 实战优化技巧5.1 性能优化方案哈希计算加速# 使用xxHash替代MD5速度快3倍 import xxhash def fast_hash(text): return xxhash.xxh64(text).hexdigest()批量写入优化# SQLite批量提交 with self.conn: self.conn.executemany(INSERT..., job_list)内存去重缓存from functools import lru_cache lru_cache(maxsize10000) def is_duplicate(url_hash): return url_hash in self.seen_hashes5.2 反爬应对策略请求间隔随机化import random time.sleep(random.uniform(1.0, 3.0))User-Agent轮询agents [...] headers {User-Agent: random.choice(agents)}自动重试机制from tenacity import retry, stop_after_attempt retry(stopstop_after_attempt(3)) def download(url): ...6. 常见问题排查6.1 典型错误解决方案哈希冲突误判现象不同职位被误判为相同解决方案组合更多特征字段生成指纹def safe_fingerprint(job): key f{job[title]}-{job[company]}-{job[salary]} return hashlib.md5(key.encode()).hexdigest()增量更新失效检查点确认数据库连接正常try: conn.execute(SELECT 1) except sqlite3.Error as e: print(fDB error: {e})CSV写入乱码解决方案指定编码格式with open(jobs.csv, a, encodingutf-8-sig) as f: ...6.2 调试技巧使用中间文件记录爬取状态def save_checkpoint(self): with open(checkpoint.json, w) as f: json.dump({ last_url: self.last_url, count: self.job_count }, f)可视化监控存储进度from tqdm import tqdm for url in tqdm(urls): self.crawl(url)7. 项目扩展方向分布式爬虫支持使用Redis作为去重中心import redis r redis.Redis() r.sadd(seen_urls, url)自动化部署方案# 使用Docker封装 FROM python:3.9 COPY requirements.txt . RUN pip install -r requirements.txt CMD [python, spider.py]数据质量监控def data_quality_check(job): if not job[title] or len(job[description]) 50: raise ValueError(Invalid job data)这个项目在实际运行中相比传统爬虫减少了约65%的冗余请求。在采集某招聘网站时原本每天需要处理2.3万次请求采用增量方案后降至8千次左右同时保证了99.2%的数据更新及时性。