scrapy框架4——下载中间件的使用

一、下载中间件

下载中间件是scrapy提供用于用于在爬虫过程中可修改Request和Response,用于扩展scrapy的功能;比如:

  1. 可以在请求被Download之前,请求头部加上某些信息(例如:user-agent,proxy);
  2. 完成请求之后,回包需要解压等处理;(selenium)

process_request(request, spider)

当每个Request对象经过下载中间件时会被调用,优先级越高的中间件,越先调用;该方法应该返回以下对象:None/Response对象/Request对象/抛出IgnoreRequest异常;

  1. 返回None:scrapy会继续执行其他中间件相应的方法;
  2. 返回Response对象:scrapy不会再调用其他中间件的process_request方法,也不会去发起下载,而是直接返回该Response对象;
  3. 返回Request对象:scrapy不会再调用其他中间件的process_request()方法,而是将其放置调度器待调度下载;
  4. 抛出IgnoreRequest异常:已安装中间件的process_exception()会被调用,如果它们没有捕获该异常,则Request.errback会被调用;如果再没被处理,它会被忽略,且不会写进日志。

process_response(request, response, spider)

当每个Response经过下载中间件会被调用,优先级越高的中间件,越晚被调用,与process_request()相反;该方法返回以下对象:Response对象/Request对象/抛出IgnoreRequest异常。

  1. 返回Response对象:scrapy会继续调用其他中间件的process_response方法;
  2. 返回Request对象:停止中间器调用,将其放置到调度器待调度下载;
  3. 抛出IgnoreRequest异常:Request.errback会被调用来处理函数,如果没有处理,它将会被忽略且不会写进日志。

process_exception(request, exception, spider)

process_exception()process_request()抛出异常时会被调用,应该返回以下对象:None/Response对象/Request对象;

  1. 如果返回None:scrapy会继续调用其他中间件的process_exception()
  2. 如果返回Response对象:中间件链的process_response()开始启动,不会继续调用其他中间件的process_exception()
  3. 如果返回Request对象:停止中间器的process_exception()方法调用,将其放置到调度器待调度下载。

from_crawler(cls, crawler)

如果存在该函数,from_crawler会被调用使用crawler来创建中间器对象,必须返回一个中间器对象,通过这种方式,可以访问到crawler的所有核心部件,如settingssignals等。

先在setting.py中开启下载中间件

DOWNLOADER_MIDDLEWARES = {
    'douban.middlewares.DoubanDownloaderMiddleware': 543,
}
知识兔

​ 下载中间件开启后,resquest和response都会经过下载中间件;在下载器发起网络请求之前,下载中间件对resquest拦截,对其中的user-agent和ip代理进行设置。

二、通过下载中间件设置UA池和代理池

打开middlewares.py(其他未进行设置的相关代码可删除)

import random
class DoubanDownloaderMiddleware(object):
    user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
        "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
        
]
    PROXY_http = [
    '153.180.102.104:80',
    '195.208.131.189:56055',]
    PROXY_https = [
    '120.83.49.90:9000',
    '95.189.112.214:35508',]
    def process_request(self, request, spider):
        resquest.headers['User-Agent'] = random.choice(self.user_agent_list)
        return None

    def process_response(self, request, response, spider):
        
        return response

    def process_exception(self, request, exception, spider):
        h = request.url.split(':')[0]  
        if h == 'https':
            ip = random.choice(PROXY_https)
            request.meta['proxy'] = 'https://'+ip
        else:
            ip = random.choice(PROXY_http)
            request.meta['proxy'] = 'http://' + ip
        return resquest
知识兔

三、处理动态加载的数据

使用requests模块获取一张页面中动态加载的数据,需要先找到真正的url,向真正的url发送请求。而使用selenium驱动浏览器,使用起始url就可以获取整张页面的所有数据。在scrapy中如何使用selenium:
知识兔

​ 在spider文件中,照常书写代码,使用每张页面的起始url,忽略动态加载的问题。

在下载中间件中,对含有动态加载页面的url的response进行拦截,用selenium对该url发送请求,使用得到的数据对response进行‘‘篡改“,这样spider就能正常解析数据了。

import scrapy
from wangyi.items import WangyiItem
from selenium import webdriver
class WyspiderSpider(scrapy.Spider):
    name = 'wyspider'
    start_urls = ['https://news.163.com/']
    chrome_options = webdriver.chrome.options.Options()
    chrome_options.add_experimental_option('excludeSwitches', ['enable-automation']) #规避检测
    path = 'D:\爬虫\chromedriver.exe'
    chrome = webdriver.Chrome(executable_path=path,options = chrome_options)
    check_list = []
    def parse(self, response):
        url_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul//a/@href').extract()
        for i in [3,4,6,8]:       
            block = url_list[i]
            self.check_list.append(block)   #收集含有动态加载页面的url
            yield scrapy.Request(callback=self.parse_title,url=block)
    def parse_title(self,response):
        temp = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div/div/div[1]/h3')
        for i in temp:
            title = i.xpath('./a/text()').extract_first()
            content_url = i.xpath('./a/@href').extract_first()
            item = WangyiItem()
            item['title'] = title
            yield scrapy.Request(callback=self.parse_content,url=content_url,meta={'item':item})
    def parse_content(self,response):
        item = response.meta['item']
        content_list = response.xpath('//*[@id="endText"]/p/text()').extract()
        content = ''.join(content_list).replace(' ','')
        content = content.replace('\n','')
        item['content'] = content
        print(content)
        yield item
    def closed(self,spider):  # 重写父类,结束后关闭
        self.chrome.close()
知识兔

在middlewares.py中

from scrapy import signals
from scrapy.http import HtmlResponse

class WangyiDownloaderMiddleware(object):
   
    def process_response(self, request, response, spider):
        if request.url in spider.check_list:   
   # 判断是否为含有动态加载页面的url,含有则使用spider中的浏览器对象进行get重新发送
            spider.chrome.get(url = request.url)   
    #将selenium的浏览器对象在Spider中进行实例化和关闭,减少开启和关闭。
            #spider.chrome.execute_script('window.scrollTo(0,document.body.scrollHeight)')
            #time.sleep(2)
            page = spider.chrome.page_source
            return HtmlResponse(url=request.url,body=page,
                                encoding='utf8',request=request) 
                          # HtmlResponse的对象,response此时才包含要解析的数据(body=page)
        else:
            return response
知识兔
计算机