Scrapy通过Itemloader获取数据
Scrapy中Item是保存结构数据的地方,Scrapy可以将解析结果以字典形式返回并通过Pipelines中的函数对返回的item进行处理,有点类似Django中的models,却简单的多。
创建Item类:
定义Item非常简单,只需要继承 scrapy.Item
类,并将所有字段都定义为scrapy.Field
类型即可
items.py
import scrapy class JobboleArticlesItem(scrapy.Item): art_url = scrapy.Field() artURL_id = scrapy.Field() front_url = scrapy.Field() front_img_path = scrapy.Field() title = scrapy.Field() up_count = scrapy.Field() collect_count = scrapy.Field() comment_count = scrapy.Field() create_date = scrapy.Field() category = scrapy.Field() tags = scrapy.Field() content = scrapy.Field()
jobbole.py 通过[ xpath/css ]解析网页,提取文章相关内容
def parse_detail_deprecated(self,response): ''' 解析文章具体内容,弃用 ''' # 文章封面图url front_url = response.meta.get('front_img_url','') # 标题 # title = response.xpath('//div[@class="entry-header"]/h1/text()').extract_first() title = response.css('.entry-header h1::text').extract_first() print('title==',title) #点赞数 # up_count = response.xpath('//div[@class="post-adds"]/span[1]/h10/text()').extract_first() up_count = response.css('.post-adds>:first-child h10::text').extract_first() #收藏数 # collect_count = response.xpath('//div[@class="post-adds"]/span[2]/text()').extract_first() collect_count = response.css('.post-adds span:nth-child(2)').extract_first() collect_match = re.match('.*?(\d+).*',collect_count) if collect_match: collect_count = collect_match.group(1) else: collect_count = '0' #评论数 # comment_count = response.xpath('//a[@href="#article-comment"]/span/text()').extract_first() comment_count = response.css('a[href="#article-comment"] span::text').extract_first() comment_match = re.match('.*?(\d+).*',comment_count) if comment_match: comment_count = comment_match.group(1) else: comment_count = '0' # 创建时间 # create_date = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/text()').extract_first() create_date = response.css('.entry-meta-hide-on-mobile::text').extract_first() create_date = create_date.replace('·','').strip() # 文章分类 # category = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a[1]/text()').extract_first() category = response.css('.entry-meta-hide-on-mobile a:nth-child(1)::text').extract_first() # tag分类.list # tags = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a[contains(@href,"/tag/")]/text()').extract() tags = response.css('.entry-meta-hide-on-mobile a[href*="/tag/"]::text').extract() tags = ','.join(tags) # 文章详细 # content = response.xpath('//div[@class="entry"]').extract_first() content = response.css('.entry').extract_first() # 保存文章Item artcileSpider_item = JobboleArticlesItem() artcileSpider_item['artURL_id'] = get_md5(response.url) artcileSpider_item['art_url']= response.url artcileSpider_item['front_url']= [front_url] artcileSpider_item['title']= title artcileSpider_item['up_count']= up_count artcileSpider_item['collect_count']= collect_count artcileSpider_item['comment_count']= comment_count artcileSpider_item['create_date']= create_date artcileSpider_item['category']= category artcileSpider_item['tags']= tags artcileSpider_item['content']= content yield artcileSpider_item
生成的 Item 类通过 yield 返回时,Scrapy 会根据 settings 文件中的配置来将item传输到对应的 pipeline 类中->保存item数据传送门
通过之前的学习,已经知道网页的基本解析流程就是先通过 css/xpath 方法进行解析,然后再把值封装到 Item 中,如果有特殊需要的话还要对解析到的数据进行转换处理,这样当解析代码或者数据转换要求过多的时候,会导致代码量变得极为庞大,从而降低了可维护性。同时在 sipider 中编写过多的数据处理代码某种程度上也违背了单一职责的代码设计原则。我们需要使用一种更加简洁的方式来获取与处理网页数据,ItemLoader 就是用来完成这件事情的。
ItemLoader 类位于 scrapy.loader ,它可以接收一个 Item 实例来指定要加载的 Item, 然后指定 response 或者 selector 来确定要解析的内容,最后提供了 add_css()、 add_xpath() 方法来对通过 css 、 xpath 解析赋值,还有 add_value() 方法来单独进行赋值。
jobole.py 通过 ItemLoader 解析文章
from scrapy.loader import ItemLoader def parse_detail(self,response): ''' 解析文章 ''' item_loader = ItemLoader(item=JobboleArticlesItem(), response=response) item_loader.add_css('title', '.entry-header h1::text') #1 item_loader.add_css('up_count', '.post-adds>:first-child h10::text') #2 item_loader.add_css('collect_count', '.post-adds span:nth-child(2)') #3 item_loader.add_css('comment_count', 'a[href="#article-comment"] span::text') #4 item_loader.add_css('tags', '.entry-meta-hide-on-mobile a[href*="/tag/"]::text') item_loader.add_css('content', '.entry') item_loader.add_css('category', '.entry-meta-hide-on-mobile a:nth-child(1)::text') item_loader.add_css('create_date', '.entry-meta-hide-on-mobile::text') item_loader.add_value('artURL_id', get_md5(response.url)) item_loader.add_value('art_url', response.url) item_loader.add_value('front_url',[response.meta.get('front_url','')]) yield item_loader.load_item() # 5
输入(input_processor)/输出处理器(output_processor):
每个Item Loader对每个Field
都有一个输入处理器和一个输出处理器。输入处理器在数据被接受到时执行,当数据收集完后调用ItemLoader.load_item()
时再执行输出处理器,返回最终结果。
执行流程:
1、通过css选择器解析的数据,传输到 title
字段的输入处理器中,在输入处理器处理完后生成结果放在Item Loader里面(这时候没有赋值给item)
2~4、#2 #3 #4 同上
5、将上边的数据,传输到各个字段的输出处理器后,将最终的结果赋值给各自对应的
字段(title、up_count...)
items.py 通过输入/输出处理器 修改Item的值:
import scrapy from scrapy.loader.processors import MapCompose,TakeFirst,Join from scrapy.loader import ItemLoader import re,json class ArticleItemLoader(ItemLoader): '''自定义输入/输出''' # 数据列表中的list的第一个元素 default_output_processor = TakeFirst() front_url_in = MapCompose(return_value)
front_url_out = MapCompose(func...)
def get_Number(value): '''点赞、收藏、评论(数)''' value_match = re.match('.*?(\d+).*', value) if value_match: value = int(value_match.group(1)) else: value = 0 return value def date_conver(value): value = value.replace('·', '').strip() return value def return_value(value): return value def content_dumps(value): content = json.dumps(value) return content class JobboleArticlesItem(scrapy.Item): art_url = scrapy.Field() artURL_id = scrapy.Field() front_url = scrapy.Field(output_processor=MapCompose(return_value)) front_img_path = scrapy.Field() title = scrapy.Field() up_count = scrapy.Field(input_processor= MapCompose(get_Number)) collect_count = scrapy.Field(input_processor= MapCompose(get_Number)) comment_count = scrapy.Field(input_processor= MapCompose(get_Number)) create_date = scrapy.Field(input_processor = MapCompose(date_conver)) category = scrapy.Field() tags = scrapy.Field(output_processor = Join(',')) content = scrapy.Field()
注意:
1、继承ItemLoader的ArticleItemLoader为自定义的ItemLoader类,并且可以通过 字段_in/字段_out 来定义相关字段的输入/输出,其中default_output_processor 等价于默认的输出函数,因为所有的字段在赋值之前的时候都会被加入到一个list中赋值,而TaskFirst()是scrapy提供的取list[0],但是字段中的front_url为图片下载链接(Pipelines中调用),数据结构为:[img_url,img_url....],所以就需要在字段中定义output_processor来替代自定义ItemLoader中的默认default_output_processor。
2、优先级:
a. 在Item Loader中定义的field_in和field_out(front_url_in/front_url_out)
b. Filed元数据(input_processor和output_processor)
c. Item Loader中的默认(default_input_processor/default_out_processor)