zoukankan      html  css  js  c++  java
  • python爬虫六

    其实这次的内容我是想放在上一篇的博文中的,但是上次犯懒了,就放在这里了奥。

    基于mysql持久化操作:

    将爬取数据写入文件这种情况还是少见的,因为文件无论是内存占用还是读写速度都存在一定的瑕疵,所以我们更多的是操作数据库。因为items对象已经准备好了所以我们只需要进行pipeline管道更改就可以了

    import pymysql
    class QiubaiproPipelineByMysql(object):
    
        conn = None  #mysql的连接对象声明
        cursor = None#mysql游标对象声明
        def open_spider(self,spider):
            print('开始爬虫')
            #链接数据库
            self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='qiubai')
        #编写向数据库中存储数据的相关代码
        def process_item(self, item, spider):
            #1.链接数据库
            #2.执行sql语句
            sql = 'insert into qiubai values("%s","%s")'%(item['author'],item['content'])
            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):
            print('爬虫结束')
            self.cursor.close()
            self.conn.close()
    
    - settings.py
    
    ITEM_PIPELINES = {
        'qiubaiPro.pipelines.QiubaiproPipelineByMysql': 300,
    }
    基于redis持久化操作:

    有时我们指定的数据库类型不同,可能存在存储进redis数据库的操作,同样的我们只需要对管道进行更改

    import redis
    
    class QiubaiproPipelineByRedis(object):
        conn = None
        def open_spider(self,spider):
            print('开始爬虫')
            #创建链接对象
            self.conn = redis.Redis(host='127.0.0.1',port=6379)
        def process_item(self, item, spider):
            dict = {
                'author':item['author'],
                'content':item['content']
            }
            #写入redis中
            self.conn.lpush('data', dict)
            return item

    当我们需要同时进行两种不一样的持久化存储的时候我们怎么做?

    我们只要在管道内实现定义好类,在配置文件中进行设置就可以了

    ITEM_PIPELINES = {
       'doublekill.pipelines.DoublekillPipeline': 300,
        'doublekill.pipelines.DoublekillPipeline_db': 200,
    }
    爬取多页数据:

    在之前我们介绍了scrapy的简单使用和持久化存储,但都是针对单页数据而言的,那么我们需要的数据十分不在多页的时候呢?

    这里有两种方法,我个人更倾向于第二种。

    第一种:递归爬取,在回调函数中设置,如果当前总页数没有爬取完整那么接下来再次回传给request重新发送请求

    import scrapy
    from qiushibaike.items import QiushibaikeItem
    # scrapy.http import Request
    class QiushiSpider(scrapy.Spider):
        name = 'qiushi'
        allowed_domains = ['www.qiushibaike.com']
        start_urls = ['https://www.qiushibaike.com/text/']
    
        #爬取多页
        pageNum = 1 #起始页码
        url = 'https://www.qiushibaike.com/text/page/%s/' #每页的url
    
        def parse(self, response):
            div_list=response.xpath('//*[@id="content-left"]/div')
            for div in div_list:
                #//*[@id="qiushi_tag_120996995"]/div[1]/a[2]/h2
                author=div.xpath('.//div[@class="author clearfix"]//h2/text()').extract_first()
                author=author.strip('
    ')
                content=div.xpath('.//div[@class="content"]/span/text()').extract_first()
                content=content.strip('
    ')
                item=QiushibaikeItem()
                item['author']=author
                item['content']=content
    
                yield item #提交item到管道进行持久化
    
             #爬取所有页码数据
            if self.pageNum <= 13: #一共爬取13页(共13页)
                self.pageNum += 1
                url = format(self.url % self.pageNum)
    
                #递归爬取数据:callback参数的值为回调函数(将url请求后,得到的相应数据继续进行parse解析),递归调用parse函数
                yield scrapy.Request(url=url,callback=self.parse)
    

      

    第二种(个人较为推荐):

    之前提到过,scrapy框架是一个高度封装的框架,我们可以重写其某些方法达到自定义功能的目的,例如重写starturl

    import scrapy
    from Duanzi.items import DuanziItem
    class DuanziSpider(scrapy.Spider):
        name = 'duanzi'
        # allowed_domains = ['www.xxx.com']
    
        def start_requests(self):
    
            for i in range(1,6):
                url = "https://duanziwang.com/category/经典段子/%d/"%i
                yield scrapy.Request(
                    url=url,
                    callback=self.parse
                )
    
    
        def parse(self, response):
    
            article_list = response.xpath('/html/body/section/div/div/main/article')
            for article in article_list:
                title = article.xpath('./div[1]/h1/a/text()').extract_first()
                content = article.xpath('./div[2]/p/text()').extract_first()
    
                item = DuanziItem()
                item['title'] = title
                item['content'] = content
                yield item
    

      

    上述过程我们都使用了scrapy.request为什么这个方法能够发送请求呢?这就要从scrapy结构说起了。

    scrapy有五大核心组件,分别是下载器,spider,管道,调度器,引擎

    • 引擎(Scrapy) 用来处理整个系统的数据流处理, 触发事务(框架核心)

    • 调度器(Scheduler) 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

    • 下载器(Downloader) 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)

    • 爬虫(Spiders) 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面

    • 项目管道(Pipeline) 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

    所以总的来说一次请求的过程大概是这样的

    1 首先,我们最原始的起始url是在我们爬虫文件中的,通常情况系,起始的url只有一个,当我们的爬虫文件执行的时候,首先对起始url发送请求,将起始url封装成了请求对象,将请求对象传递给了引擎,引擎就收到了爬虫文件给它发送的封装了起始URL的请求对象。我们在爬虫文件中发送的请求并没有拿到响应(没有马上拿到响应),只有请求发送到服务器端,服务器端返回响应,才能拿到响应。

    2 引擎拿到这个请求对象以后,又将请求对象发送给了调度器,队列接受到的请求都放到了队列当中,队列中可能存在多个请求对象,然后通过过滤器,去掉重复的请求

    3 调度器将过滤后的请求对象发送给了引擎,

    4 引擎将拿到的请求对象给了下载器

    5 下载器拿到请求后将请求拿到互联网进行数据下载

    6 互联网将下载好的数据发送给下载器,此时下载好的数据是封装在响应对象中的

    7 下载器将响应对象发送给引擎,引擎接收到了响应对象,此时引擎中存储了从互联网中下载的数据。

    8 最终,这个响应对象又由引擎给了spider(爬虫文件),由parse方法中的response对象来接收,然后再parse方法中进行解析数据,此时可能解析到新的url,然后再次发请求;也可能解析到相关的数据,然后将数据进行封装得到item,

    9 spider将item发送给引擎

    10 引擎将item发送给管道。

    其中,在引擎和下载中间还有一个下载器中间件,spider和引擎中间有爬虫中间件,

    下载器中间件

    可以拦截请求和响应对象,请求和响应交互的时候一定会经过下载中间件,可以处理请求和响应。

    爬虫中间件

    拦截请求和响应,对请求和响应进行处理。

    post请求:

    我们需要通过重写start_request方法来进行post请求

    def start_requests(self):
            #请求的url
            post_url = 'http://fanyi.baidu.com/sug'
            # post请求参数
            formdata = {
                'kw': 'wolf',
            }
            # 发送post请求
            yield scrapy.FormRequest(url=post_url, formdata=formdata, callback=self.parse)
    scrapy日志等级设置

    对于任何一个程序项目来说,日志都是关键的东西(尤其是运维,小伙伴一定要学着会看日志)。那么作为一个高度封装且极为好用的框架来说scrapy的日志等级该如何设置。

    在scrapy的settings中是存在相关配置的

    LOG_LEVEL = ‘指定日志信息种类’即可。

    LOG_FILE = 'log.txt'则表示将日志信息写入到指定文件中进行存储。

    请求传参:

    我们有时候会需要根据第一个页面的数据来请求第二次,从而得到第一个页面和第二个页面的数据进行分析,那么这时就需要用到请求传参

    import scrapy
    from moviePro.items import MovieproItem
    
    class MovieSpider(scrapy.Spider):
        name = 'movie'
        allowed_domains = ['www.id97.com']
        start_urls = ['http://www.id97.com/']
    
        def parse(self, response):
            div_list = response.xpath('//div[@class="col-xs-1-5 movie-item"]')
    
            for div in div_list:
                item = MovieproItem()
                item['name'] = div.xpath('.//h1/a/text()').extract_first()
                item['score'] = div.xpath('.//h1/em/text()').extract_first()
                #xpath(string(.))表示提取当前节点下所有子节点中的数据值(.)表示当前节点
                item['kind'] = div.xpath('.//div[@class="otherinfo"]').xpath('string(.)').extract_first()
                item['detail_url'] = div.xpath('./div/a/@href').extract_first()
                #请求二级详情页面,解析二级页面中的相应内容,通过meta参数进行Request的数据传递
                yield scrapy.Request(url=item['detail_url'],callback=self.parse_detail,meta={'item':item})
    
        def parse_detail(self,response):
            #通过response获取item
            item = response.meta['item']
            item['actor'] = response.xpath('//div[@class="row"]//table/tr[1]/a/text()').extract_first()
            item['time'] = response.xpath('//div[@class="row"]//table/tr[7]/td[2]/text()').extract_first()
            item['long'] = response.xpath('//div[@class="row"]//table/tr[8]/td[2]/text()').extract_first()
            #提交item到管道
            yield item
    
      items文件:
    
    # -*- coding: utf-8 -*-
    
    # Define here the models for your scraped items
    #
    # See documentation in:
    # https://doc.scrapy.org/en/latest/topics/items.html
    
    import scrapy
    
    
    class MovieproItem(scrapy.Item):
        # define the fields for your item here like:
        name = scrapy.Field()
        score = scrapy.Field()
        time = scrapy.Field()
        long = scrapy.Field()
        actor = scrapy.Field()
        kind = scrapy.Field()
        detail_url = scrapy.Field()
    
        管道文件:
    
    # -*- coding: utf-8 -*-
    
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
    
    import json
    class MovieproPipeline(object):
        def __init__(self):
            self.fp = open('data.txt','w')
        def process_item(self, item, spider):
            dic = dict(item)
            print(dic)
            json.dump(dic,self.fp,ensure_ascii=False)
            return item
        def close_spider(self,spider):
            self.fp.close()
    提高scrapy效率

    在某方面来说scarpy为了完美运行是对我们进行了一些限制的,所以当我们认为手动的打开了这些限制,我们就会获得更高的执行效率

    增加并发:
    默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。

    降低日志级别:
    在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’

    禁止cookie:
    如果不是真的需要cookie,则在scrapy爬取数据时可以进制cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False

    禁止重试:
    对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False

    减少下载超时:
    如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s

     

  • 相关阅读:
    数据库练习
    数据库的设计范式知识
    asserts文件存到外部SD卡里
    用户的注册信息存储到文件里,登录成功后读出并显示出来
    java快速排序引起的StackOverflowError异常
    并发入库面临重复数据的问题
    《旅行青蛙》安卓版本修改钱和奖券
    ddmlib问题总结——同步获取设备信息
    Java ArrayList中对象的排序 (Comparable VS Comparator)
    Authentication(Spring Security 认证笔记)
  • 原文地址:https://www.cnblogs.com/Jicc-J/p/13790646.html
Copyright © 2011-2022 走看看