scrapy
简介
scrapy是一个专门用于异步爬虫的框架,框架可以理解为是一个被集成了很多功能且具有很强通用性的一个项目模板。
安装
1 终端输入
pip install scrapy
2 anaconda - environments - base(root) -search packages搜索scrapy下载,回到pycharm中import后直接下载,或是在preference-project interpreter 按+ 搜索后 install package
下载显示成功后在终端输入scrapy,没报错就说明下载成功
基础使用指令
- 创建
scrapy startproject ProName
scrapy startproject onePick
- 进入工程
cd ProName
cd onePick
- 创建爬虫源文件,网址可以创建后修改
- 在spider中创建爬虫文件,一般情况下只创建1个,name对应的属性值就是该爬虫文件,它是当前源文件的唯一标识
- 如果在spider中创建多个爬虫文件,name对应的属性值不可相同
scrapy genspider spiderName www.xxx.com
scrapy genspider one www.xxx.com
- 执行工程
- 在pycharm中只能使用指令执行,无法通过run 执行
- 不能同时执行多个爬虫文件,只能是一条指令执行一个
- 直接进行执行工程后,默认会输出工程的日志信息
scrapy crawl spiderName
scrapy crawl one
- 在创建爬虫源文件后,可先到
settings.py
中进行修改:- 写入
LOG_LEVEL = 'ERROR'
- 找到
ROBOTSTXT_OBEY = True
并修改为ROBOTSTXT_OBEY = False
- 找到UA并写入
- 写入
工程下的目录结构
duanziPro(ProName)
:创建工程后会生成一个工程同名的文件夹spider
:爬虫文件夹,该文件夹必须要存放一个爬虫源文件items.py
:可以理解为一种容器,存储爬取的数据pipelines.py
:管道类settings.py
:工程的配置文件scrapy.cfg
:配置文件
数据解析
- 测试流程
打开终端,进入指定文件夹内 cd 3\ scrapy
创建工程scrapy startproject onePick
进入工程cd onePick
创建爬虫文件scrapy genspider theOne www.xxx.com
打开settings.py
,做3步修改
打开theOne.py
import scrapy
# TheoneSpider 文件名+spider作方法名,Spider是它的父类
class TheoneSpider(scrapy.Spider):
# 爬虫文件的名称:当前源文件的唯一标识
name = 'theOne'
# 允许的域名(通常不用)
# 限定请求发送的URL,如在allowed_domains中指定了单独的域名,无论start_urls中有多少个域名都只能访问属于指定域名之下的URL
# allowed_domains = ['www.xxx.com']
# 起始URL列表:只可以存储URL
# 作用:列表中储存的URL都会被进行get请求的发送,如需要post要重写父类方法
start_urls = ['https://www.baidu.com/','https://www.sougou.com']
# 数据解析
# parse方法调用的次数取决于请求发送的次数(start_urls列表里的URL请求次数)
# response:表示的是服务器返回的响应对象
def parse(self, response):
print(response)
执行工程scrapy crawl theOne
- scrapy数据解析
使用response.xpath(‘xpath表达式’)
scrapy封装的xpath和etree中的xpath区别:
scrapy中的xpath直接将定位到的标签中存储的值或者属性值取出,返回的是Selector对象,且相关的数据值是存储在Selector对象的data属性中,需要调用extract、extract_first()取出字符串数据
import scrapy
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.duanziku.com/gaoxiaoduanzi/']
# 数据解析
def parse(self, response):
paper_list = response.xpath('/html/body/div[5]/div[1]/ul')
for paper in paper_list:
# 返回Selector对象,数据存储在对象的data属性中
title = paper.xpath('./li/h3/a/text()')[0]
content = paper.xpath('./li/p[2]/text()')[0]
# .extract() 从Selector对象中取出data属性值,返回字符串
title = paper.xpath('./li/h3/a/text()')[0].extract()
content = paper.xpath('./li/p[2]/text()')[0].extract()
# 直接使用列表调用extract() 可以将列表中的每一个列表元素表示的Selector中data取出,返回列表
title = paper.xpath('./li/h3/a/text()').extract()
content = paper.xpath('./li/p[2]/text()').extract()
# .extract_first() 将列表中的第一个列表元素表示的Selector对象中的data值取出
title = paper.xpath('./li/h3/a/text()').extract_first()
content = paper.xpath('./li/p[2]/text()').extract_first()
print(title,content)
break
直接调用xpath表达式,返回的不是字符串,是Selector对象,所需的文本数据存储在selector对象中的data属性中
<Selector xpath='./li/h3/a/text()' data='少小离家老大回,安能辨我是雌雄'> <Selector xpath='./li/p[2]/text()' data='1.大学时不喜欢读书,跟上铺的哥们说,我最羡慕那些徒步旅行的驴友,想去那里...'>
[0].extract():少小离家老大回,安能辨我是雌雄 1.大学时不喜欢读书,跟上铺的哥们说,我最羡慕那些徒步旅行的驴友,想去那里就去那,真想来一次说走就走的徒步旅行,只可惜……
.extract():['少小离家老大回,安能辨我是雌雄', '据说女人很喜欢这个笑话', '小明,你出去!(四)', ‘搞笑段子:……俺老孙来也! 老夫子也想凑个...']
extract_first():少小离家老大回,安能辨我是雌雄 1.大学时不喜欢读书,跟上铺的哥们说,我最羡慕那些徒步旅行的驴友,想去那里就去那,真想来一次说走就走的徒步旅行,只可惜……
持久化储存
基于终端指令的持久化储存
执行指令:scrapy crawl spiderName -o filePath
该种方式只可以将parse方法的返回值存储到本地指定后缀的文本文件中
scrapy crawl duanzi -o duanzi.txt
error: Unrecognized output format 'txt'. Set a supported one (('json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle')) after a colon at the end of the output URI (i.e. -o/-O <URI>:<FORMAT>) or as a file extension.
实操
import scrapy
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.duanziku.com/gaoxiaoduanzi/']
# 持久化存储
def parse(self, response):
all_data = []
paper_list = response.xpath('/html/body/div[5]/div[1]/ul')
for i in range(1,10):
page = str(i)
for paper in paper_list:
# 数据解析title和content
title = paper.xpath('./li'+ '[' + page +']' +'/h3/a/text()').extract_first()
content = paper.xpath('./li'+'[' + page +']'+'/p[2]/text()').extract_first()
dic = {
'title' : title,
'content' : content
}
all_data.append(dic)
return all_data
执行指令scrapy crawl duanzi -o duanzi.csv
基于管道的持久化储存
步骤
- 在爬虫文件中进行数据解析(发送请求,xpath表达式定位获取数据)
- 在
items.py
中定义相关属性(在爬虫文件中解析了几个字段的数据,在此就定义几个属性)
- 在爬虫文件中将解析到的数据存储封装到Item类型的对象中 将Item类型的对象提交给管道
- 不可以通过.xxx的方式调用属性,item中存储的是键值对
- 不可以通过.xxx的方式调用属性,item中存储的是键值对
- 在管道文件
pipelines.py
中,接收爬虫文件提交过来的Item类型对象,且对其进行任意形式的持久化存储操作
- 在配置文件中开启管道机制
ITEM_PIPELINES = {'duanziPro.pipelines.DuanziproPipeline': 300,}
- 300表示管道类的优先级,数值越小优先级越高,优先级高的管道类先被执行
完整代码
- 爬虫文件
import scrapy
from duanziPro.items import DuanziproItem
class DuanziSpider(scrapy.Spider):
name = 'duanzi'
# allowed_domains = ['www.xxx.com']
start_urls = ['http://www.duanziku.com/gaoxiaoduanzi/']
# 基于管道的持久化存储
def parse(self, response):
all_data = []
paper_list = response.xpath('/html/body/div[5]/div[1]/ul')
for i in range(1,10):
page = str(i)
for paper in paper_list:
# 数据解析title和content
title = paper.xpath('./li'+ '[' + page +']' +'/h3/a/text()').extract_first()
content = paper.xpath('./li'+'[' + page +']'+'/p[2]/text()').extract_first()
# 实例化一个item对象,将解析到的数据存储到该对象中
item = DuanziproItem()
# tips: 不可以通过.xxx的方式调用属性,item中存储的是键值对
item['title'] = title
item['content'] = content
# 将item对象提交给管道
yield item
items.py
import scrapy
class DuanziproItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# Field() 定义好的属性当做是一个万能类型的属性
# 在爬虫文件中解析了几个字段的数据,在此就定义几个属性
title = scrapy.Field()
content = scrapy.Field()
- 管道文件
pipelines.py
from itemadapter import ItemAdapter
class DuanziproPipeline(object):
fp = None
# 重写父类方法
def open_spider(self,spider):
print('open_spider(),爬虫开始前执行1次')
self.fp = open('duanzi.txt','w',encoding='utf-8')
def close_spider(self,spider):
print('close_spider(),爬虫结束后执行1次')
self.fp.close()
def process_item(self, item, spider):
# item是爬虫文件提交过来 接收到的item对象,item其实是一个字典,存储键值对
self.fp.write(item['title']+':'+item['content']+'\n')
# 将item存储到文本文件中
return item
settings.py
ITEM_PIPELINES = {
'duanziPro.pipelines.DuanziproPipeline': 300,
}
实现备份(存储在本地、MySQL、Redis)
一个管道类对应一种形式的持久化存储操作,如果将数据存储到不同的载体中就需要使用多个管道类。定义后将数据写入,item不会依次提交给多个管道类,只会被提交给优先级最高的管道类,优先级高的管道类需要在process_item
中实现return item
,使得优先级低的管道类能够接收到并执行,直到最后一个被执行的管道类,不再需要return item
。
之前通过Java学的MySQL,还没学过Redis,这部分待学习后再做调整
- 爬虫文件(同上不变)
items.py
(同上不变)settings.py
ITEM_PIPELINES = {
# 300表示管道类的优先级,数值越小优先级越高
# 优先级高的管道类先被执行
'duanziPro.pipelines.DuanziproPipeline': 300,
'duanziPro.pipelines.MysqlPipeline': 301,
'duanziPro.pipelines.RedisPipeline': 302,
}
- 管道文件
pipelines.py
from itemadapter import ItemAdapter
import pymysql
from redis import Redis
class DuanziproPipeline(object):
fp = None
# 重写父类方法
def open_spider(self,spider):
print('open_spider(),爬虫开始前执行1次')
self.fp = open('duanzi.txt','w',encoding='utf-8')
def close_spider(self,spider):
print('close_spider(),爬虫结束后执行1次')
self.fp.close()
def process_item(self, item, spider):
# item是爬虫文件提交过来 接收到的item对象,item其实是一个字典,存储键值对
self.fp.write(item['title']+':'+item['content']+'\n')
# 将item存储到文本文件中
return item
class MysqlPipeline(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='utf-8')
print(self.conn)
def process_item(self, item, spider):
self.cursor = self.conn.cursor()
sql = 'insert into duanzi values ("%s","%s")'%(item['title'],item['content'])
# 开启事务
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 RedisPipeline(object):
conn = None
# 重写父类方法
def open_spider(self, spider):
self.conn = Redis(host='127.0.0.1',port=6379)
print(self.conn)
def process_item(self, item, spider):
self.conn.lpush('duanziData',item)
# return item 优先级最低,不用再传给下一个管道类