初学scrapy爬虫的一些坑
'# 初学scrapy爬虫的一些坑
一、背景与问题
Scrapy 是 Python 世界最流行的爬虫框架之一,其设计目标是实现高效的分布式爬虫系统。但初学者在使用时往往容易陷入一些常见的陷阱,比如:
- 对异步处理机制理解不深导致性能瓶颈
- 忽略中间件配置引发的反爬虫问题
- 去重机制失效导致数据重复
- 未处理响应类型错误引发的程序崩溃
- 不合理设置并发参数导致服务器压力过大
这些陷阱背后往往涉及到 Scrapy 框架的核心原理,需要从底层机制进行深入理解。
二、基本原理
Scrapy 的核心架构包含以下关键组件:
- 引擎(Engine):负责协调各组件工作,处理请求和响应
- Spider:定义爬取逻辑和初始请求
- Downloader:下载网页内容
- Item Pipeline:处理和存储爬取数据
- Middlewares:处理请求和响应的中间件
其工作流程如下:
Spider -> Engine -> Downloader -> Engine -> Spider
| |
| |
Response Item
| |
| |
Spider -> Engine -> Item Pipeline三、环境准备
# 安装Scrapy 2.x版本
pip install scrapy==2.8.2
# 创建项目
scrapy startproject myproject注意:Scrapy 1.x 和 2.x 在中间件配置方式上有显著差异,需注意版本兼容性。
四、核心实现
1. 基础爬虫实现
# myproject/spiders/example_spider.py
import scrapy
class ExampleSpider(scrapy.Spider):
name = 'example'
start_urls = ['http://example.com']
def parse(self, response):
# 提取标题
yield {'title': response.css('h1::text').get()}
# 提取链接
for link in response.css('a::attr(href)').getall():
yield {'link': link}关键点解释:
parse方法是核心解析函数response对象包含完整的响应内容css方法支持CSS选择器提取数据
2. 中间件配置
# myproject/settings.py
# 自定义中间件处理反爬虫
class MyMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = 'CustomUserAgent'
request.meta['proxy'] = 'http://127.0.0.1:8080'# myproject/middlewares.py
class MyMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = 'CustomUserAgent'
request.meta['proxy'] = 'http://127.0.0.1:8080'关键点解释:
- 中间件需要实现
process_request或process_response方法 request.meta可用于传递自定义参数- 中间件配置需在
settings.py中注册
3. 数据管道实现
# myproject/pipelines.py
class MyPipeline:
def process_item(self, item, spider):
# 存储数据到数据库
self.db.insert(item)
return item关键点解释:
- 每个 pipeline 需实现
process_item方法 - 数据处理顺序由
SPIDER_MIDDLEWARES控制 - 可使用
yield实现多阶段处理
五、完整案例
新闻网站爬虫案例
# myproject/spiders/news_spider.py
import scrapy
from scrapy.http import Request
class NewsSpider(scrapy.Spider):
name = 'news'
start_urls = ['https://example-news-site.com']
def parse(self, response):
# 提取文章列表
for article in response.css('div.article'):
yield {
'title': article.css('h2::text').get(),
'url': article.css('a::attr(href)').get()
}
# 翻页处理
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield Request(url=next_page, callback=self.parse)# myproject/pipelines.py
class NewsPipeline:
def open_spider(self, spider):
self.db = self.connect_db()
def process_item(self, item, spider):
self.db.insert({
'title': item['title'],
'url': item['url']
})
return item
def close_spider(self, spider):
self.db.close()关键点说明:
- 使用
Request实现翻页功能 - 在
open_spider和close_spider中管理数据库连接 - 使用
yield实现异步请求处理
六、源码解析
# scrapy/engine.py
class Engine:
def __init__(self, settings):
self.settings = settings
self.downloader = Downloader()
self.spider = Spider()
self.pipeline = Pipeline()
def start(self):
for url in self.spider.start_urls:
self.downloader.request(url)关键点分析:
- 引擎负责协调各组件的协作
- 使用队列管理待处理的请求
- 通过回调函数实现流程控制
# scrapy/downloader.py
class Downloader:
def request(self, url):
# 模拟网络请求
response = self.download(url)
self.engine.spider.parse(response)关键点分析:
- 使用异步IO处理网络请求
- 通过回调机制传递响应对象
- 包含重试机制和异常处理
七、进阶使用
1. 处理动态内容
# 使用Selenium处理JavaScript渲染
from selenium import webdriver
class SeleniumSpider(scrapy.Spider):
def start_requests(self):
driver = webdriver.Chrome()
yield Request(url='https://example.com', callback=self.parse, meta={'driver': driver})
def parse(self, response):
driver = response.meta['driver']
driver.get(response.url)
# 提取动态内容
yield {'content': driver.page_source}2. 处理验证码
# 使用第三方验证码识别服务
import requests
def recognize_captcha(image_url):
response = requests.post('https://api.captcha.com/recognize', files={'image': image_url})
return response.json()['text']3. 分布式爬虫
# 使用Scrapy-Redis实现分布式
from scrapy_redis.spiders import RedisSpider
class DistributedSpider(RedisSpider):
redis_key = 'scrapy:queue'
def parse(self, response):
# 分布式爬虫逻辑
pass八、性能与工程实践
1. 性能优化策略
- 调整
CONCURRENT_REQUESTS参数控制并发数 - 使用
DOWNLOAD_DELAY设置请求间隔 - 启用
TELNET_PORT进行实时监控 - 使用
DUPEFILTER_CLASS选择合适的去重策略
# settings.py
CONCURRENT_REQUESTS = 100
DOWNLOAD_DELAY = 1
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'2. 数据处理优化
- 使用批量插入代替单条插入
- 启用数据库事务
- 使用索引加速查询
# 使用批量插入
self.db.bulk_insert([{'title': t, 'url': u} for t, u in items])3. 异常处理机制
def parse(self, response):
try:
# 可能抛出异常的代码
except Exception as e:
self.logger.error(f"Error: {e}")
# 记录日志或重试机制九、常见问题与踩坑
1. 响应类型错误
# 错误示例:未处理二进制响应
response.text # 会抛出异常解决办法:
if response.headers['Content-Type'].startswith('image'):
with open('image.jpg', 'wb') as f:
f.write(response.body)
else:
return response.text2. 去重机制失效
# 错误示例:未设置指纹
yield {'url': 'http://example.com'}解决办法:
yield {'url': 'http://example.com', 'fingerprint': 'unique_id'}3. 中间件配置错误
# 错误示例:未注册中间件
SPIDER_MIDDLEWARES = {}解决办法:
SPIDER_MIDDLEWARES = {
'myproject.middlewares.MyMiddleware': 543,
}4. 反爬虫机制应对
# 错误示例:未设置headers
yield Request(url='https://example.com')解决办法:
yield Request(
url='https://example.com',
headers={'User-Agent': 'CustomUserAgent'},
meta={'proxy': 'http://127.0.0.1:8080'}
)十、最佳实践
- 合理设置并发参数:根据服务器负载动态调整
CONCURRENT_REQUESTS - 使用指纹去重:为每个页面生成唯一标识进行去重
- 分阶段处理数据:使用多个 pipeline 实现数据清洗、转换、存储
- 启用日志记录:通过
LOG_LEVEL控制日志输出级别 - 使用缓存机制:对频繁请求的接口使用缓存减少服务器压力
- 处理异常情况:为关键操作添加 try-catch 块
- 定期清理数据:避免数据库存储过多冗余数据
十一、总结
Scrapy 作为成熟的爬虫框架,其背后涉及复杂的异步处理、中间件机制和分布式架构。初学者在使用时需要特别注意:
- 正确理解异步处理机制
- 合理配置中间件和去重策略
- 处理响应类型和异常情况
- 合理设置并发参数和性能优化
- 注意数据安全和反爬虫机制
在实际项目中,Scrapy 更适合用于中大规模数据采集场景,如构建数据中台、爬取电商数据、抓取新闻资讯等。但需要注意:
- 不适合简单的小规模数据采集
- 不适合需要实时交互的场景
- 不适合处理大量动态内容
通过深入理解 Scrapy 的底层原理和正确使用其核心机制,可以有效避免常见的陷阱,提高爬虫项目的稳定性和效率。
评论已关闭