目录
- 一、Scrapy框架基础知识
- 1.1、什么是scrapy?
- 1.2、scrapy的工作流程
- 1.3、scrapy中每个模块的作用:
- 1.4、scrapy的入门使用
- 1.4.1 安装scrapy
- 1.4.2、scrapy项目实现流程
- 1.4.3、创建scrapy项目
- 1.4.4、创建爬虫
- 1.4.5、完善spider
- 1.4.6、配置settings文件
- 1.4.7、数据存储
- 1.4.7.1、使用终端命令行进行存储
- 1.4.7.2、利用管道pipeline来处理(保存)数据(写入文件中)
- 1.4.8、运行scrapy
- 二、Scrapy的进阶知识-存储
- 2.1、了解scrapy的debug信息
- 2.2、了解scrapyShell
- 2.3、settings.py中的设置信息
- 2.4、pipeline管道的深入使用
- 2.4.1 创建工程(省略),创建爬虫,配置爬虫代码如下:
- 2.4.2、 开启管道存储数据
- 2.4.2.1、前期准备
- 2.4.2.2、存储方式一——文件存储
- 2.4.2.3、存储方式二——存储到MySQL数据库中
- 2.4.2.4、存储方式三——存储到MongoDB数据库中
- 2.4.2.5、存储方式四——数据同时存储到文件、MySQL、MongoDB中
- 2.4.3 pipeline使用注意点
- 三、Scrapy的存储实战案例
- 3.1、需求
- 3.2、创建工程
- 3.3、配置settings.py
- 3.4、爬虫实现
- 3.5、配置数据传输格式items.py
- 3.6、管道代码
一、Scrapy框架基础知识
1.1、什么是scrapy?
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量的代码,就能够快速的抓取。
Scrapy 使用了Twisted异步网络框架,可以加快我们的下载速度。
1.2、scrapy的工作流程
1. 调度器把requests-->引擎-->下载中间件--->下载器
2. 下载器发送请求,获取响应---->下载中间件---->引擎--->爬虫中间件--->爬虫
3. 爬虫提取url地址,组装成request对象---->爬虫中间件--->引擎--->调度器
4. 爬虫提取数据--->引擎--->管道
5. 管道进行数据的处理和保存
1.3、scrapy中每个模块的作用:
引擎(engine):负责数据和信号在不同模块间的传递
调度器(scheduler):实现一个队列,存放引擎发过来的request请求对象
下载器(downloader):发送引擎发过来的request请求,获取响应,并将响应交给引擎
爬虫(spider):处理引擎发过来的response,提取数据,提取url,并交给引擎
管道(pipeline):处理引擎传递过来的数据,比如存储
下载中间件(downloader middleware):可以自定义的下载扩展,比如设置代理ip
爬虫中间件(spider middleware):可以自定义request请求和进行response过滤
1.4、scrapy的入门使用
1.4.1 安装scrapy
安装scrapy命令:
pip install scrapy==2.5.1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple scrapy==2.5.1
pip install scrapy-redis==0.7.2
如果安装失败. 请先升级一下pip. 然后重新安装scrapy即可.
最终你的控制台输入`scrapy version`能显示版本号. 就算成功了
1.4.2、scrapy项目实现流程
1. 创建一个scrapy项目:scrapy startproject mySpider
2. 生成一个爬虫:scrapy genspider myspider [www.xxx.cn](www.xxx.cn)
3. 提取数据:完善spider,使用xpath等方法
4. 保存数据:pipeline中保存数据
1.4.3、创建scrapy项目
创建scrapy项目的命令:scrapy startproject +<项目名字>
示例:scrapy startproject myspider
生成的目录和文件结果如下:
1.4.4、创建爬虫
命令:
在项目路径下执行:scrapy genspider +<爬虫名字> + <允许爬取的域名>
示例:
- scrapy startproject duanzi01
- cd duanzi01/
- scrapy genspider duanzi duanzixing.com
生成的目录和文件结果如下:
1.4.5、完善spider
完善spider即通过方法进行数据的提取等操作
在/duanzi01/duanzi01/spiders/duanzi.py中修改内容如下:
import scrapy
# 自定义spider类,继承scrapy.spider
class DuanziSpider(scrapy.Spider):
# 爬虫名字
name = 'duanzi'
# 允许爬取的范围,防止爬虫爬到别的网站
allowed_domains = ['duanzixing.com']
# 开始爬取的url地址
start_urls = ['http://duanzixing.com/']
# 数据提取的方法,接受下载中间件传过来的response 是重写父类中的parse方法
def parse(self, response, **kwargs):
# 打印抓取到的页面源码
# print(response.text)
# xpath匹配每条段子的article列表
article_list = response.xpath('//article[@class="excerpt"]')
# print(article_list)
# 循环获取每一个article
for article in article_list:
# 匹配标题
# title = article.xpath('./header/h2/a/text()')[0].extract()
# 等同于
title = article.xpath('./header/h2/a/text()').extract_first()
# 获取段子内容
con = article.xpath('./p[@class="note"]/text()').extract_first()
print('title', title)
print('con', con)
启动爬虫命令: scrapy crawl duanzi
response响应对象的常用属性
- response.url:当前响应的url地址
- response.request.url:当前响应对应的请求的url地址
- response.headers:响应头
- response.request.headers:当前响应的请求头
- response.body:响应体,也就是html代码,byte类型
- response.text 返回响应的内容 字符串
- response.status:响应状态码
注意:
1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
2. extract() 返回一个包含有字符串的列表
3. extract_first() 返回列表中的第一个字符串,列表为空没有返回None
4. spider中的parse方法必须有
5. 需要抓取的url地址必须属于allowed_domains,但是start_urls中的url地址没有这个限制
6. 启动爬虫的时候注意启动的位置,是在项目路径下启动
1.4.6、配置settings文件
- ROBOTSTXT_OBEY = False
robots是一种反爬协议。在协议中规定了哪些身份的爬虫无法爬取的资源有哪些。
在配置文件中setting,取消robots的监测:
- 在配置文件中配置全局的UA:USER_AGENT='xxxx'
- 在配置文件中加入日志等级:LOG_LEVEL = 'ERROR' 只输出错误信息
其它日志级别:
- CRITICAL 严重错误
- ERROR 错误
- WARNING 警告
- INFO 消息
- DEBUG 调试
1.4.7、数据存储
1.4.7.1、使用终端命令行进行存储
- 代码配置
/myspider/myspider/spiders/ITSpider.py
class ITSpider(scrapy.Spider):
name = 'ITSpider'
start_urls = ['https://duanzixing.com/page/2/']
# 通过终端写入文件的方式
def parse(self, response):
article_list = response.xpath('/html/body/section/div/div/article')
# 创建列表, 存储数据
all_data = []
for article in article_list:
title = article.xpath('./header/h2/a/text()').extract_first()
con = article.xpath('./p[2]/text()').extract_first()
dic = {
'title': title,
'con': con
}
all_data.append(dic)
return all_data
- 终端命令
scrapy crawl ITSpider -o ITSpider.csv
将文件存储到ITSpider.csv 文件中
1.4.7.2、利用管道pipeline来处理(保存)数据(写入文件中)
1、打开 myspider/myspider/items.py文件,添加如下代码:
import scrapy
class DuanziproItem(scrapy.Item):
title = scrapy.Field()
con = scrapy.Field()
2、打开/myspider/myspider/spiders/ITSpider.py文件,添加如下代码:
import scrapy
from myspider.items import DuanziproItem
class ITSpiderSpider(scrapy.Spider):
name = 'ITSpider'
start_urls = ['https://duanzixing.com/page/2/']
# 写入管道 持久化存储
def parse(self, response):
article_list = response.xpath('/html/body/section/div/div/article')
for article in article_list:
title = article.xpath('./header/h2/a/text()').extract_first()
con = article.xpath('./p[2]/text()').extract_first()
item = DuanziproItem()
item['title'] = title
item['con'] = con
yield item
2.1、为什么要使用yield?
让整个函数变成一个生成器,遍历这个函数的返回值的时候,挨个把数据读到内存,不会造成内存的瞬间占用过高
注意:yield能够传递的对象只能是:BaseItem,Request,dict,None
3、打开管道文件myspider/myspider/pipelines.py 添加如下代码:、
class ITSpiderPipeline:
f = None
def open_spider(self, spider):
print('爬虫开始时被调用一次')
self.f = open('./duanzi.text', 'w')
# 爬虫文件中提取数据的方法每yield一次item,就会运行一次
# 该方法为固定名称函数
def process_item(self, item, spider):
print(item)
self.f.write(item['title']+item['con']+'\n')
return item
def close_spider(self, spider):
print('爬虫结束时被调用')
self.f.close()
3.1、open_spider方法
重写父类中open_spider方法,只有爬虫开始时被调用一次
3.2、close_spider 方法
重写父类中lose_spider方法 爬虫结束时被调用一次
4、在settings.py设置开启pipeline
将默认被注释的管道打开
ITEM_PIPELINES = {
'myspider.pipelines.MyspiderPipeline': 300,
}
其中数值代表优先级,数值越小优先级越高
1.4.8、运行scrapy
命令:在项目目录下执行scrapy crawl +<爬虫名字>
示例:scrapy crawl ITSpider
二、Scrapy的进阶知识-存储
2.1、了解scrapy的debug信息
2.2、了解scrapyShell
scrapy shell是scrapy提供的一个终端工具,能够通过它查看scrapy中对象的属性和方法,以及测试xpath
使用方法:
scrapy shell http://www.baidu.com
在终端输入上述命令后,能够进入python的交互式终端,此时可以使用:
- response.xpath():直接测试xpath规则是否正确
- response.url:当前响应的url地址
- response.request.url:当前响应对应的请求的url地址
- response.headers:响应头
- response.body:响应体,也就是html代码,默认是byte类型
- response.request.headers:当前响应的请求头
2.3、settings.py中的设置信息
1、USER_AGENT 设置ua
2、ROBOTSTXT_OBEY 是否遵守robots协议,默认是遵守
3、CONCURRENT_REQUESTS 设置并发请求的数量,默认是16个
4、DOWNLOAD_DELAY 下载延迟,默认无延迟 (下载器在从同一网站下载连续页面之前应等待的时间(以秒为单位)。这可以用来限制爬行速度,以避免对服务器造成太大影响)
5、COOKIES_ENABLED 是否开启cookie,即每次请求带上前一次的cookie,默认是开启的
6、DEFAULT_REQUEST_HEADERS 设置默认请求头,这里加入了USER_AGENT将不起作用
7、SPIDER_MIDDLEWARES 爬虫中间件,设置过程和管道相同
8、DOWNLOADER_MIDDLEWARES 下载中间件
9、LOG_LEVEL 控制终端输出信息的log级别,终端默认显示的是debug级别的log信息
- LOG_LEVEL = "WARNING"
- CRITICAL 严重
- ERROR 错误
- WARNING 警告
- INFO 消息
- DEBUG 调试
10、LOG_FILE 设置log日志文件的保存路径,如果设置该参数,终端将不再显示信息
LOG_FILE = "./test.log"
2.4、pipeline管道的深入使用
2.4.1 创建工程(省略),创建爬虫,配置爬虫代码如下:
1、打开items.py文件,添加如下代码:
注意:属性名称和当前爬虫db.py中抓到要存储数据的变量一致,否则报错
class DoubanfileItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
img_src = scrapy.Field()
name = scrapy.Field()
to_star = scrapy.Field()
2、打开db.py文件,添加如下代码:
import scrapy
import re
from doubanfile.items import DoubanfileItem
class DbSpider(scrapy.Spider):
name = 'db'
allowed_domains = ['https://movie.douban.com/chart']
start_urls = ['https://movie.douban.com/chart']
def parse(self, resp, **kwargs):
item = DoubanfileItem() # 实例化item类
# print(resp.text)
# 先获取到每一行数据的tr
tr_list = resp.xpath('//div[@class="indent"]/div/table/tr[@class="item"]')
for tr in tr_list:
# 获取封面
item['img_src'] = tr.xpath('./td[1]/a/img/@src').extract_first()
# 电影名称
name = tr.xpath('./td[2]/div[@class="pl2"]/a//text()').extract_first()
# 去除空白字符使用replace替换
# name = name.replace('\n', '').replace('\r', '').replace('/', '').replace(' ', '')
# 去除空白字符使用正则替换
item['name'] = re.sub('(/)|(\s)', '', name)
# 主演
item['to_star'] = tr.xpath('./td[2]/div[@class="pl2"]/p[@class="pl"]/text()').extract_first()
yield item
2.4.2、 开启管道存储数据
2.4.2.1、前期准备
pipeline中常用的方法:
1. process_item(self,item,spider):实现对item数据的处理
2. open_spider(self, spider): 在爬虫开启的时候仅执行一次
3. close_spider(self, spider): 在爬虫关闭的时候仅执行一次
settings.py 打开当前注释:
ITEM_PIPELINES = {
'doubanfile.pipelines.DoubanfilePipeline': 300,
}
2.4.2.2、存储方式一——文件存储
打开pipelines.py文件,添加如下代码:
class DoubanfilePipeline:
f = None
def open_spider(self, item):
self.f = open('./db.text', 'w')
def process_item(self, item, spider):
print(item)
self.f.write(item['img_src']+'\n')
self.f.write(item['name']+'\n')
self.f.write(item['to_star']+'\n')
return item
def close_spider(self, item):
self.f.close()
注意:当前process_item中的return item必须存在,如果当前爬虫存在于多个管道的时候,如果没有return item 则下一个管道不能获取到当前的item数据
2.4.2.3、存储方式二——存储到MySQL数据库中
1、创建数据库表
create database douban character set utf8;
use douban
CREATE TABLE `douban` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`img_src` varchar(200) NOT NULL COMMENT '封面地址',
`name` varchar(50) NOT NULL COMMENT '电影名称',
`to_star` varchar(250) NOT NULL COMMENT '主演',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2、打开pipelines.py文件,添加如下代码:
from itemadapter import ItemAdapter
import pymysql
class DoubanmysqlPipeline:
db = None
cursor = None
def open_spider(self, spider):
# 判断当前运行的是否为db爬虫,不是db爬虫则下面代码不执行
# 当前仅限于一个scrapy下有多个爬虫工程
if spider.name == 'db':
self.db = pymysql.connect(host='127.0.0.1', port=3306, db='douban', user='root', passwd='123456', charset='utf8')
self.cursor = self.db.cursor()
def process_item(self, item, spider):
# 判断当前运行的是否为db爬虫
if spider.name == 'db':
try:
sql = f'insert into douban(img_src, name, to_star) values("{item["img_src"]}", "{item["name"]}", "{item["to_star"]}")'
self.cursor.execute(sql)
self.db.commit()
except Exception as e:
print(e)
print(sql)
self.db.rollback()
return item
def close_spider(self, item):
# 关闭数据库连接
self.db.close()
2.4.2.4、存储方式三——存储到MongoDB数据库中
打开pipelines.py文件,添加如下代码:
from itemadapter import ItemAdapter
from pymongo import MongoClient
class DoubanmongodbPipeline:
con = None
collection = None
def open_spider(self, spider): # 在爬虫开启的时候仅执行一次
if spider.name == 'db':
self.con = MongoClient(host='127.0.0.1', port=27017) # 实例化mongoclient
self.collection = self.con.spider.douban # 创建数据库名为spider,集合名为douban的集合操作对象
def process_item(self, item, spider):
if spider.name == 'db':
# print(spider.name)
self.collection.insert_one(dict(item)) # 此时item对象需要先转换为字典,再插入
# 不return的情况下,另一个权重较低的pipeline将不会获得item
return item
def close_spider(self, item):
# 关闭数据库连接
self.con.close()
注意:
需要开启mongo服务
mongod.exe --dbpath=C:/User/xxx/db
新开终端
mongo.exe
2.4.2.5、存储方式四——数据同时存储到文件、MySQL、MongoDB中
1、打开pipelines.py文件,添加如下代码:
from itemadapter import ItemAdapter
import pymysql
from pymongo import MongoClient
class DoubanFilePipeline:
'''
设置文件存储
'''
f = None
def open_spider(self, item):
self.f = open('./db.text', 'w')
def process_item(self, item, spider):
print(item)
self.f.write(item['img_src'] + '\n')
self.f.write(item['name'] + '\n')
self.f.write(item['to_star'] + '\n')
return item
def close_spider(self, item):
self.f.close()
class DoubanmysqlPipeline:
'''
存储到MySQL数据库中
'''
db = None
cursor = None
def open_spider(self, spider):
# 判断当前运行的是否为db爬虫,不是db爬虫则下面代码不执行
# 当前仅限于一个scrapy下有多个爬虫工程
if spider.name == 'db':
self.db = pymysql.connect(host='127.0.0.1', port=3306, db='douban', user='root', passwd='123456', charset='utf8')
self.cursor = self.db.cursor()
def process_item(self, item, spider):
# 判断当前运行的是否为db爬虫
if spider.name == 'db':
try:
sql = f'insert into douban(img_src, name, to_star) values("{item["img_src"]}", "{item["name"]}", "{item["to_star"]}")'
self.cursor.execute(sql)
self.db.commit()
except Exception as e:
print(e)
print(sql)
self.db.rollback()
return item
def close_spider(self, item):
# 关闭数据库连接
self.db.close()
class DoubanmongodbPipeline:
'''
存储到MongoDB数据库中
'''
con = None
collection = None
def open_spider(self, spider): # 在爬虫开启的时候仅执行一次
if spider.name == 'db':
self.con = MongoClient(host='127.0.0.1', port=27017) # 实例化mongoclient
self.collection = self.con.spider.douban # 创建数据库名为spider,集合名为douban的集合操作对象
def process_item(self, item, spider):
if spider.name == 'db':
# print(spider.name)
self.collection.insert_one(dict(item)) # 此时item对象需要先转换为字典,再插入
# 不return的情况下,另一个权重较低的pipeline将不会获得item
return item
def close_spider(self, item):
# 关闭数据库连接
self.con.close()
2、修改settings.py 添加管道
ITEM_PIPELINES = {
'douban.pipelines.DoubanFilePipeline': 300, # 300表示权重
'douban.pipelines.DoubanmysqlPipeline': 400,
'douban.pipelines.DoubanmongodbPipeline': 500,
}
注意:在此设置中分配给类的整数值决定了它们运行的顺序:项目从低值到高值的类。通常将这些数字定义在 0-1000 范围内。
思考:pipeline在settings中能够开启多个,为什么需要开启多个?
1. 不同的pipeline可以处理不同爬虫的数据,通过spider.name属性来区分
2. 不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存
3. 同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分
2.4.3 pipeline使用注意点
1. 使用之前需要在settings中开启
2. pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过
3. 有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None值
4. pipeline中process_item的方法必须有,否则item没有办法接受和处理
5. process_item方法接受item和spider,其中spider表示当前传递item过来的spider
6. open_spider(spider) :能够在爬虫开启的时候执行一次
7. close_spider(spider) :能够在爬虫关闭的时候执行一次
8. 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接
三、Scrapy的存储实战案例
3.1、需求
抓取网站:https://movie.douban.com/chart 中子页面的数据
实现思路:
+ 先对第一层url进行请求
+ 请求返回数据进行解析循环 找到每一条子页面的url
+ 找到子页面的url以后进行再次请求
+ 请求解析子页面请求返回的数据
+ 结束
3.2、创建工程
+ scrapy startproject doubandetail
+ cd doubandetail
+ scrapy genspider db movie.douban.com/chart
3.3、配置settings.py
# 设置日志级别
LOG_LEVEL = 'ERROR'
ROBOTSTXT_OBEY = False
COOKIES_ENABLED = False # cookies的中间件将不起作用,下面的cookie起作用
# 设置请求头
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36',
'Cookie': '设定cookie 防止反扒'
}
# 开启管道
ITEM_PIPELINES = {
'doubandetail.pipelines.DoubandetailPipeline': 300,
}
3.4、爬虫实现
+ 打开db.py,添加如下代码:
import scrapy
from doubandetail.items import DoubandetailItem
import re
class DbSpider(scrapy.Spider):
name = 'db'
start_urls = ['http://movie.douban.com/chart/']
def parse(self, resp, **kwargs):
print(resp.text)
# 先获取到每一行数据的tr
tr_list = resp.xpath('//div[@class="indent"]/div/table/tr[@class="item"]')
for tr in tr_list:
# 获取每个详情页的url
detail_url = tr.xpath('./td[1]/a/@href').extract_first()
# 请求子页面
print(detail_url)
yield scrapy.Request(detail_url, callback=self.parse_deatil)
# 解析子页面数据
def parse_deatil(self, response):
# 默认携带我们settings.py中所配置的请求头进行请求
# print(response.request.headers)
item = DoubandetailItem()
item['name'] = response.xpath('//*[@id="content"]/h1/span[1]/text()').extract_first() # 电影名称
item['director'] = response.xpath('//*[@id="info"]/span[1]/span[2]/a/text()').extract_first() # 导演
item['screenwriter'] = ''.join(response.xpath('//*[@id="info"]/span[2]/span[2]//text()').extract()) # 编剧
item['to_star'] = ''.join(response.xpath('//*[@id="info"]/span[3]/span[2]//text()').extract()) # 主演
item['type'] = '/'.join(response.xpath('//span[@property="v:genre"]//text()').extract()) # 类型
item['link_report'] = re.sub('(/)|(\s)|(\u3000)|(\'\n\')', '', link_report)
print(item)
return item
+ 注意:
需要将allowed_domains注释掉,否则详情页url不符合当前允许,所以会出现不请求的问题
3.5、配置数据传输格式items.py
import scrapy
class DoubandetailItem(scrapy.Item):
name = scrapy.Field() # 电影名称
director = scrapy.Field() # 导演
screenwriter = scrapy.Field() # 编剧
to_star = scrapy.Field() # 主演
type = scrapy.Field() # 类型
3.6、管道代码
from itemadapter import ItemAdapter
from pymongo import MongoClient
class DoubandetailPipeline:
con = None
collection = None
def open_spider(self, spider): # 在爬虫开启的时候仅执行一次
self.con = MongoClient(host='127.0.0.1', port=27017) # 实例化mongoclient
self.collection = self.con.spider.douban # 创建数据库名为spider,集合名为douban的集合操作对象
def process_item(self, item, spider):
print(item)
self.collection.insert_one(dict(item)) # 此时item对象需要先转换为字典,再插入
return item
def close_spider(self, item):
# 关闭数据库连接
self.con.close()
注意:
如果访问频率过高被禁止访问,可以携带登录后的cookie进行访问