zoukankan      html  css  js  c++  java
  • scrapy抓取拉勾网职位信息(五)——代码优化

    上一篇我们已经让代码跑起来,各个字段也能在控制台输出,但是以item类字典的形式写的代码过于冗长,且有些字段出现的结果不统一,比如发布日期。

    而且后续要把数据存到数据库,目前的字段基本都是string类型,会导致占用空间较多,查询时速度会较慢,所以本篇先对目前已写好的代码进行适当优化。


    本篇目的:使用item loader以及processor对代码进行优化,对字段数据进行清洗

    1、修改一下items.py文件的字段

    我们对工资和工作经验字段进行分割让其更适合数据库存储:

    import scrapy
    
    
    class LagouItem(scrapy.Item):
        url = scrapy.Field()
        name = scrapy.Field()
        ssalary = scrapy.Field()  #最低工资
        esalary = scrapy.Field()  #最高工资
        location = scrapy.Field()
        syear = scrapy.Field()  #最低工作经验
        eyear = scrapy.Field()  #最高工作经验
        edu_background = scrapy.Field()
        type = scrapy.Field()
        tags = scrapy.Field()
        release_time = scrapy.Field()
        advantage = scrapy.Field()
        job_desc = scrapy.Field()
        work_addr = scrapy.Field()
        company = scrapy.Field()

    小贴士:我们要使用item loaders,需要对其有一个基本的了解。item loaders可以看成是对item对象的一个封装,也就是说它同样可以对字段进行操作,另外对于每一个字段,都有一个input processor和一个output processor,也就是输入处理器,和输出处理器,一般情况下,output processor即输出处理器使用的更多,它可以对我们使用css或者xpath选择出的结果进行进一步的处理。


    2、编写lagou_c.py文件

    导入LagouCLoader以及修改parse_item()函数

     1   from lagou.loaders import LagouCLoader  #这个loaders.py文件是新建立的文件,它包括一个LagouCLoader类使用处理器对item进行处理
     2 
     3 
     4   def parse_item(self,response):
     5         loader = LagouCLoader(item=LagouItem(),response=response)
     6         loader.add_value('url',response.url)
     7         loader.add_css('name','.name::text')
     8         loader.add_css('ssalary','.salary::text')
     9         loader.add_css('esalary','.salary::text')
    10         loader.add_xpath('location','//*[@class="job_request"]//span[2]/text()')
    11         loader.add_xpath('syear','//*[@class="job_request"]//span[3]/text()')
    12         loader.add_xpath('eyear','//*[@class="job_request"]//span[3]/text()')
    13         loader.add_xpath('edu_background','//*[@class="job_request"]//span[4]/text()')
    14         loader.add_xpath('type','//*[@class="job_request"]//span[5]/text()')
    15         loader.add_css('tags','.labels::text')
    16         loader.add_css('release_time','.publish_time::text')
    17         loader.add_css('advantage','.job-advantage p::text')
    18         loader.add_css('job_desc','.job_bt p::text')
    19         loader.add_xpath('work_addr','//div[@class="work_addr"]/a[1]/text()')  #这个work_addr分成了三次进行填充,最终需要把得到的数据进行合并
    20         loader.add_xpath('work_addr','//div[@class="work_addr"]/a[2]/text()')
    21         addr_value = response.css('.work_addr::text').extract()[-2].strip()
    22         loader.add_value('work_addr',addr_value)
    23         loader.add_css('company','.job_company img::attr(alt)')
    24         yield loader.load_item()

    解释:可以看到我们先生成了一个LagouCLoader实例对象,这个类使用了两个参数,一个是LagouItem对象,还有一个是response对象,而下方调用了一系列的方法对字段进行填充,add_xpath,add_css,add_value就是字面上的意思,对response使用xpath,css或者直接用一个value值进行填充。这和我们之前写的parse_item()比较类似,不同的是之前的每一个字段我们都直接进行了处理。最后使用yield loader.load_item()方法将字段分配出去。


    强行解释一波:

    上面的解释可能大家看的还是有点懵,我就用现实生活中的例子来说吧。现在我要做3个菜,青椒肉丝,西红柿炒鸡蛋,酸辣土豆丝。

    items (空字段):可以看成这三个菜,但是最开始是没有的,需要加工出来

    input processor:可以看成是前置处理,洗菜,切菜,准备配料等。

    add_xpath,add_css,add_value:开始炒菜咯,放点油,放点盐,多加点辣椒。。。

    output processor: 可以看成是后置处理,菜做好了, 撒点葱,香油

    items(成品):现在可以上菜了,yield laoder.load_item()


    3、编写loaders.py文件

    细心的童鞋可能发现了,我们在lagou_c.py文件中只是把字段提取了出来,但是还没有做处理,所以我们在items.py文件的同级目录下建立一个loaders.py文件

    • 首先导入loaders以及几个自带的processor处理器,Join,Compose,TakeFirst,MapCompose
     from scrapy.loader.processors import Join, Compose, TakeFirst, MapCompose
     from scrapy.loader import ItemLoader

    Join:和python原生库中的Join用法类似,用来做拼接

    Compose:参数传入多个函数对象,这几个函数会依次调用

    MapCompose:和Compose类似,不同的是它可以对一个列表中的每个值分别调用函数

    TakeFirst:和extract_first()类似,不过,TakeFirst是产生第一个非空的值

    • 建立一个LagouCLoader类,它继承自ItemLoader,字段的处理在这个类下面进行
    class LagouCLoader(ItemLoader):
        default_output_processor = TakeFirst()  #设置默认的输出处理器为TakeFirst,也就是说,所有的字段信息提取出来后,会自动取第一个值,类似extract_first()

    但是我们目前还有几个字段不能这样处理

    location、edu_backgroud:提取出的字段需要去掉/和多余的空格

    ssalary、esalary:最低工资和最高工资在同一个字段里,需要分别拿出来

    syear、eyear:最低工作年限和最高工作年限需要进一步处理,提取出的字段有类似几种情况,a:如3-5年,b.如三年以上 c.如经验不限

    release_time:发布时间同样分类似几种情况,a.如 2天前 b.如13:20c.如2018-11-11

    tags、advantage、job_desc:需要使用Join方法进行拼接


    • 我这里直接给出代码
    from scrapy.loader.processors import Join, Compose, TakeFirst, MapCompose
    from scrapy.loader import ItemLoader
    import re
    import datetime
    
    
    def change_value(value):  
        return value.replace('/', '').strip()  #把字段的’/‘更换为空,然后把首位多余的空格去掉
    
    
    def time_trans(value):
        if '天前' in value:   #针对字段中有几天前的情况
            num = re.search('(d+).*', value).group(1)
            time_pub = datetime.datetime.now() - datetime.timedelta(days=int(num))  #获取num天前的日期
            return time_pub.strftime('%Y-%m-%d')
        if '-' in value: #针对字段中有-的情况,也就是有具体发布日期的情况
            time_pub = re.search('(d+-d+-d+).*', value).group(1)
            return time_pub
        else:
            return datetime.datetime.now().strftime('%Y-%m-%d')  #把当前日期转为string格式
    
    
    def exp_trans(value):
        if '-' in value:  #针对有具体工作经验年限要求的
            result = re.findall('(d+).*-(d+).*', value)
        elif '以上' in value:  #针对含多少年以上工作经验的情况
            result = re.search('(d+).*', value)
        else: 
            result = [(0, 0)] #和另外两种情况保持一致格式
        return result  #返回的结果是一个列表(内含元组),元组内第一个是最低工作年限,第二个值是最高工作年限
    
    
    class LagouCLoader(ItemLoader):
        default_output_processor = TakeFirst()  #将所有的字段都进行后处理,只取第一个非空值,但是要注意这个优先级是低于_in,_out的
        location_out = Compose(TakeFirst(), change_value)  #字段名+_out代表的就是对输出后的处理
        edu_background_out = Compose(TakeFirst(), change_value)
        ssalary_out = Compose(TakeFirst(), lambda x: re.search('(d+).*-(d+).*', x).group(1))
        esalary_out = Compose(TakeFirst(), lambda x: re.search('(d+).*-(d+).*', x).group(2))
        syear_out = Compose(TakeFirst(), exp_trans, lambda x: x[0][0])
        eyear_out = Compose(TakeFirst(), exp_trans, lambda x: x[0][1])
        release_time_out = Compose(TakeFirst(), time_trans)
        tags_out = Compose(Join(','))
        advantage_out = Compose(Join(','))
        job_desc_out = Compose(Join('
    '))
        work_addr_out = Compose(Join(''))

    小贴士:

    • default_output_processor = TakeFirst(),设置所有的默认输出处理器都使用TakeFirst(),这样对于所有的字段全都只会取第一个非空值,针对数据本身就很整齐,不需要额外进行清洗的,这种操作收益最大。
    • 如果需要对某个字段使用输入处理器,可以使用字段名+_in = processor()来对该字段进行输入的修饰处理,注意这里的processor是指Compose、TakeFirst等。比如字段名是name,name_in = Compose(lambda x:x.strip())就可以对输入进行处理了,处理的结果就是输入的值前后去除了多余的空格。同理,如果要对某个字段使用输出处理器,那就使用字段名+_out来进行操作。
    • 优先级说明:类似_in,_out这种操作,优先级是高于default_output_processor(或者default_input_processor)优先级的,所以如果对某个字段采取了_in,_out操作,这个操作就会把default_output_processor的操作给覆盖掉。

    这样我们就完成了前期的代码优化,运行下程序看下结果,可以看到符合我们的预期要求。

  • 相关阅读:
    new Date在不同浏览器识别问题
    22. Generate Parentheses dfs填表
    迪杰斯特拉+优先队列实现
    1062 最简分数 (20 分)
    1091 N-自守数 (15 分)
    1054 求平均值 (20 分)
    1045 快速排序 (25 分)
    1086 就不告诉你 (15 分)
    1076 Wifi密码 (15 分)
    1081 检查密码 (15 分)
  • 原文地址:https://www.cnblogs.com/sjfeng1987/p/10033066.html
Copyright © 2011-2022 走看看