zoukankan      html  css  js  c++  java
  • Scrapy 深入了解2 [管道类,中间件,scrapy中使用selenium, scrapy框架爬取效率提升]

    Scrapy 深入了解2

    利用ImagesPipeline管道类爬取图片

    ### 编码流程
    	- 从spider爬虫文件夹下的爬虫文件 解析出图片地址
    	- 将图片的地址封装到 item中,并提交到管道
    	- 管道文件中自定义一个 ImagesPipeline为父类的管道类
    	- 重写 3 个方法
    		- def get_media_requests(self,item,info) # 对 图片地址发送请求
    		- def file_path(self,request,response=None,info=None) # 保存图片
    		- def item_completed(self,result,item,info)  # 提交到下一个 管道类
    	- 在settings配置文件中 `开启管道` 且 加上 IMAGES_STORE='./imgLib' 图片存储路径
        
    
    ### scrapy 如何手动 发送请求 
    	- GET 请求  
    		- yield scrapy.Request(url,callback)
    	- POST 请求
    		- yield scrapy.FormRequest(url,callback,formdata)
            
    #### 爬虫文件中 ,start_urls 默认是发送 GET 请求, 如何对起始URL发送post请求呢?
    	- 重写父类方法 start_requests(self)
        	def start_resquests(self):
                for url in self.start_urls:
                    #  callback 是 回调解析函数
                    #  formdata 是 data 参数
                    yield scrapy.formRequest(url,callback=self.parse,formdata={})
    
    pipelines.py 代码示例
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    
    
    # useful for handling different item types with a single interface
    from itemadapter import ItemAdapter
    
    # class ImgproPipeline:
    #     def process_item(self, item, spider):
    #         print('item',item)
    #         return item
    
    ####### 此处有一个大坑 ,需要安装pillow模块, 必须的
    from scrapy.pipelines.images import ImagesPipeline
    import scrapy
    class ImgproPipeline(ImagesPipeline):
    
        # 1. 此方法  是 对 item中的image_src 进行网络请求
        def get_media_requests(self, item, info):
            image_src = item['image_src']
            yield scrapy.Request(url=image_src)
    
        # 2. 保存图片
        def file_path(self, request, response=None, info=None, *, item=None):
            file_name = request.url.split('/')[-1]
            print(file_name)
            return file_name
    
        # 3. 提交到下一个管道类
        def item_completed(self, result,item, info):
            return item
    
    imgspider.py
    import scrapy
    
    from ..items import ImgproItem
    
    
    class ImgspiderSpider(scrapy.Spider):
        name = 'imgspider'
        # allowed_domains = ['www.baidu.com']
        start_urls = ['http://www.521609.com/tuku/']
    
        # 定义一个 通用的URL
        url = 'http://www.521609.com/tuku/index_%d.html'
    
        pageNum = 1
    
        def parse(self, response):
            li_list = response.xpath('//ul[@class="pbl "]/li')
            for li in li_list:
                image_src = "http://www.521609.com" + li.xpath('./a/img/@src').extract_first()
                item = ImgproItem()
                item['image_src'] = image_src
                yield item
    
            if self.pageNum < 3:
                self.pageNum += 1
                url = format(self.url%self.pageNum)
                print('url',url)
                yield scrapy.Request(url=url, callback=self.parse)
    
    

    如何提高框架爬虫的效率

    增加并发:
            默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
    
        降低日志级别:
            在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘ERROR’
    
        禁止cookie:
            如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
    
        禁止重试:
            对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
    
        减少下载超时:
            如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 1 超时时间为10s
    
    

    scrapy 的核心组件

    # 引擎   处理所有的任务流
    # 调度器  请求的过滤和压入队列
    # 下载器 , 基于异步 twisted网络模型
    # 爬虫   爬取URL地址, 和 parse 解析结果
    # 管道   存储数据
    

    Scrapy 中间件

    ### 中间有哪些?
    	- 下载中间件(推荐)
    	- 爬虫中间件
    
    ### 下载中间件的作用
    	- 拦截所有的请求和响应
    
    ### 为什么拦截请求
    	- 1. 对请求的 `头信息` , 进行修改(UA,conntion)
    	- 2. 在 process_request 下载中间件的这个方法 设置 请求头
        	 request.headers['User-Agent'] = random.choices(user_agent_list)
    
    	- 3. 使用代理,对IP进行修改.同第二步
        	 request.meta['proxy'] = random.choices(http_proxy_lst)
    ### 为什么拦截响应
    	- 1. 篡改响应数据
    	- 2. 篡改响应对象(推荐)
    
    

    网易新闻爬取实战

    # 分析
    	1. 对 国内,国际,军事数据爬取
    	2. 对 新闻标日和详情页数据爬取
    	3. 利用百度提取关键词 , 提取关键词
    	4. 存储数据到 MySQL 和 Redis中(直接存储字典需要2.10.6版本的Redis工具包)
    # 可能出现的问题
    	1. 校验数据是否爬取到
    	2. 对响应对象(动态加载的数据,采用 selenium 可见即可得方式获取)
    	3. redis的版本
    

    news.py

    import scrapy
    from selenium import webdriver
    from ..items import WangyiproItem
    from aip import AipNlp
    
    """ 你的 APPID AK SK """
    APP_ID = 'XXXX'
    API_KEY = 'xxxx'
    SECRET_KEY = 'xxxx'
    
    
    class NewsSpider(scrapy.Spider):
        name = 'news'
        # allowed_domains = ['www.wangyi.com']
        start_urls = ['https://news.163.com/']
        client = AipNlp(APP_ID, API_KEY, SECRET_KEY)
        plate_urls = []
        # 实例化 浏览器自动化对象
        options = webdriver.ChromeOptions()
        options.binary_location = r"E:Program Files (x86)GoogleChromeApplicationchrome.exe"
    
        # bro = webdriver.Chrome('chromedriver.exe', options=options)
        bro = webdriver.Chrome(executable_path=r'E:安装包谷歌下载文件软件包chromedriver.exe', options=options)
    
        # 解析出每一个网易新闻的板块URL
        def parse(self, response):
            li_lst = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div//li')
            indexs = [3, 4, 6, 7, 8]
            for index in indexs:
                li_plate = li_lst[index]
                # 解析到每一个板块对应到的URL
                plate_url = li_plate.xpath('./a/@href').extract_first()
                # 存放 5个板块对应的URL
                self.plate_urls.append(plate_url)
                # 对 具体的板块 URL 手动发送请求
                yield scrapy.Request(url=plate_url, callback=self.parse_plate_page)
    
        def parse_plate_page(self, response):
            '''
                # 此方法 用来解析每一个板块对应的新闻数据
                # 此处的 response 是不满足需求的,因为获取到的Response不包含动态加载的新闻数据
                # 因此 需要修改response 板块响应数据. 需要利用到中间件
            :param response:
            :return:
            '''
    
            div_lst = response.xpath('//div[@class="ndi_main"]/div')
    
            for div in div_lst:
                news_title = div.xpath('.//div[@class="news_title"]//a/text()').extract_first()
    
                news_detail_url = div.xpath('.//div[@class="news_title"]//a/@href').extract_first()
                item = WangyiproItem()
                item['title'] = news_title
                if not news_title and not news_detail_url:
                    continue
                yield scrapy.Request(url=news_detail_url, callback=self.parse_deatile_method, meta={'item': item})
        def parse_deatile_method(self, response):
            # 接收item
            item = response.meta['item']
    
            news_content = response.xpath('//*[@id="content"]//div[@class="post_body"]//text()').extract()
            news_content = ''.join(news_content)
            item['content'] = news_content
            yield item
    
        # 该方法是 在最后被执行一次
        def closed(self, spider):
            # 关闭浏览器
            self.bro.quit()
    
    

    middlewares.py

    import time
    import scrapy
    from scrapy import signals
    from itemadapter import is_item, ItemAdapter
    from scrapy.http import HtmlResponse
    
    
    class WangyiproDownloaderMiddleware:
        # Not all methods need to be defined. If a method is not defined,
        # scrapy acts as if the downloader middleware does not modify the
        # passed objects.
    
        @classmethod
        def from_crawler(cls, crawler):
            # This method is used by Scrapy to create your spiders.
            s = cls()
            crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
            return s
    
        def process_request(self, request, spider):
            return None
    
        # 拦截到所有的响应对象
        def process_response(self, request, response, spider):
            '''
                # 此处 修改 不满足条件的5个响应对象
                # 1. 根据 5个 板块请求对象 , 定位到对应的响应对象
                # 2. spider 是爬虫文件的内部的数据,打点调用
            :param request:
            :param response:
            :param spider:
            :return:
            '''
            plate_urls = spider.plate_urls
            bro = spider.bro
    
            if request.url in plate_urls:
                # 重新获得 5个板块的响应数据
                # 需要 重新实例化一个响应对象,需要利用 HtmlResponse
    
                bro.get(request.url)
                time.sleep(1)
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                time.sleep(1)
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                time.sleep(1)
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                time.sleep(1)
                bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
    
                # page_text 页面 数据
                page_text = bro.page_source
                new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
                # 方法 2 , 待测试
                # new_response = scrapy.Request(url=response.url)
                return new_response
            else:
                return response
    
        def process_exception(self, request, exception, spider):
            pass
    
        def spider_opened(self, spider):
            spider.logger.info('Spider opened: %s' % spider.name)
    
    

    pipelines.py

    from itemadapter import ItemAdapter
    import pymysql
    from redis import Redis
    
    
    def NplDealwith(client, title, content):
        result = client.topic(title, content)
        try:
            score = result['item']['lv2_tag_list'][0]['score']
            tag = result['item']['lv2_tag_list'][0]['tag']
            return score, tag
        except Exception as e:
            print("NPL:",e)
            return  "Score Error","KeyWord Error"
    
    
    class WangyiMySQL(object):
        conn = None
        cursor = None
    
        def open_spider(self, spider):
            self.conn = pymysql.Connection(
                host='127.0.0.1',
                port=3306,
                user='root',
                password='xxx',
                db='xxx',
                charset='utf8'
            )
            print(self.conn)
    
        def process_item(self, item, spider):
            score, tag = NplDealwith(spider.client, item['title'], item['content'])
            item['keyword'] = tag
            item['score'] = score
            sql = 'INSERT INTO wangyi_news values ("%s","%s","%s","%s")' % (
                item['title'], item['content'], tag, score)
            self.cursor = self.conn.cursor()
            try:
                self.cursor.execute(sql)
                self.conn.commit()
            except Exception as e:
                print(e)
                self.conn.rollback()
    
            # 交给下一个管道执行
            return item
    
        def close_spider(self, spider):
            self.cursor.close()
            self.conn.close()
    
    
    class WangyiRedis(object):
        def open_spider(self, spider):
            self.conn = Redis(
                host='127.0.0.1',
                port=6379
            )
            print(self.conn)
    
        def process_item(self, item, spider):
            # 把 item 以字典形式存储
            self.conn.lpush('news', item)
    
        def close_spider(self, spider):
            pass
    
    
  • 相关阅读:
    Linux vim 入门 配置 及 使用初步
    Java网络编程之TCP通信
    [ javascript ] getElementsByClassName与className和getAttribute!
    找出二叉查找树中指定结点的”下一个&quot;结点(也即中序后继)
    DNS分别在什么情况下使用UDP和TCP
    高仿京东APP首页“京东快报”自己主动向上滚动的广告条
    HDU 5016 Mart Master II (树上点分治)
    Myeclipse10完美破解过程
    git format-patch 用法【转】
    Git下的冲突解决【转】
  • 原文地址:https://www.cnblogs.com/dengz/p/14829532.html
Copyright © 2011-2022 走看看