Scrapy框架进阶
Scrapy的启动和debug
-
命令行
scrapy crawl jd_search
-
启动脚本
# 新建run.py from scrapy import cmdline command = "scrapy crawl jd_search".split() cmdline.execute(command)
Scrapy Item
只是对解析的结构化结果进行一个约束, 在到达pipeline前就可以检查出数据错误.
Scrapy的设置
-
*ROBOTTEXT_OBEY
ROBOTTEXT_OBEY=False
获取对方网站是否允许爬虫获取数据的信息.
-
设置中间件
数字越小, 离
ENGINE
越近DOWNLOADER_MIDDLEWARES = { # 'jd_crawler_scrapy.middlewares.JdCrawlerScrapyDownloaderMiddleware': 543, 'jd_crawler_scrapy.middlewares.UAMiddleware': 100, }
-
设置PIPELINE
ITEM_PIPELINES = { 'jd_crawler_scrapy.pipelines.JdCrawlerScrapyPipeline': 300, }
-
请求限制
-
*CONCURRENT_REQUESTS
请求并发数, 通过控制请求并发数达到避免或者延缓IP被封禁
CONCURRENT_REQUESTS = 1
- CONCURRENT_REQUESTS_PER_DOMAIN 控制每个`域名`请求的并发数 - CONCURRENT_REQUESTS_IP 控制每个`IP`请求的次数. 通过这样的方式可以过掉一些对IP封禁严格的网站. - CONCURRENT_ITEMS 默认为100, 控制处理`item`的并发数. 如果我存入的数据库性能比较差, 通过这样的方式解决防止数据库崩溃的情况. - ***DOWNLOAD_DELAY** 默认为0, 控制请求的频率. 在调度完一个请求后, 休息若干秒. > Scrapy会自动帮我们进行随机休息 (DOWNLOAD_DELAY - 0.5, DOWNLOAD_DELAY + 0.5)
DOWNLOAD_DELAY = 2
- ***DOWNLOAD_TIMEOUT** 控制每个请求的超时时间. 通过这样的方式解决IP代理池质量差的问题.
根据自己的IP代理池质量自定决定
DOWNLOAD_TIMEOUT = 6
- ***REDIRECT_ENABLE** 默认为`True`, 建议修改为`False`, 因为大部分情况下, 重定向都是识别出你当前身份有问题, 重定向到`log in`页面
-
-
重试机制
-
*RETRY_ENABLE
RETYR_ENABLE = False
默认为`True`, 建议改成`False`, 然后自己重写重试中间件 - RETRY_TIMES 控制重新次数, RETRY_TIMES其实是当前项目的兜底配置 > 如果当前请求失败后永远会重试, 正好你请求的接口是收费的, 万一有一天报错, 那么产生的费用是巨大的.
RETRY_TIMES = 3
- RETRY_HTTP_CODES
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]
-
-
过滤器
-
设置中指定过滤器
-
DUPEFILTER_CLASS = “jd_crawler_scrapy.middlewares.MyRFPDupeFilter”
```
-
Spider中打开过滤器
yield scrapy.FormRequest( dont_filter=False, url=url, method='GET', # formdata=data, callback=self.parse_search )
-
过滤器
from scrapy.dupefilters import RFPDupeFilter from w3lib.url import canonicalize_url from scrapy.utils.python import to_bytes import hashlib import weakref class MyRFPDupeFilter(RFPDupeFilter): """ 过滤器是在到达下载器之前就生成了过滤指纹, 如果我们的下载器中间件报错了, 那么过滤指纹仍然生效, 但是没有实际请求. 所以我们可以通过一些特殊参数来进行自定义过滤规则 """ def request_fingerprint(self, request, include_headers=None, keep_fragments=False): cache = _fingerprint_cache.setdefault(request, {}) cache_key = (include_headers, keep_fragments) if cache_key not in cache: fp = hashlib.sha1() fp.update(to_bytes(request.method)) fp.update(to_bytes(canonicalize_url(request.url, keep_fragments=keep_fragments))) fp.update(request.body or b'') fp.update(request.meta.get("batch_no", "").encode("utf-8")) cache[cache_key] = fp.hexdigest() return cache[cache_key]
-
LOG
-
LOG_ENABLE
默认为
True
, 是否使用log -
LOG_FILE
设置保存的log文件目录
-
LOG_LEVEL(按严重程序排序)
- CRITICAL
- ERROR
- WARNING
- INFO
- DEBUG
-
Scrapy的中间件
-
请求头中间件
class UAMiddleware: def process_request(self, request, spider): request.headers["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"
-
重试中间件
from scrapy.downloadermiddlewares.retry import RetryMiddleware from scrapy.utils.response import response_status_message class MyRetryMiddleware(RetryMiddleware): """ 解决对方服务器返回正常状态码200, 但是根据IP需要进行验证码验证的情况. 我们可以通过换IP可以解决验证码, 那么就应该重试. """ def process_response(self, request, response, spider): if request.meta.get('dont_retry', False): return response if "验证码" in response.text: reason = response_status_message(response.status) return self._retry(request, reason, spider) or response return response
Meta
-
信息传递
-
在创建
REQEUSTS
对象时设置metayield scrapy.FormRequest(meta={"keyword": keyword, "sta_date": sta_date})
-
在
REQUESTS
对象流转中修改meta- download_slot
- download_latency
-
在
RESPONSE
对象中获取meta#等同于response.request.meta response.meta
-
-
自定义单个请求的配置
https://docs.scrapy.org/en/latest/topics/request-response.html?highlight=meta#topics-request-meta
-
dont_redirect
如果设置为
True
, 当前请求则不会重定向. -
dont_retry
如果设置为
True
, 当前请求则不会重试. -
max_retry_times
设置最大重试次数.
-
dont_merge_cookies
和cookiejar
操作cookie的meta参数, 但是不建议这么使用, 一般来说我们直接设置
request.headers["cookie"] = "......."
-
proxy
设置请求代理
request.meta['proxy'] = '127.0.0.1:8989'
-
设置优先级
如果你设置了优先级队列, 那么可以只是
priority
参数决定请求的顺序# 数字越小, 优先级越高 request.meta['priority'] = 10
-
异常处理
异常处理时scrapy最大的痛点, 因为你一定要熟悉事件的流向.
-
Spiders组件
在异常处理中, Spider组件其实是处理
RESPONSE
对象或者请求之后产生的异常, 一般作为一次请求异常处理的终点, 也就是指定的回调函数errorback
.-
errorback
处理不可控的异常
def start_request(self): yield scrapy.FormRequest(errorback=self.process_error) def process_error(self, failure): print(failure) # 记录异常 # 发送通知 # 重做任务 ...
-
failure.request
当前异常请求对象
-
failure.value
当前的异常对象
-
-
CloseSpider
遇到像cookie过期, 账号警告, 代理池空了这样严重的错, 需要关闭实例, 可抛出CloseSpider异常, 该异常最终会流向
ENGINE
后关闭爬虫实例.from scrapy.exceptions import CloseSpider
-
-
中间件
处理可控的异常
def process_exception(self, request, exception, spider): pass
-
返回None
由下一层中间件继续处理, 如果你指定了
errback
, 最终会到达errback
-
返回
REPONSE
对象中断异常链, 返回的
RESPONSE
对象会到达Spiders
组件 -
返回
Request
中断异常链, 返回的
Request
对象将到达ENGINE
由SCHEDULER
重新调度.def process_exception(self, request, exception, spider): # 如果异常是cookie池空了, 可以在这里完成cookie池的补充 # 补充cookie池 if isinstance(exception, IndexError): # 我已经知道异常产生原因, 所以免除当前重试次数的计数 retry_times = request.meta.get('retry_times', 1) request.meta['retry_times'] = retry_times - 1 return request
-