一、Scrapy的作用
Scrapy应该算是Python宇宙中最常用的爬虫框架了,他是一个较完善的爬虫框架,同时也是一个比较难学的框架。Scrapy多应用于中型网站内容爬取。
Scrapy的优点:
- 提供内置的HTTP缓存,加速本地开发
- 自动节流调整机制,遵守 robots.txt 的设置
- 自定义爬取深度
- 执行HTTP基本认证,不需要明确保存状态
- 自动填写表单
- 自动设置请求中的引用头
- 支持通过3xx响应重定向,也可以通过HTML元刷新
- 避免被网站使用的 < noscript > meta 重新向困住,以检查没有JS支持的页面
- 默认使用CSS选择器或XPath编写解析器
- 可以用过splash或任何其他技术呈现JavaScript页面
- 有强大的社区支持
- 提供通用spider抓取常见格式
二、Scrapy框架认识
- Scrapy Engine(引擎):负责控制数据流。
- Scheduler(调度器):接收爬取请求(Request)。
- Downloader(下载器):获取页面数据,返回response。
- Spider(蜘蛛):分析response,提取信息,返回Item类。
- Item Pipeline(数据管道):处理Item类,数据信息清洗。
- Downloader middlewares(下载器中间件):下载器和引擎中间的特定钩子,处理下载器传送给引擎的response,拓展scrapy功能。
- Spider middlewares(Spider中间件):spider和引擎中间特定的钩子,处理输入spider的response和spider输出的item。使得scrapy的功能得到拓展。
三、Scrapy基本认识
1.文件目录结构
|——(工程名文件)
| |——— _init_.py '''包定义 '''
| |——— items.py '''模型定义 '''
| |——— pipelines.py '''管道定义 '''
| |——— settings.py '''配置文件 '''
| |——— spiders '''蜘蛛(spider)文件夹 '''
| |——— _init_.py '''控制文件'''
|——scrapy.cfg '''运行配置文件 '''
1.1、创建项目
scrapy startproject + ProjectName
运行该命令后即在当前目录下创建一个Scarpy工程,同时建立工程文件,文件结构如上所诉。这也是官方所推荐的scrapy最小运行框架。
1.2、创建spider
scrapy genspider + SpiderName + Start_Url
Spider 是 Scrapy 中很是重要也很是常见的一个功能,当我们创建一个scrapy项目是,一般都会先编写spider的逻辑。spider的作用是分析有scrapy引擎返回的爬网内容(即:Response),并决定是否继续生成新的请求(即:Request),最终返回Item。
1.3、启动爬网
scrapy crawl + SpiderName
2.数据模型Item
Item可以理解为一种数据容器,作为spider和pipeline的数据载体。spider对Response进行分析,提取具体的数据结构,并生成对应的item,然后有scrapy引擎传递给对应的pipeline进行处理。spider只有产生Item,才能进入下一环节,所以使用scrapy必须要先定义Item。
# -*- coding: utf-8 -*-# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.htmlimport scrapyclass ChinanewsSrawlerItem(scrapy.Item):# define the fields for your item here like:# name = scrapy.Field()''''我们尝试定义一个简单的item'''title = scrapy.Field()price = scrapy.Field()stock = scrapy.Field()date = scrapy.Field()data = scrapy.Field()#Field对象指明了每个字段的元数据(metadata),Field对象接受的值没有任何限制
3.蜘蛛——spider
spider类定义了如何爬取某些网站,包括爬取的动作以及如何从网页的内容中提取结构化数据(Item)
# -*- coding: utf-8 -*-
import scrapy
from bs4 import BeautifulSoup
from ..items import ChinanewsSrawlerItemclass ChinanewsSpider(scrapy.Spider):name = 'chinanews'allowed_domains = ['https://?????.com/']start_urls = ['https://www.?????.com/']def parse(self, response):rss = BeautifulSoup(response.body,'html.parser')for item in rss.findAll('item'):fee_item = ChinanewsSrawlerItem()fee_item['title'] = item.titlefee_item['price'] = item.pricefee_item['stock'] = item.stockfee_item['date'] = item.datefee_item['data'] = item.datayield fee_item''' yield关键字的作用,有点类似return,都是用于结果的返回。与return不同的是,yield返回的是一个迭代器。yield不会像return直接将当前执行的代码中断并返回,而是将当前可被返回的对象生成一个迭代器,\然后继续执行下一行代码'''
蜘蛛要从spider 基类中继承,当然也可以从其他 spider 的其他类继承。scrapy工具所创建的蜘蛛类有三个属性。
- name : 蜘蛛的名称,用于识别。当一个项目中存在多个蜘蛛(spider)时,这个名称标识就尤为重要。
- allowed_domains :只爬取该域内的内容,自动过滤链接到其他域的内容。
- start_urls : 当蜘蛛被启动时,第一批请求的url。也就是从这个网址开始爬取。
'''
以下用两个parse函数实现“间接”爬取,用于对比上面的“直接”爬取
'''
# -*- coding: utf-8 -*-
import scrapy
from bs4 import BeautifulSoup
from ..items import ChinanewsSrawlerItemclass ChinanewsSpider(scrapy.Spider):name = 'chinanews'allowed_domains = ['https://?????.com/']start_urls = ['https://www.?????.com/']def parse(self, response):rss = BeautifulSoup(response.body,'html.parser')rss_links = set([item['data'] for i in rss.findAll('a')])for link in rss_links:yield scrapy.Request(url = link , callback = self.parse_feed)def parse_feed(self,response):rss = BeautifulSoup(response.body,'html.parser')for item in rss.findAll('item'):fee_item = ChinanewsSrawlerItem()fee_item['title'] = item.titlefee_item['price'] = item.pricefee_item['stock'] = item.stockfee_item['date'] = item.datefee_item['data'] = item.datayield fee_item
parse是由spider基类中定义的一个空函数,函数体内只有一个pass关键字。我们可以将spider理解为一个抽象类,而parse则是所有子类中必须实现的“抽象方法”,因而每个spider中必须有一个能返回item的parse函数,scrapy的调度器会找到parse函数并调用它。
上诉parse函数是并没有返回fee_item的迭代器,而是返回一个request的迭代器,在request的构造函数中将parse_feed函数引用作为参数输入。当request对象被下载器发送到目标url并返回之后,就会自动调用parse_feed函数,并将返回的response作为参数传入,这样就形成了二次循环。
最后,当parse函数返回的是一个Item的枚举时,就标志着这个蜘蛛已经完成他需要处理的事情,scrapy引擎将引导工作流进行下一环节,即 “管道” 处理。
4.管道——Pipeline
“管道,”只接收Item并拖过Item执行一些行为,并决定此Item是否继续通过管道,他只负责输入和输出,并且输入输出的对象都是Item
# -*- coding: utf-8 -*-# 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.htmlclass ChinanewsSrawlerPipeline(object):def process_item(self, item, spider):return item
每个管道都需要调用 process_item 方法,而且这个方法必须返回一个Item对象或者是抛出一个 DropItem 异常。每个管道每次只能通过一个Item,意味着每次只能处理一个Item对象。
参数:
item :当前处理的Item对象
spider: 产生当前Item对象的spider
4.1、过滤性管道
面对那些没用的、重复的数据,我们可以通过建立一个过滤性管道来判断数据的有效性。在 process_item 处理函数中发起 scarapy.exceptions.DropItem 的异常就能完成丢弃动作,从而过滤掉垃圾数据。在此过程中并不用担心 process_item中发起的异常会使得工程中止。因为管道的每次处理是针对单个Item对象进行的。
'''
建立过滤管道,过滤'tital'中包含'游戏'信息的数据
'''
import scrapy
class BlockPipeline:def process_item(self, item, spider):key = '游戏'if key in item['tital']:raise scrapy.exceptions.DropItemreturn item'''
建立过滤管道,去重
'''
class DoplicatesPipeline:def _int_(self):self.du = set()def process_item(self, item, spider):if self.du in item['tital']:self.du.add(item['tital'])raise scrapy.exceptions.DropItemreturn item
4.2、加工性管道
建立管道,对数据进行加工。
有如,运算求和,加权,格式化等。
4.3、储存性管道
import jsonclass jsonfeedPipeline(object):def _int_(self):self.json_file = open('feed.json','wt')self.json_file.write('[\n')def process_item(self, item, spider):line = json.dumps(dict(item))+',\n'self.json_file.write(line)return itemdef close_spider(self,spider):self.json_file.write('\n]')self.json_file.close()
如果要将所有的Item保存到一个json文件中,无需手工实现json的储存。
- 全局性指定:在配置过程中,配置FEED_URI 和FEED_FORMAT 两个配置项,输出文件会保存到指定的位置(即:FFED_URI指定的位置)
- 动态指定:在scrapy 命令中加入 -o +文件名称 ,即可在“根目录”下生成输出文件。
4.4、管道引用
管道的引用需要在settings.py文件中进行指定。即指定spider之后使用哪个管道,和使用顺序。
如果有两个不同的item,需要在管道中进行识别,防止出错。
在setings.py中找到下面的配置内容,该部分内容即指定管道的使用。
ITEM_PIPELINES 内要以全路径引用,可同时引用多个管道。
管道 “:”后面的数字表示优先级(即:执行顺序),数字越小优先级越高,数字的取值范围0 ~ 1000
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
# 'chinanews_srawler.pipelines.ChinanewsSrawlerPipeline': 300,
#}'''
配置
'''
ITEM_PIPELINES = {'chinanews_srawler.pipelines.BlockPipeline': 300,'chinanews_srawler.pipelines.jsonfeedPipeline': 301,
}