1. Scrapy Requests类深度解析
在Python爬虫开发中,Scrapy框架的Requests类扮演着至关重要的角色。作为Scrapy的核心组件之一,它负责处理所有HTTP请求和响应,是爬虫与目标网站交互的桥梁。与标准库requests不同,Scrapy的Requests类专为大规模爬取场景设计,内置了许多高级功能。
1.1 Requests类核心功能
Scrapy的Request对象封装了一个HTTP请求的所有要素,主要包含以下核心属性:
- url:请求的目标地址
- method:HTTP方法(默认为GET)
- headers:请求头字典
- body:请求体内容(对于POST请求)
- meta:用于在请求间传递数据的特殊字典
- callback:请求完成后的回调函数
- errback:请求失败时的错误处理函数
- cookies:请求携带的cookies
- priority:请求优先级(调度器使用)
典型创建方式:
python复制from scrapy import Request
request = Request(
url='https://example.com',
method='GET',
headers={'User-Agent': 'Mozilla/5.0'},
callback=self.parse_response,
meta={'page': 1}
)
1.2 请求处理流程
当Scrapy引擎处理一个Request对象时,会经历以下完整生命周期:
- 调度器接收Request并分配优先级
- 下载器中间件处理(process_request阶段)
- 下载器执行实际HTTP请求
- 下载器中间件处理(process_response阶段)
- 调用回调函数或错误处理函数
- 返回处理结果给引擎
这个过程中,Request对象会被序列化/反序列化多次,因此需要注意:
不要在Request对象上存储不可序列化的数据(如数据库连接等)
2. 高级请求配置技巧
2.1 请求头动态设置
在实际爬取中,合理的请求头设置能显著降低被封禁风险。除了基础的User-Agent,还需要注意:
- Accept-Language
- Referer
- Accept-Encoding
- Connection
推荐使用随机请求头中间件:
python复制from fake_useragent import UserAgent
class RandomUserAgentMiddleware:
def process_request(self, request, spider):
ua = UserAgent()
request.headers.setdefault('User-Agent', ua.random)
request.headers.setdefault('Accept-Language', 'en-US,en;q=0.9')
2.2 请求体处理
对于POST请求,需要正确处理请求体格式:
- Form表单数据:
python复制FormRequest(
url='https://example.com/login',
formdata={'username': 'admin', 'password': 'secret'}
)
- JSON数据:
python复制Request(
url='https://example.com/api',
method='POST',
body=json.dumps({'query': 'test'}),
headers={'Content-Type': 'application/json'}
)
- 文件上传:
python复制from scrapy.http import FormRequest
FormRequest(
url='https://example.com/upload',
formdata={'file': ('report.pdf', open('report.pdf', 'rb'))}
)
2.3 请求优先级与调度控制
Scrapy使用优先级队列管理请求,数值越小优先级越高。合理设置优先级可以优化爬取顺序:
python复制# 首页高优先级
yield Request(url=homepage, priority=0)
# 详情页中等优先级
yield Request(url=detail_page, priority=50)
# 图片等资源低优先级
yield Request(url=image_url, priority=100)
3. 响应处理与数据提取
3.1 Response对象解析
Scrapy的Response对象包含以下重要属性和方法:
- url:响应URL
- status:HTTP状态码
- headers:响应头
- body:原始响应体
- text:解码后的文本
- css():CSS选择器方法
- xpath():XPath选择器方法
- json():解析JSON响应
典型处理流程:
python复制def parse_response(self, response):
if response.status != 200:
self.logger.error(f'请求失败: {response.url}')
return
data = {
'title': response.css('h1::text').get(),
'content': response.xpath('//div[@class="content"]/text()').getall(),
'url': response.url
}
if 'api' in response.url:
data.update(response.json())
yield data
3.2 高级选择器技巧
- 链式选择:
python复制response.css('div.product').xpath('./a/@href').get()
- 正则表达式提取:
python复制response.css('script::text').re_first(r'var productId = "(\d+)"')
- 属性选择:
python复制response.css('img::attr(src)').getall()
- 排除元素:
python复制response.css('div.content').xpath('.//*[not(self::script)]/text()').getall()
4. 实战:分页爬取完整实现
4.1 基础分页实现
以电商网站为例,完整分页爬取实现:
python复制import scrapy
from urllib.parse import urljoin
class EcommerceSpider(scrapy.Spider):
name = 'ecommerce'
start_urls = ['https://example.com/products']
def parse(self, response):
# 提取产品详情链接
for product in response.css('div.product-item'):
yield {
'name': product.css('h3::text').get(),
'price': product.css('.price::text').get(),
'link': product.css('a::attr(href)').get()
}
# 处理分页
next_page = response.css('a.next-page::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)
4.2 AJAX分页处理
对于动态加载的分页内容,通常需要:
- 分析AJAX请求接口
- 构造合适的请求参数
- 处理JSON响应
示例实现:
python复制class AjaxSpider(scrapy.Spider):
name = 'ajax_pagination'
def start_requests(self):
base_url = 'https://example.com/api/products'
for page in range(1, 101): # 假设最多100页
yield scrapy.Request(
url=f'{base_url}?page={page}',
callback=self.parse_api,
meta={'page': page}
)
def parse_api(self, response):
data = response.json()
for item in data['products']:
yield {
'name': item['name'],
'price': item['price'],
'page': response.meta['page']
}
5. 高级应用与性能优化
5.1 请求去重与过滤
Scrapy内置了基于URL的重复请求过滤,但实际项目中可能需要更复杂的去重逻辑:
python复制from scrapy.dupefilters import RFPDupeFilter
class CustomDupeFilter(RFPDupeFilter):
def request_fingerprint(self, request):
# 基于URL和POST数据生成指纹
if request.method == 'POST':
return hash(request.url + str(sorted(request.body.items())))
return super().request_fingerprint(request)
然后在settings.py中配置:
python复制DUPEFILTER_CLASS = 'myproject.custom_filters.CustomDupeFilter'
5.2 请求延迟与并发控制
合理配置并发参数可以避免被封禁:
python复制# settings.py
CONCURRENT_REQUESTS = 16 # 全局并发数
CONCURRENT_REQUESTS_PER_DOMAIN = 4 # 单域名并发
DOWNLOAD_DELAY = 0.5 # 请求间隔
AUTOTHROTTLE_ENABLED = True # 自动限速
对于特别敏感的网站,可以使用动态延迟:
python复制class DynamicDelayMiddleware:
def process_request(self, request, spider):
domain = urlparse(request.url).netloc
if domain in sensitive_domains:
request.meta['download_latency'] = random.uniform(1, 3)
6. 常见问题与调试技巧
6.1 请求失败处理
完善的错误处理应包括:
python复制def parse(self, response):
try:
# 正常解析逻辑
data = extract_data(response)
yield data
except Exception as e:
yield {
'url': response.url,
'status': response.status,
'error': str(e),
'retries': response.meta.get('retry_times', 0)
}
def errback(self, failure):
# 处理各种请求异常
if failure.check(TimeoutError):
self.logger.warning(f'请求超时: {failure.request.url}')
elif failure.check(HttpError):
response = failure.value.response
self.logger.error(f'HTTP错误: {response.status}')
6.2 调试技巧
- 使用scrapy shell快速测试选择器:
bash复制scrapy shell 'https://example.com'
>>> response.css('h1::text').get()
- 查看请求/响应详情:
python复制# settings.py
DEBUG_PROPAGATE_EXCEPTIONS = True
# 或者在回调中
def parse(self, response):
self.logger.debug(f'Request headers: {response.request.headers}')
self.logger.debug(f'Response headers: {response.headers}')
- 使用中间件记录请求:
python复制class RequestLoggerMiddleware:
def process_response(self, request, response, spider):
spider.logger.info(f'{request.method} {request.url} -> {response.status}')
return response
7. 性能优化实战
7.1 批量请求处理
对于大量相似请求,可以使用scrapy的Request批量生成:
python复制def start_requests(self):
base_url = 'https://example.com/product/{}'
for product_id in range(1000, 2000):
yield Request(
url=base_url.format(product_id),
callback=self.parse_product,
meta={'product_id': product_id}
)
7.2 缓存请求结果
启用缓存可以显著提升开发效率:
python复制# settings.py
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 86400 # 缓存1天
HTTPCACHE_DIR = 'httpcache'
HTTPCACHE_IGNORE_HTTP_CODES = [500, 502, 503, 504]
7.3 分布式请求处理
结合scrapy-redis实现分布式爬取:
python复制# settings.py
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
REDIS_URL = 'redis://localhost:6379'
# 爬虫实现
from scrapy_redis.spiders import RedisSpider
class MyDistributedSpider(RedisSpider):
name = 'distributed'
redis_key = 'my_spider:start_urls'
8. 安全与合规注意事项
8.1 robots.txt遵守
Scrapy默认遵守robots.txt规则,但有时需要调整:
python复制# settings.py
ROBOTSTXT_OBEY = True # 建议生产环境保持True
ROBOTSTXT_USER_AGENT = 'MyBot/1.0' # 指定robots.txt检查使用的UA
8.2 请求频率控制
合理的爬取频率设置:
python复制# settings.py
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5.0
AUTOTHROTTLE_MAX_DELAY = 60.0
AUTOTHROTTLE_TARGET_CONCURRENCY = 4.0
8.3 敏感数据处理
处理用户数据时的注意事项:
python复制# 在pipeline中
class DataAnonymizationPipeline:
def process_item(self, item, spider):
if 'email' in item:
item['email'] = anonymize_email(item['email'])
if 'phone' in item:
item['phone'] = anonymize_phone(item['phone'])
return item
9. 扩展与定制
9.1 自定义Request类
继承Request实现特殊需求:
python复制from scrapy import Request
class ScreenshotRequest(Request):
def __init__(self, screenshot=False, *args, **kwargs):
super().__init__(*args, **kwargs)
self.screenshot = screenshot
# 在下载器中间件中处理
class ScreenshotMiddleware:
def process_response(self, request, response, spider):
if isinstance(request, ScreenshotRequest) and request.screenshot:
take_screenshot(response)
return response
9.2 请求预处理
使用中间件预处理请求:
python复制class PreprocessMiddleware:
def process_request(self, request, spider):
if 'special' in request.url:
request.headers['X-Special-Header'] = 'value'
request.meta['handle_httpstatus_all'] = True
9.3 响应后处理
在中间件中对响应进行统一处理:
python复制class ResponseCleanMiddleware:
def process_response(self, request, response, spider):
# 统一处理响应编码
response = response.replace(encoding='utf-8')
# 清理HTML中的无效字符
if 'text/html' in response.headers.get('Content-Type', b'').decode():
body = clean_html(response.text)
return response.replace(body=body.encode('utf-8'))
return response
在实际项目中,Scrapy的Request和Response处理能力远超基本用法。通过合理组合各种中间件、扩展和定制类,可以构建出适应各种复杂场景的专业爬虫。一个经验丰富的Scrapy开发者通常会建立自己的工具库,包含常用的Request处理工具、Response解析函数和异常处理逻辑,这些积累能显著提升开发效率和质量。
