scrapy框架
-
什么是框架?
- 其实就是一个具有很强通用性且集成了很多功能的项目模板.
-
如何学习框架:
-
掌握框架的功能,可以熟练使用每一种功能即可.
-
scrapy:
- 集成了异步操作,高性能的数据解析,高性能的持久化存储.......
-
环境的安装:
a. pip3 install wheelb. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl d. pip3 install pywin32 e. pip3 install scrapy 测试:在终端中录入scrapy
-
使用流程:
- 创建一个工程:scrapy startproject ProName(项目工程名称)
- cd ProName
- 创建一个爬虫文件:scrapy genspider spiderName www.xxx.com(指定的url,必须要先CD到项目中,保证爬虫文件在spiders目录中)
- 执行工程:scrapy crawl spiderName
- setting中添加UA伪装,并选择不遵从爬虫Robots协议
#UA伪装
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'
#不遵从爬虫Robots协议
ROBOTSTXT_OBEY = False
#只输出错误信息日志
LOG_LEVEL = 'ERROR'
-
scrapy的数据解析
- 在scrapy中使用xpath解析标签中的文本内容的话,最终获取的是一个Selector的对象,且我们需要的字符串数据全部被封装在了该对象中
- 如果可以确定xpath返回的列表只有一个列表元素则使用extract_first(),否则使用extract()
-
scrapy的持久化存储
- 基于终端指令:
- 可以将parse方法的返回值对应的数据进行本地磁盘文件的持久化存储
- scrapy crawl qiubai -o filePath
- 优点:便捷
- 缺点:局限性较强(数据不可以存储到数据库,数据存储文件的后缀有要求)
- 基于终端指令的持久化存储代码示例
#基于终端指令的持久化存储代码示例,直接写在你的项目文件中即可, #运行指令scrapy crawl qiubai -o filePath # -*- coding: utf-8 -*- import scrapy # from qiubaiPor.items import QiubaiporItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] # 存放在该列表中的url都会被scrapy自动的进行请求发送 start_urls = ['https://www.qiushibaike.com/text/'] #基于终端指令的持久化存储:可以将parse方法的返回值对应的数据进行本地磁盘文件的持久化存储 def parse(self, response): all_data = [] # 数据解析:作者and段子内容 div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: # 在scrapy中使用xpath解析标签中的文本内容的话,最终获取的是一个Selector的对象,且我们需要的字符串数据全部被封装在了该对象中 #如果可以确定xpath返回的列表只有一个列表元素则使用extract_first(),否则使用extract() author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() content = div.xpath('./a/div/span/text()').extract() dic = {"author":author,"content":content} all_data.append(dic) return all_data
- 基于管道:
- 编码流程:
- 1.数据解析
- 2.在item类中进行相关属性的封装
- 3.实例化一个item类型的对象
- 4.将解析的数据存储封装到item类型的对象中
- 5.将item提交给管道
- 6.在配置文件中开启管道
- 2.在item类中进行相关属性的封装
- 1.数据解析
- 编码流程:
- 基于sarapy模块进行的数据爬取存储示例(qiubai项目运行文件内容)
#基于sarapy模块进行的数据爬取存储 # -*- coding: utf-8 -*- import scrapy from qiubaiPro.items import QiubaiproItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] #存放在该列表中的url都会被scrapy自动的进行请求发送 start_urls = ['https://www.qiushibaike.com/text/'] #基于终端指令的持久化存储:可以将parse方法的返回值对应的数据进行本地磁盘文件的持久化存储 # def parse(self, response): # all_data = [] # #数据解析:作者and段子内容 # div_list = response.xpath('//div[@id="content-left"]/div') # for div in div_list: # #在scrapy中使用xpath解析标签中的文本内容的话,最终获取的是一个Selector的对象,且我们需要的字符串数据全部被封装在了该对象中 # #如果可以确定xpath返回的列表只有一个列表元素则使用extract_first(),否则使用extract() # author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() # content = div.xpath('./a/div/span/text()').extract() # # dic = { # 'author':author, # 'content':content # } # all_data.append(dic) # # print(author,content) # return all_data #基于管道实现持久化存储 def parse(self, response): all_data = [] #数据解析:作者and段子内容 div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: #在scrapy中使用xpath解析标签中的文本内容的话,最终获取的是一个Selector的对象,且我们需要的字符串数据全部被封装在了该对象中 #如果可以确定xpath返回的列表只有一个列表元素则使用extract_first(),否则使用extract() author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() if not author: author = '匿名用户' content = div.xpath('./a/div/span/text()').extract() content = ''.join(content) #创建一个item类型的对象(只可以存储一组解析的数据) item = QiubaiproItem() #将解析到的数据存储到item对象中 item['author'] = author item['content'] = content #将item提交给管道类 yield item
- pipelines.py中的内容,主要用于存储
# -*- 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 pymysql from redis import Redis #一个管道类对应一种平台的数据存储 class QiubaiproPipeline(object): fp = None #重写父类的方法:只在开始爬虫的时候被执行一次 def open_spider(self,spider): print('开始爬虫......') self.fp = open('./qiubai.txt','w',encoding='utf-8') #处理item类型的对象 #什么是处理? #将封装在item对象中的数据值提取出来且进行持久化存储 #参数item表示的就是爬虫文件提交过来的item对象 #该方法每接收一个item就会被调用一次 def process_item(self, item, spider): print('this is process_item()') author = item['author'] content = item['content'] self.fp.write(author+':'+content+" ") #返回的item就会传递给下一个即将被执行的管道类 return item def close_spider(self,spider): print('结束爬虫!') self.fp.close() #将数据同时存储到mysql class mysqlPileLine(object): conn = None cursor = None def open_spider(self,spider): self.conn = pymysql.Connect(host='127.0.0.1',port=3306,db='spider',user='root',password='',charset='utf8') print(self.conn) def process_item(self,item,spider): 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): self.cursor.close() self.conn.close() class redisPileLine(object): conn = None def open_spider(self,spider): self.conn = Redis(host='127.0.0.1',port=6379) def process_item(self,item,spider): dic = { 'author':item['author'], 'content':item['content'] } self.conn.lpush('qiubaiData',dic)
- 不要忘记对items.py文件中字段的设置
# -*- 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 QiubaiporItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() author = scrapy.Field() content = scrapy.Field()
- 最后将settings.py文件进行配置
ITEM_PIPELINES = { 'qiubaiPro.pipelines.QiubaiproPipeline': 300, # 'qiubaiPro.pipelines.mysqlPileLine': 301, 'qiubaiPro.pipelines.redisPileLine': 302, #300表示的是优先级,数值越小优先级越高 }
- 注意事项:
- 爬虫文件提交的item只会传递给第一个被执行的管道类
- 在管道类的process_item方法中的return item,是将item传递给下一个即将被执行的管道类
- 什么时候需要定制多个管道类?
- 将数据同时存储到多个平台的时候
- 习惯:每一个process_item中都需要编写return item
- 基于终端指令:
-
进行全站数据的爬取
- 手动请求的发送(get请求)
- yield scrapy.Request(url,callback)
- callback:用于数据解析- 发送post请求:
- 手动进行post请求的发送:
- yield scrapy.FromRequest(url,callback,formdata)
- formdata请求参数 ==>requests.post(data)
- yield scrapy.FromRequest(url,callback,formdata)
- 想要将起始url列表中存储的起始url进行post请求的发送
- 重写父类的该方法:def start_requests(self):
- 手动进行post请求的发送:
def start_requests(self): for url in self.start_urls: data = {} yield scrapy.FormRequest(url,callback=self.parse,formdata=data)
- 基于scrapy的全栈数据爬取(qiubai.py运行文件内容)
# -*- coding: utf-8 -*- import scrapy #爬取多页 from qiubaiByPages.items import QiubaibypagesItem class QiubaiSpider(scrapy.Spider): name = 'qiubai' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.qiushibaike.com/text/'] #定制一个通用的url模板 url = 'https://www.qiushibaike.com/text/page/%d/' pageNum = 1 def parse(self, response): print('正在爬取{}页......'.format(self.pageNum)) all_data = [] # 数据解析:作者and段子内容 div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: # 在scrapy中使用xpath解析标签中的文本内容的话,最终获取的是一个Selector的对象,且我们需要的字符串数据全部被封装在了该对象中 # 如果可以确定xpath返回的列表只有一个列表元素则使用extract_first(),否则使用extract() author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() if not author: author = '匿名用户' content = div.xpath('./a/div/span/text()').extract() content = ''.join(content) # 创建一个item类型的对象(只可以存储一组解析的数据) item = QiubaibypagesItem() # 将解析到的数据存储到item对象中 item['author'] = author item['content'] = content # 将item提交给管道类 yield item #递归解析+手动请求发送 ==> 全站数据爬取 if self.pageNum <= 13: self.pageNum += 1 new_url = format(self.url%self.pageNum) yield scrapy.Request(url=new_url,callback=self.parse)
- piplines.py文件内容
class QiubaibypagesPipeline(object): fp = None # 重写父类的方法:只在开始爬虫的时候被执行一次 def open_spider(self, spider): print('开始爬虫......') self.fp = open('./qiubai.txt', 'w', encoding='utf-8') # 处理item类型的对象 # 什么是处理? # 将封装在item对象中的数据值提取出来且进行持久化存储 # 参数item表示的就是爬虫文件提交过来的item对象 # 该方法每接收一个item就会被调用一次 def process_item(self, item, spider): # print('this is process_item()') author = item['author'] content = item['content'] self.fp.write(author + ':' + content + " ") # 返回的item就会传递给下一个即将被执行的管道类 return item def close_spider(self, spider): print('结束爬虫!') self.fp.close()
-
最后不要忘记settings跟items文件的配置
-
爬取http://www.521609.com/meinvxiaohua/ 图片数据
#运行文件内容 import scrapy from xiaohuaPro.items import XiaohuaproItem class XiaohuaSpider(scrapy.Spider): name = 'xiaohua' # allowed_domains = ['www.xxx.com'] start_urls = ['http://www.521609.com/meinvxiaohua/'] url = 'http://www.521609.com/meinvxiaohua/list12%d.html' page_num = 1 def parse(self, response): li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li') for li in li_list: #创建一个对象 item = XiaohuaproItem() img_src = 'http://www.521609.com'+li.xpath('./a[1]/img/@src').extract_first() title = li.xpath('./a[1]/img/@alt').extract_first() item['title'] = title item['img_src'] = img_src yield item if self.page_num <21: self.page_num += 1 new_url = format(self.url%self.page_num) #使用yield方式进行反复的递归回调,来获取新的内容 yield scrapy.Request(new_url,self.parse)
-
items中定义字段
import scrapy class XiaohuaproItem(scrapy.Item): # define the fields for your item here like: title = scrapy.Field() img_src = scrapy.Field() pass
-
pipelines中接收并进行存储
from scrapy.pipelines.images import ImagesPipeline import scrapy class XiaohuaproPipeline(object): def process_item(self, item, spider): print(item) return item #使用scrapy专门封装好的一个管道类(ImagesPipeline)文件数据下载和持久化存储 class imgPileLine(ImagesPipeline): #进行文件请求 def get_media_requests(self, item, info): yield scrapy.Request(item['img_src']) #指定文件最终持久化存储对应的文件名称 def file_path(self, request, response=None, info=None): img_src = request.url img_name = img_src.split('/')[-1] return img_name def item_completed(self, results, item, info): print(results) return item #可以将item传递给下一个即将被执行的管道类
-
在这些代码操作之前不要忘了对你的工程项目进行settings的配置
#UA伪装 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36' #不遵从爬虫Robots协议 ROBOTSTXT_OBEY = False #只显示报错信息 LOG_LEVEL = 'ERROR' #管道优先级 TEM_PIPELINES = { 'xiaohuaPro.pipelines.XiaohuaproPipeline': 301, 'xiaohuaPro.pipelines.imgPileLine': 300, } #设置文件夹存储路径 IMAGES_STORE = './xiaohuas'
- 发送post请求:
-
scrapy的五大核心组件
-
引擎(Scrapy)
- 用来处理整个系统的数据流处理, 触发事务(框架核心)
-
调度器(Scheduler)
- 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求 的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
-
下载器(Downloader)
- 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
-
爬虫(Spiders)
- 爬虫是主要干活的, 用于从特定的网页中提取自己 需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
-
项目管道(Pipeline)
- 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验 证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
-
-
请求传参:
- 使用场景:
- 爬取的数据没有存在于同一张页面中
- 请求传参传递的是item
- 实现:在进行手动请求发送的时候,可以将一个字典(meta)传递给回调函数
- yield scrapy.Request(url,callback,meta)
- callback取meta字典:
- response.meta
- 使用场景:
-
基于scrapy框架的对电影及详情页内容的爬取案例
- 执行文件movie.py
import scrapy from moviePro.items import MovieproItem class MovieSpider(scrapy.Spider): name = 'movie' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.4567tv.tv/index.php/vod/show/id/5.html'] #封装一个通用的url模板 url = 'https://www.4567tv.tv/index.php/vod/show/id/5/page/%d.html' page_num = 1 def parse(self, response): print('正在爬取第{}页......'.format(self.page_num)) li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: title = li.xpath('./div/a/@title').extract_first() detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first() item = MovieproItem() item['title'] = title #对详情页发起get请求 #meta是一個字典,将meta传递给回调函数 yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item}) if self.page_num <= 33: self.page_num += 1 new_url = format(self.url%self.page_num) yield scrapy.Request(new_url,callback=self.parse) #用来解析详情页中的电影简介 def parse_detail(self,response): #提取meta item = response.meta['item'] desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['desc'] = desc yield item
- items.py文件内容
import scrapy class MovieproItem(scrapy.Item): # define the fields for your item here like: #电影名称 title = scrapy.Field() #电影详情介绍 desc = scrapy.Field()
- pipelines.py文件内容
class MovieproPipeline(object): def process_item(self, item, spider): print(item) return item