一.什么是Scrapy?
Scrapy是一个为 了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。
- 组件的作用:
● 引擎(Scrapy)
作用:判读数据对象,触发下一次步骤。用来处理整个系统的数据流处理,触发事务(框架核心)
● 调度器(Scheduler)
作用:只会接收spider对象生成的请求对象。用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回.可以想像成-个URL (抓取网页的网址或者说是链接)的优先队列, 由它来决定下一-个要抓取的网址是什么,同时去除重复的网址
● 下载器( Downloader)
作用:从互联网中请求数据。用于下载网页内容,井将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
● 爬虫(Spiders)
作用:1.批量产生URL。2.用于数据解析。爬虫是主要干活的,用于从特定的网页中提取自2需要的信息,即所谓的实体(tem)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
● 项目管道(Pipeline)
作用:持久化存储。负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。管道就是将一个程序的输出重定向为另一个程序的输入。
- 请求传参实现的深度爬取
- 深度爬取:爬取的数据没有在同一张页面中(首页数据+详情页数据)
- 在scrapy中如果没有请求传参我们是无法持久化存储数据
- 实现方式:
scrapy . Request(url, callback , meta)
meta是一个字典, 可以将meta传 递给callback
callback取出meta:
response. meta
- 中间件:
作用:批量拦截请求和响应
爬虫中间件
下载中间件(推荐)
- 拦截请求:
篡改请求url
伪装请求头信息
UA
Cookie
设置请求代理(重点)
- 拦截响应
篡改响应数据
- 代理操作必须使用中间件才可以实现
process_ exception:
request .meta['proxy'] = 'http://ip:port'
二.安装
Linux:
pip3 install scrapy
Windows:
a. pip3 install wheel
b.下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
c.进入下载目录,执行pip3 install Twisted-20.3.0-cp37-cp37m-win_amd64.whl
注意:必须保证安装成功。安装失败,可以换另一个版本的twisted文件再次进行安装
d. pip3 install pywin32
e. pip3 install scrapy
三.基本使用
- 创建一个工程
scrapy startproject proName
- 工程目录下的两个文件表示的含义:
- spiders包:爬虫文件夹
必须在爬虫文件夹中创建一个爬虫源文件
- settings.py:配置文件
- cd proName
- 创建一个爬虫文件(爬虫文件要创建在spiders包中)
- scrapy genspider spiderName www xxx.com - 该指令可以创建多个爬虫文件,但是爬虫文件名称不可以重复 - 编写爬虫文件
- 执行工程
- scrapy crawl spiderName
import scrapy
class FirstSpider(scrapy.Spider):
# name表示爬忠文件的名称,爬虫文件的名称表示的是爬虫文件的唯一标识
name = 'first'
#允许的域名:用来做请求限定。一旦该域名定义好之后。则start url只可以发起该域名下的url的get请求
allowed_domains = ['www.xxx.com']
#起始的url列表:将想要爬取的urlm存放在该列表中,这个列表就可以帮我们将每一个ur1进行get请求的发送
start_urls = ['http://www.xxx.com/']
#用作于数据解析。response参数就是请求回来的响应对象
def parse(self, response):
print(response)
pass
四.爬虫文件的编写
定义好了一个类,该类的父类是Spider, Spider是sC rapy所有类的父类
-类中定义好了三个属性和一个方法
- name :
爬虫文件的名称
- start_ urls:起始url列表
作用:可以对列表中的ur1进行get请求的发送
- allow_ demains :允许的域名。
- parse(self, response ) :
将起始ur1列表中的url请求成功后,response就是获取的响应对象,在该方法中负责实现数据解析
scrapy工程默认是遵从rotbos协议的,需要在配置文件中进行操作:
不遵从robots协议 False
指定日志等级
- LOG_LEVEL = ' ERROR '
# 数据解析
def parse(self, response):
# respanse. expathO
# tree.expathl
# 上述两个xpath不是同一个方法,但是使用上基本一致。只有细微的差异
li_list = response.xpath(' //* [@id="list"]/ul/li')
for li in li_list:
# 返回的不是宇符串,而是一个Selector对象.且提取的字符串数据被存储在了该对象中
content = li.xpath('./div[1]/text()')[0].extract()
# title = li. xpath('./div [2]/span//text()' )#返回的是列表,列表中有多个元素
# title = join(title) # 将列表转成字符串
print('内容:', content)
# print('标题:', title)
break
五. 数据解析
- response. xpath()
- 注意:提取标签内容时,返回的不是字符串,而是Selector对象, 字符串是存储在该对象中的。需要调用extract( )或者extract_ first( )将Selector对象中的字符串取出。
六. 持久化存储
- 基于终端指令:
- 只可以将parse方法的返回值存储写入到指定后缀的文本文件中。通过指定方式执行工程:
- scrapy crawl duanzi -o data_duanzi. csv
- 基于管道:
- 实现流程:
- 1.在爬虫文件中解析数据
- 2.在Item类中定义相关的属性(解析的数据有几个字段就定义几个属性)
- 3.将在爬虫文件中解析的数据存储封装到Item对象中
- 4.将存储了解析数据的Item对象提交给管道
- 5.在管道文件中接受Item对象,且对其进行任意形式的持久化存储操作
- 6.在配置文件中开启管道
并基于管道的持久化存储
def parse(seLf, response):
li_list = response.xpath('//* [@id="list"]/ul/li')
for li in li_ list:
content = li. xpath( './div[1]/text()').extract_ first()
title = li. xpath( './div [2]/span//text()').extract()#返回的是列表列表中有多个
title = ''.join(title)#将列表转成宇符串
#将解析到的数据存储到Item对象
item = DuanziproItem( )
#注意: itemL'title' 1访问item对象中的title属性(只可以这样访问属性)
item['title'] = title
item ['content'] = content
#将item对象提交给管道(pipelines.py)
yield item
- 如何实现数据的备份
- 指的是将爬取到的一组数据存储到多个不同的载体(文件, mysql, redis) 中。
- 持久化存储的操作必须要写在管道文件中
- 一个管道类对应一种形式的持久化存储
- 如果想将数据存储到多个载体中则必须要有多个管道类
- 问题:让两个管道类都接受到item且对其进行持久化存储,爬虫文件提交的item可同时提交给两个管道类?
- 爬虫文件提交的item只可以提交个优先级最高的那一个管道类。- 如何可以让优先级低的管道类也可以获取接受到item呢?
- 可以让优先级高的管道类在process_ item中通过return item的形式将item传递给下一个即将被执行的管道类。
管道文件的编写
#该管道类是将数据在储到了本地文件
class FirstbloodPipeline:
fp = None
#重写父类一个方法:只会在爬虫开始后被执行一次
def open spider(self,spider):
print('i am open_ spider()' )
fp = open('./duanzi.txt', 'w', encoding='utf-8')
#这个方法是用来接收爬虫文件提交过来的item对象(一 次只能接受个item对象)共参数item:就是接受到item对象
def process_ item(self, item, spider):
#将接收到的item对象写入文件
title = item['title'], #将item对象中存储的值取出
content = item['content']
#open('text.txt' )#写在此处不合适。因为process_item方法会被调用多次
return item
管道文件储存到MySQL中
import pymysql
#将数据存储到mysa1中一份
class MysqlPileline(object):
conn = None
cursor = None
def open_spider(self, spider):
self.conn = pymysql.Connect(host='127.0.0.1',port=3306, user='root',password='123456',db='spider',charset='utf8')
print(self.conn)
def process_item(self, item, spider):
title = item['title']
content = item['content']
sql = 'insert into duanzi values ("%s", "%s")'%(title,content)
#使用游标对象执行sql语句
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()
七.手动请求发送实现的全站数据爬取
- 如何通过代码手动对指定的url进行请求发送
- yield scrapy.Request(url, callback) :get请求
- 如何手动发起post请求
- yield scrapy.FormRequest(url, formdata, callback) post请求
formdate:字典,请求参数。注意:在scrapy中一般不发送post请求 为什么start_ urls列表中的url会被自动进行get请求的发送?
- 因为列表中的ur1其实是被start_ requests这个父类方法实现的get请求发送
def start_ requests(self):
for u in self.start_urls:
yield scrapy . Request (url=u, callback=self. parse)
如何将start_ _urls中的urL默认进行post请求的发送?
- 重写start_ requests方法即可
def start_ requests(self):
for u in self.start_urls:
yield scrapy.FormRequest(url=u, callback=self.parse,fromdata=data)
#手动请求发送的操作
if self.page <= 120 :进结束递归的条件
#手动请求发送的操作
new_url = format(self.url, self.page)
self.page += 30
#callback参数:请求成功会调用指定的回调函数进行数据解析
yield scrapy.Request(urL=new_url, callback=self.parse)扶手动对指定的url进行get请求