1. Scrapy Requests类核心机制解析
在Scrapy框架中,Requests类是实现网络爬虫最基础也是最核心的组件。与直接使用Python的requests库不同,Scrapy的Request对象被设计用于框架的异步架构中,具有更强大的扩展性和集成度。
1.1 Request对象构造参数详解
创建Scrapy Request对象时,最基础的调用方式如下:
python复制from scrapy import Request
request = Request(
url='http://example.com',
callback=self.parse_response,
method='GET',
headers={'User-Agent': 'Mozilla/5.0'},
cookies={'session_id': 'abc123'},
meta={'proxy': 'http://proxy.example.com:8080'},
dont_filter=False
)
关键参数说明:
url:目标URL字符串或可迭代的URL列表callback:响应处理函数,默认为Spider类的parse方法method:HTTP方法,默认为'GET'headers:请求头字典,会覆盖默认请求头cookies:cookie字典或列表meta:元数据字典,用于在请求和响应之间传递数据dont_filter:是否跳过去重过滤器,默认为False
重要提示:meta参数是Request类最强大的特性之一,可以传递任意Python对象,常用于:
- 代理设置
- 请求重试配置
- 页面深度记录
- 自定义数据处理标记
1.2 请求处理流程剖析
当Request对象被yield后,会经历以下处理阶段:
-
调度器阶段:
- 检查dont_filter标志
- 通过dupefilter进行去重判断
- 进入调度队列排序
-
下载器中间件处理:
- 按优先级顺序执行process_request方法
- 可修改请求头、代理设置等
- 可返回Response对象直接跳过下载
-
下载器执行:
- 通过Twisted异步引擎发送HTTP请求
- 支持HTTP/HTTPS协议
- 自动处理重定向(除非meta中设置dont_redirect=True)
-
响应处理:
- 通过下载器中间件process_response方法
- 最终传递给callback函数处理
python复制# 典型Spider中的请求生成示例
def start_requests(self):
for page in range(1, 6):
yield Request(
url=f'https://example.com/page/{page}',
callback=self.parse_list,
meta={'page_num': page} # 传递页码信息
)
2. 高级请求配置技巧
2.1 定制化请求头管理
在实际爬虫项目中,合理设置请求头能显著降低被反爬的概率。Scrapy提供了多种请求头管理方式:
全局默认头设置:
在settings.py中配置:
python复制DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
}
动态请求头中间件:
python复制class RandomUserAgentMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(USER_AGENT_LIST)
request.headers['Accept-Encoding'] = 'gzip, deflate'
请求级头覆盖:
python复制yield Request(
url,
headers={
'Referer': 'https://example.com',
'X-Requested-With': 'XMLHttpRequest'
}
)
2.2 表单提交与AJAX请求
处理登录表单提交:
python复制FormRequest.from_response(
response,
formdata={'username': 'admin', 'password': 'secret'},
callback=self.after_login
)
模拟AJAX请求:
python复制yield JsonRequest(
'https://api.example.com/data',
data={'param1': 'value1'},
headers={'X-Requested-With': 'XMLHttpRequest'}
)
2.3 请求重试与错误处理
Scrapy内置了完善的请求重试机制,可通过以下配置优化:
python复制# settings.py
RETRY_TIMES = 3 # 默认重试次数
RETRY_HTTP_CODES = [500, 502, 503, 504, 408] # 需要重试的状态码
# 请求级别控制
yield Request(
url,
meta={
'max_retry_times': 5, # 覆盖全局设置
'retry_on_timeout': True
}
)
自定义重试中间件示例:
python复制class CustomRetryMiddleware:
def process_response(self, request, response, spider):
if response.status == 429:
retry_after = int(response.headers.get('Retry-After', 60))
new_request = request.copy()
new_request.dont_filter = True
return new_request.replace(
meta={'retry_times': request.meta.get('retry_times', 0) + 1}
)
return response
3. 响应处理深度解析
3.1 Response对象结构
Scrapy的Response对象比requests库的响应对象功能更强大:
python复制def parse(self, response):
# 响应属性
print(response.url) # 最终URL(考虑重定向)
print(response.status) # HTTP状态码
print(response.headers) # 响应头
print(response.request) # 对应的Request对象
# 内容提取方法
print(response.css('title::text').get()) # CSS选择器
print(response.xpath('//title/text()')) # XPath选择器
print(response.json()) # JSON响应解析
3.2 选择器高级用法
链式选择:
python复制for item in response.css('div.product'):
yield {
'name': item.css('h3::text').get(),
'price': item.css('span.price::text').re_first(r'\d+\.\d{2}'),
'specs': dict(zip(
item.css('div.specs dt::text').getall(),
item.css('div.specs dd::text').getall()
))
}
正则表达式配合:
python复制# 提取所有数字
numbers = response.css('::text').re(r'\d+')
# 带分组的正则
for match in response.css('div.info').re(r'(\w+):\s*([^<]+)'):
print(f"{match[0]} = {match[1]}")
3.3 二进制内容处理
处理文件下载:
python复制def parse_pdf(self, response):
self.logger.info(f"PDF size: {len(response.body)} bytes")
filename = response.url.split('/')[-1]
with open(filename, 'wb') as f:
f.write(response.body)
图片管道集成:
python复制# items.py
class ProductItem(scrapy.Item):
image_urls = scrapy.Field()
images = scrapy.Field()
# spider.py
yield ProductItem(
image_urls=[response.urljoin(img) for img in response.css('img::attr(src)').getall()]
)
4. 实战问题排查指南
4.1 常见错误与解决方案
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 请求未执行 | 未yield Request对象 | 确保在parse方法中使用yield而非return |
| 回调函数未触发 | 请求被过滤或丢弃 | 检查dont_filter设置和日志中的过滤原因 |
| 响应内容为空 | 反爬机制触发 | 添加随机延迟、更换User-Agent、使用代理 |
| JSON解析失败 | 响应不是有效JSON | 先打印response.text检查实际内容 |
| 重定向循环 | 会话状态异常 | 检查cookies处理,设置dont_redirect=True调试 |
4.2 调试技巧
请求日志分析:
python复制# settings.py
LOG_LEVEL = 'DEBUG'
Shell即时调试:
bash复制scrapy shell 'https://example.com'
>>> view(response) # 在浏览器中查看
>>> from scrapy.utils.response import open_in_browser
>>> open_in_browser(response)
性能分析:
python复制# settings.py
EXTENSIONS = {
'scrapy.extensions.corestats.CoreStats': 0,
'scrapy.extensions.telnet.TelnetConsole': 0,
'scrapy.extensions.memusage.MemoryUsage': 0,
'scrapy.extensions.memdebug.MemoryDebugger': 0,
'scrapy.extensions.closespider.CloseSpider': 0,
'scrapy.extensions.logstats.LogStats': 0,
'scrapy.extensions.spiderstate.SpiderState': 0,
'scrapy.extensions.throttle.AutoThrottle': 0,
}
4.3 性能优化建议
-
并发控制:
python复制# settings.py CONCURRENT_REQUESTS = 32 # 默认16 CONCURRENT_REQUESTS_PER_DOMAIN = 8 # 默认8 -
智能限速:
python复制AUTOTHROTTLE_ENABLED = True AUTOTHROTTLE_START_DELAY = 5.0 AUTOTHROTTLE_MAX_DELAY = 60.0 -
DNS缓存:
python复制DNSCACHE_ENABLED = True DNS_TIMEOUT = 60 -
连接池优化:
python复制DOWNLOADER_CLIENTCONTEXTFACTORY = 'scrapy.core.downloader.contextfactory.BrowserLikeContextFactory'
在实际项目中,我通常会先使用较低并发测试目标网站的响应情况,然后逐步提高并发数,同时监控服务器的响应时间和错误率。当发现响应时间明显增加或出现错误时,就找到了最佳的并发平衡点。
