1. 为什么需要通用爬虫框架?
在数据驱动的时代,爬虫技术已经成为获取互联网信息的标配工具。但每次针对新网站都要重写爬取逻辑,这种重复劳动让很多开发者感到疲惫。我曾在三个月内为不同客户写了17个爬虫脚本,每个脚本80%的代码都在处理相同的流程——请求重试、异常处理、数据清洗。直到有天凌晨三点调试第18个爬虫时,我决定打造一个能覆盖大多数场景的通用解决方案。
传统爬虫开发存在三个典型痛点:
- 配置与代码耦合:爬取规则、URL列表等配置信息硬编码在脚本中,每次修改都需要动代码
- 异常处理碎片化:每个项目都要重新实现网络超时、反爬应对等机制
- 扩展成本高:新增数据源时需要复制粘贴大量相似代码
我们的解决方案核心是:用YAML定义爬取规则,用Python实现引擎逻辑。这就像给爬虫装上了可更换的"任务卡带"——YAML文件描述"要做什么",Python引擎负责"怎么做"。当需要采集新网站时,只需编写新的YAML配置,无需改动核心代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 框架架构设计
2.1 核心模块划分
这套框架采用分层设计,各模块职责分明:
code复制▌ 配置层 (YAML)
│
├── 站点配置(请求头、cookies等)
├── 爬取规则(URL模式、解析规则)
└── 输出格式(数据存储方式)
│
▌ 引擎层 (Python)
│
├── 下载器(自动重试、代理切换)
├── 解析器(XPath/CSS选择器执行)
├── 调度器(URL去重、优先级队列)
└── 管道(数据清洗、存储)
2.2 YAML配置详解
下面是一个采集电商商品信息的配置示例:
yaml复制# config/example.yaml
site:
name: "示例电商"
base_url: "https://example.com"
headers:
User-Agent: "Mozilla/5.0"
cookies: {}
crawl:
start_urls:
- "https://example.com/category/electronics"
link_extract:
pattern: "//a[contains(@href,'/product/')]/@href"
max_depth: 3
parse:
product:
title: "//h1[@class='product-title']/text()"
price: "//span[@class='price']/text()"
selector: "css:.sku-option::attr(data-value)"
output:
type: "csv"
filename: "products.csv"
关键配置项说明:
site:定义目标站点基础信息crawl:控制爬取行为的参数parse:使用XPath或CSS选择器定义数据提取规则output:指定数据存储方式
提示:YAML中使用
|可以保留多行文本格式,非常适合存储复杂的XPath表达式
3. Python引擎实现
3.1 核心类设计
python复制class CrawlerEngine:
def __init__(self, config_path):
self.load_config(config_path)
self.visited_urls = set()
self.session = requests.Session()
def load_config(self, path):
with open(path, 'r') as f:
self.config = yaml.safe_load(f)
def start(self):
for url in self.config['crawl']['start_urls']:
self.crawl(url)
def crawl(self, url, depth=0):
if depth > self.config['crawl'].get('max_depth', 1):
return
try:
response = self.download(url)
data = self.parse(response)
self.save(data)
for next_url in self.extract_links(response):
if next_url not in self.visited_urls:
self.crawl(next_url, depth+1)
except Exception as e:
self.handle_error(url, e)
3.2 关键功能实现
智能重试机制:
python复制def download(self, url, retries=3):
for i in range(retries):
try:
resp = self.session.get(url,
headers=self.config['site']['headers'],
timeout=10)
resp.raise_for_status()
return resp
except RequestException as e:
if i == retries - 1:
raise
time.sleep(2 ** i) # 指数退避
多解析器支持:
python复制def parse(self, response):
data = {}
for field, selector in self.config['parse'].items():
if selector.startswith('//'):
data[field] = response.xpath(selector).get()
elif selector.startswith('css:'):
data[field] = response.css(selector[4:]).get()
return data
4. 高级功能扩展
4.1 反爬虫应对策略
在site配置中添加反爬策略:
yaml复制anti_bot:
proxy:
enable: true
list: "proxies.txt"
delay:
min: 1
max: 3
user_agents:
- "Mozilla/5.0 (Windows NT 10.0)"
- "Mozilla/5.0 (Macintosh; Intel Mac OS X)"
引擎对应实现:
python复制def prepare_request(self):
if self.config['anti_bot']['proxy']['enable']:
proxy = random.choice(self.load_proxies())
self.session.proxies = {'http': proxy, 'https': proxy}
if 'user_agents' in self.config['anti_bot']:
headers = self.config['site']['headers'].copy()
headers['User-Agent'] = random.choice(
self.config['anti_bot']['user_agents'])
self.session.headers = headers
4.2 数据管道扩展
支持多种输出方式:
python复制def save(self, data):
output_type = self.config['output']['type']
if output_type == 'csv':
with open(self.config['output']['filename'], 'a') as f:
writer = csv.DictWriter(f, fieldnames=data.keys())
if f.tell() == 0:
writer.writeheader()
writer.writerow(data)
elif output_type == 'mongodb':
self.mongo_collection.insert_one(data)
5. 实战部署指南
5.1 环境准备
推荐使用conda创建隔离环境:
bash复制conda create -n spider python=3.8
conda activate spider
pip install requests pyyaml scrapy parsel
5.2 项目目录结构
code复制spider-framework/
├── engine.py # 核心引擎
├── config/
│ ├── site1.yaml # 站点配置1
│ └── site2.yaml # 站点配置2
├── data/ # 输出目录
└── utils/ # 工具函数
5.3 运行与监控
启动爬虫:
bash复制python engine.py -c config/example.yaml
添加基础监控:
python复制class StatsCollector:
def __init__(self):
self.start_time = time.time()
self.request_count = 0
def log_request(self):
self.request_count += 1
def print_stats(self):
elapsed = time.time() - self.start_time
print(f"[STATS] 已处理 {self.request_count} 请求")
print(f"[STATS] 平均速度 {self.request_count/elapsed:.2f} req/s")
6. 性能优化技巧
- 并发控制:
python复制from concurrent.futures import ThreadPoolExecutor
def concurrent_crawl(self):
with ThreadPoolExecutor(max_workers=5) as executor:
futures = []
for url in self.config['crawl']['start_urls']:
futures.append(executor.submit(self.crawl, url))
- 内存优化:
- 使用
scrapy.Request的meta参数传递数据 - 定期将已爬取URL集合持久化到磁盘
- 断点续爬:
python复制def save_progress(self):
with open('progress.json', 'w') as f:
json.dump({
'visited': list(self.visited_urls),
'queue': list(self.queue)
}, f)
7. 常见问题解决方案
问题1:网站返回动态渲染内容
- 解决方案:集成Selenium或Playwright
yaml复制render:
enable: true
timeout: 10000 # 毫秒
问题2:验证码拦截
- 解决方案:配置打码平台API
yaml复制captcha:
service: "ruokuai"
username: "your_username"
password: "your_password"
问题3:数据格式不一致
- 解决方案:添加数据清洗管道
python复制def clean_price(self, price_str):
return float(''.join(c for c in price_str if c.isdigit() or c == '.'))
这套框架在我团队内部已经稳定运行两年多,累计采集过327个不同类型的网站。最让我自豪的是,新成员入职后只需学习YAML配置语法就能快速上手开发,而不用深入理解爬虫引擎的实现细节。对于需要定制化开发的场景,Python部分的开放架构也允许灵活扩展。
