Scrapy框架
Scrapy框架
一、前言
1、介绍
前面我们学习了基础的爬虫实现方法和selenium以及mongodb数据库,那么接下来会我们学习一个上场率非常高的爬虫框架:scrapy
2、内容
- scrapy的基础概念和工作流程
- scrapy入门使用
二、scrapy的概念和流程
学习目标:
- 了解 scrapy的概念
- 掌握 scrapy框架的运行流程
- 掌握 scrapy框架的作用
1、为什么学习scrapy?
- 能够让开发过程方便、快速
- scrapy框架能够让我们的爬虫效率更高
2、什么是scrapy?
文档地址:https://docs.scrapy.org/en/latest/
Scrapy 使用了Twisted['twɪstɪd]异步网络框架,可以加快我们的下载速度。
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量的代码,就能够快速的抓取。
3、异步和非阻塞的区别
前面我们说Twisted是一个异步的网络框架,经常我们也听到一个词语叫做非阻塞,那么他们有什么区别呢?
异步:调用在发出之后,这个调用就直接返回,不管有无结果;异步是过程。 非阻塞:关注的是程序在等待调用结果(消息,返回值)时的状态,指在不能立刻得到结果之前,该调用不会阻塞当前线程。
4、scrapy的工作流程
4.1 回顾之前的爬虫流程
4.2 上面的流程可以改写为
4.3 scrapy的流程
其流程可以描述如下:
- 调度器把requests-->引擎-->下载中间件--->下载器
- 下载器发送请求,获取响应---->下载中间件---->引擎--->爬虫中间件--->爬虫
- 爬虫提取url地址,组装成request对象---->爬虫中间件--->引擎--->调度器
- 爬虫提取数据--->引擎--->管道
- 管道进行数据的处理和保存
注意:
- 图中绿色线条的表示数据的传递
- 注意图中中间件的位置,决定了其作用
- 注意其中引擎的位置,所有的模块之前相互独立,只和引擎进行交互
4.4 scrapy中每个模块的具体作用
小结
-
scrapy的概念:Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架
-
scrapy框架的运行流程以及数据传递过程:
- 调度器把requests-->引擎-->下载中间件--->下载器
- 下载器发送请求,获取响应---->下载中间件---->引擎--->爬虫中间件--->爬虫
- 爬虫提取url地址,组装成request对象---->爬虫中间件--->引擎--->调度器
- 爬虫提取数据--->引擎--->管道
- 管道进行数据的处理和保存
-
scrapy框架的作用:通过少量代码实现快速抓取
-
掌握scrapy中每个模块的作用:
引擎(engine):负责数据和信号在不同模块间的传递
调度器(scheduler):实现一个队列,存放引擎发过来的request请求对象
下载器(downloader):发送引擎发过来的request请求,获取响应,并将响应交给引擎
爬虫(spider):处理引擎发过来的response,提取数据,提取url,并交给引擎
管道(pipeline):处理引擎传递过来的数据,比如存储
下载中间件(downloader middleware):可以自定义的下载扩展,比如设置代理ip
爬虫中间件(spider middleware):可以自定义request请求和进行response过滤
-
理解异步和非阻塞的区别:异步是过程,非阻塞是状态
三、scrapy的入门使用
学习目标:
- 掌握 scrapy的安装
- 应用 创建scrapy的项目
- 应用 创建scrapy爬虫
- 应用 运行scrapy爬虫
- 应用 解析并获取scrapy爬虫中的数据
1、scrapy项目实现流程
- 创建一个scrapy项目:scrapy startproject mySpider
- 生成一个爬虫:scrapy genspider myspider www.xxx.cn
- 提取数据:完善spider,使用xpath等方法
- 保存数据:pipeline中保存数据
2、安装
安装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即可. python -m pip install --upgrade pip
最新版本的pip升级完成后. 安装依然失败, 可以根据报错信息进行一点点的调整, 多试几次pip. 直至success.
注意:
如果上述过程还是无法正常安装scrapy, 可以考虑用下面的方案来安装:
如果上述过程还是无法正常安装scrapy, 可以考虑用下面的方案来安装:
-
安装wheel
pip install wheel
-
下载twisted安装包, https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
-
用wheel安装twisted.
pip install Twisted‑21.7.0‑py3‑none‑any.whl
-
安装pywin32
pip install pywin32
-
安装scrapy
pip install scrapy
总之, 最终你的控制台输入scrapy version
能显示版本号. 就算成功了
3、创建scrapy项目
创建scrapy项目的命令:scrapy startproject +<项目名字>
示例:scrapy startproject myspider
生成的目录和文件结果如下:
scrapy的核心组件
- 引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心) - 调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 - 下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的) - 爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面 - 项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
4、创建爬虫
命令:在项目路径下执行:scrapy genspider +<爬虫名字> + <允许爬取的域名>
示例:
- scrapy startproject duanzi01
- cd duanzi01/
- scrapy genspider duanzi duanzixing.com
生成的目录和文件结果如下:
注意
出现以下问题. 解决方案:
twisted.web._newclient.ResponseNeverReceived: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', '', 'unexpected eof while reading')]>]
解决方案, 降低cryptography版本即可:
pip uninstall cryptography
pip install cryptography==36.0.2
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()')
# [<Selector xpath='./header/h2/a/text()' data='一个不小心就把2000块钱的包包设置成了50包邮'>]
# 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:响应状态码
注意:
-
response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
-
extract() 返回一个包含有字符串的列表
如果使用列表调用extract()则表示,extract会将列表中每一个列表元素进行extract操作,返回列表
-
extract_first() 返回列表中的第一个字符串,列表为空没有返回None
-
spider中的parse方法必须有
-
需要抓取的url地址必须属于allowed_domains,但是start_urls中的url地址没有这个限制
-
启动爬虫的时候注意启动的位置,是在项目路径下启动
6、配置settings文件
-
ROBOTSTXT_OBEY = False
robots是一种反爬协议。在协议中规定了哪些身份的爬虫无法爬取的资源有哪些。
在配置文件中setting,取消robots的监测:
-
在配置文件中配置全局的UA:USER_AGENT='xxxx'
-
在配置文件中加入日志等级:LOG_LEVEL = 'ERROR' 只输出错误信息
其它日志级别
- CRITICAL 严重错误
- ERROR 错误
- WARNING 警告
- INFO 消息
- DEBUG 调试
代码实例
# Scrapy settings for mySpider project
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
7、数据存储
7.1、使用终端命令行进行存储
-
代码配置
/myspider/myspider/spiders/ITSpider.py
class ITSpider(scrapy.Spider): name = 'ITSpider' # allowed_domains = ['www.xxx.com'] 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 文件中
7.2、利用管道pipeline来处理(保存)数据(写入文件中)
先跟着配置 后面会单讲
代码配置
-
打开items.py文件 添加如下代码
myspider/myspider/items.py
import scrapy class MyspiderItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() con = scrapy.Field()
-
/myspider/myspider/spiders/ITSpider.py
import scrapy from myspider.items import MyspiderItem class ITSpiderSpider(scrapy.Spider): name = 'ITSpider' # allowed_domains = ['www.xxx.com'] 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
在爬虫文件ITSpider.py中parse()函数中最后添加
yield item
思考:为什么要使用yield?
- 让整个函数变成一个生成器,有什么好处呢?
- 遍历这个函数的返回值的时候,挨个把数据读到内存,不会造成内存的瞬间占用过高
- python3中的range和python2中的xrange同理
注意:yield能够传递的对象只能是:BaseItem,Request,dict,None
-
打开管道文件 pipelines.py 添加如下代码
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()
-
open_spider方法
重写父类中open_spider方法 只有爬虫开始十被调用一次
-
close_spider 方法
重写父类中lose_spider方法 爬虫结束时被调用一次
-
-
在settings.py设置开启pipeline
将默认被注释的管道打开
ITEM_PIPELINES = { 'myspider.pipelines.MyspiderPipeline': 300, }
其中数值代表优先级 数值越小优先级越高
8、运行scrapy
命令:在项目目录下执行scrapy crawl +<爬虫名字>
示例:scrapy crawl ITSpider
9、总结
- srapy的安装:pip install scrapy
- 创建scrapy的项目: scrapy startproject myspider
- 创建scrapy爬虫:在项目目录下执行 scrapy genspider ITSpider www.xxx.cn
- 运行scrapy爬虫:在项目目录下执行 scrapy crawl ITSpider
- 解析并获取scrapy爬虫中的数据:
- response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
- extract() 返回一个包含有字符串的列表
- extract_first() 返回列表中的第一个字符串,列表为空没有返回None
- scrapy管道的基本使用:
- 完善pipelines.py中的process_item函数
- 在settings.py中设置开启pipeline
四、Scrapy深入使用-存储
学习目标:
- 了解 scrapy debug信息
- 了解 scrapy shell的使用
- 掌握 scrapy的settings.py设置
- 掌握 scrapy管道(pipelines.py)的使用
- 掌握scrapy下载图片
1、了解scrapy的debug信息
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:当前响应的请求头
3、settings.py中的设置信息
3.1 为什么项目中需要配置文件
在配置文件中存放一些公共变量,在后续的项目中方便修改,如:本地测试数据库和部署服务器的数据库不一致
3.2 配置文件中的变量使用方法
- 变量名一般全部大写
- 导入即可使用
3.3 settings.py中的重点字段和含义
-
USER_AGENT 设置ua
-
ROBOTSTXT_OBEY 是否遵守robots协议,默认是遵守
-
CONCURRENT_REQUESTS 设置并发请求的数量,默认是16个
-
DOWNLOAD_DELAY 下载延迟,默认无延迟 (下载器在从同一网站下载连续页面之前应等待的时间(以秒为单位)。这可以用来限制爬行速度,以避免对服务器造成太大影响)
-
COOKIES_ENABLED 是否开启cookie,即每次请求带上前一次的cookie,默认是开启的
-
DEFAULT_REQUEST_HEADERS 设置默认请求头,这里加入了USER_AGENT将不起作用
-
SPIDER_MIDDLEWARES 爬虫中间件,设置过程和管道相同
-
DOWNLOADER_MIDDLEWARES 下载中间件
-
LOG_LEVEL 控制终端输出信息的log级别,终端默认显示的是debug级别的log信息
- LOG_LEVEL = "WARNING"
- CRITICAL 严重
- ERROR 错误
- WARNING 警告
- INFO 消息
- DEBUG 调试
- LOG_LEVEL = "WARNING"
-
LOG_FILE 设置log日志文件的保存路径,如果设置该参数,终端将不再显示信息
LOG_FILE = "./test.log"
4、pipeline管道的深入使用
之前我们在scrapy入门使用一节中学习了管道的基本使用,接下来我们深入的学习scrapy管道的使用
4.1 使用终端命令行进行存储
-
代码配置
/myspider/myspider/spiders/ITSpider.py
class ITSpider(scrapy.Spider): name = 'ITSpider' # allowed_domains = ['www.xxx.com'] start_urls = ['https://duanzixing.com/page/1/'] # 通过终端写入文件的方式 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 爬虫名称 -o 文件名.csv
scrapy crawl ITSpider -o ITSpider.csv
将文件存储到ITSpider.csv 文件中
4.2 使用管道存储到文件中
(1) 抓取网站:https://movie.douban.com/chart
(2) 创建工程
-
scrapy startproject doubanfile
-
cd doubanfile
-
scrapy genspider db movie.douban.com/chart
(3) 抓取需求与实现
抓取:
- 封面
- 电影名称
- 主演
(4) 配置settings.py
LOG_LEVEL = 'ERROR'
ROBOTSTXT_OBEY = False
(5) db.py中实现
先抓取每一行数据的tr列表
import scrapy
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):
print(resp.text)
# 先获取到每一行数据的tr
tr_list = resp.xpath('//div[@class="indent"]/div/table/tr[@class="item"]')
print(tr_list)
此刻运行打印
srapy crawl db
发现无任何打印,将LOG_LEVEL = 'ERROR' 更改为 INFO ,发现此刻请求没有权限
发现问题所在后修改settings.py
在默认请求头DEFAULT_REQUEST_HEADERS
中添加User-Agent
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'
}
再次请求,成功
(6) db.py完整实现
import scrapy
import re
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):
# print(resp.text)
# 先获取到每一行数据的tr
tr_list = resp.xpath('//div[@class="indent"]/div/table/tr[@class="item"]')
for tr in tr_list:
# 获取封面
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(' ', '')
# 去除空白字符使用正则替换
name = re.sub('(/)|(\s)', '', name)
# 主演
to_star = tr.xpath('./td[2]/div[@class="pl2"]/p[@class="pl"]/text()').extract_first()
(7) 打开items.py文件
添加如下代码
属性名称和当前爬虫db.py中抓到要存储数据的变量一致 否则报错
import scrapy
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()
(8) 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
思考:为什么要使用yield?
- 让整个函数变成一个生成器,有什么好处呢?
- 遍历这个函数的返回值的时候,挨个把数据读到内存,不会造成内存的瞬间占用过高
- python3中的range和python2中的xrange同理
注意:yield能够传递的对象只能是:BaseItem,Request,dict,None
(8) 开启管道
pipeline中常用的方法:
- process_item(self,item,spider):实现对item数据的处理
- open_spider(self, spider): 在爬虫开启的时候仅执行一次
- close_spider(self, spider): 在爬虫关闭的时候仅执行一次
settings.py 打开当前注释
ITEM_PIPELINES = {
'doubanfile.pipelines.DoubanfilePipeline': 300,
}
(9) 在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数据
4.3 存储到MySQL数据库中
(1) 抓取网站:https://movie.douban.com/chart
(2) 创建工程
-
scrapy startproject doubanmysql
-
cd doubanmysql
-
scrapy genspider db movie.douban.com/chart
(3) 抓取需求与实现
抓取:
- 封面
- 电影名称
- 主演
(4) 配置settings.py
# 设置日志级别
LOG_LEVEL = 'ERROR'
ROBOTSTXT_OBEY = False
# 设置请求头
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'
}
# 开启管道
ITEM_PIPELINES = {
'doubanmysql.pipelines.DoubanmysqlPipeline': 300,
}
(5) 创建豆瓣数据库
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
示例SQL语句
insert into info(img_src, name, to_star) values("https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2673202034.webp", "尼罗河上的惨案 Death on the Nile", "肯尼思·布拉纳", "迈克尔·格林 / 阿加莎·克里斯蒂", "肯尼思·布拉纳艾玛·麦基/艾米·汉莫/珍妮弗·桑德斯/苏菲·奥康内多/安妮特·贝宁/妮基塔·查达哈/汤姆·巴特曼/亚当·加西亚/汉/爱德华·刘易斯·弗伦奇/拉普洛斯·卡伦福佐斯")
(6) items.py
import scrapy
class DoubanmysqlItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
img_src = scrapy.Field()
name = scrapy.Field()
to_star = scrapy.Field()
(7) db.py
import scrapy
from doubanmysql.items import DoubanmysqlItem
import re
class DbSpider(scrapy.Spider):
name = 'db'
allowed_domains = ['movie.douban.com/chart']
start_urls = ['http://movie.douban.com/chart/']
def parse(self, resp, **kwargs):
item = DoubanmysqlItem() # 实例化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
(8) 管道代码
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()
(9) 运行爬虫
scrapy crawl db
查看终端数据是否有报错,如果没有报错查看数据库数据是否存储成功
4.4 存储到MongoDB数据库中
(1) 抓取网站:https://movie.douban.com/chart
(2) 创建工程
-
scrapy startproject doubanmongodb
-
cd doubanmongodb
-
scrapy genspider db movie.douban.com/chart
(3) 抓取需求与实现
抓取:
- 封面
- 电影名称
- 主演
(4) 配置settings.py
# 设置日志级别
LOG_LEVEL = 'ERROR'
ROBOTSTXT_OBEY = False
# 设置请求头
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'
}
# 开启管道
ITEM_PIPELINES = {
'doubanmongodb.pipelines.DoubanmongodbPipeline': 300,
}
(5) items.py
import scrapy
class DoubanmongodbItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
img_src = scrapy.Field()
name = scrapy.Field()
to_star = scrapy.Field()
(6) db.py
import scrapy
from doubanmongodb.items import DoubanmongodbItem
import re
class DbSpider(scrapy.Spider):
name = 'db'
allowed_domains = ['movie.douban.com/chart']
start_urls = ['http://movie.douban.com/chart/']
def parse(self, resp, **kwargs):
item = DoubanmongodbItem() # 实例化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
(7) 管道代码
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
(8) 运行爬虫
scrapy crawl db
查看终端数据是否有报错,如果没有报错查看数据库数据是否存储成功
4.5 数据同时存储到文件、MySQL、MongoDB中
(1) 抓取网站:https://movie.douban.com/chart
(2) 创建工程
-
scrapy startproject douban
-
cd douban
-
scrapy genspider db movie.douban.com/chart
(3) 抓取需求与实现
抓取:
- 封面
- 电影名称
- 主演
(4) 配置settings.py
# 设置日志级别
LOG_LEVEL = 'ERROR'
ROBOTSTXT_OBEY = False
# 设置请求头
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'
}
# 开启管道
ITEM_PIPELINES = {
'douban.pipelines.DoubanPipeline': 300,
}
(5) items.py
import scrapy
class DoubanItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
img_src = scrapy.Field()
name = scrapy.Field()
to_star = scrapy.Field()
(6) db.py
import scrapy
from douban.items import DoubanItem
import re
class DbSpider(scrapy.Spider):
name = 'db'
allowed_domains = ['movie.douban.com/chart']
start_urls = ['http://movie.douban.com/chart/']
def parse(self, resp, **kwargs):
item = DoubanItem() # 实例化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
(7) 管道代码
# 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.html
# useful for handling different item types with a single interface
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()
(8) 修改settings.py 添加管道
ITEM_PIPELINES = {
'douban.pipelines.DoubanFilePipeline': 300, # 300表示权重
'douban.pipelines.DoubanmysqlPipeline': 400,
'douban.pipelines.DoubanmongodbPipeline': 500,
}
您在此设置中分配给类的整数值决定了它们运行的顺序:项目从低值到高值的类。通常将这些数字定义在 0-1000 范围内。
思考:pipeline在settings中能够开启多个,为什么需要开启多个?
- 不同的pipeline可以处理不同爬虫的数据,通过spider.name属性来区分
- 不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存
- 同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分
4.6 pipeline使用注意点
- 使用之前需要在settings中开启
- pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过
- 有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None值
- pipeline中process_item的方法必须有,否则item没有办法接受和处理
- process_item方法接受item和spider,其中spider表示当前传递item过来的spider
- open_spider(spider) :能够在爬虫开启的时候执行一次
- close_spider(spider) :能够在爬虫关闭的时候执行一次
- 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接
4.7 总结
- debug能够展示当前程序的运行状态
- scrapy shell能够实现xpath的测试和对象属性和方法的尝试
- scrapy的settings.py能够实现各种自定义的配置,比如下载延迟和请求头等
- 管道能够实现数据的清洗和保存,能够定义多个管道实现不同的功能,其中有个三个方法
- process_item(self,item,spider):实现对item数据的处理
- open_spider(self, spider): 在爬虫开启的时候仅执行一次
- close_spider(self, spider): 在爬虫关闭的时候仅执行一次
5、抓取详情页
(1) 概述
在之前的抓取中我们都是抓取当前再生成spider时候的网址中的数据,那如果我们想要访问当前数据中的子页面的数据,那又该如何操作呢,回忆下我们在前面requests课程中是如何抓取子页面数据的
思路:
- 先对第一层url进行请求
- 请求返回数据进行解析循环 找到每一条子页面的url
- 找到子页面的url以后进行再次请求
- 请求解析子页面请求返回的数据
- 结束
那么在我们scrapy中的思路也是一样的,通过抓取当前第一层页面中解析出来的子页面的数据,在通过scrapy.Rquests进行子页面的请求,那我们了解了当前抓取的思路,就开始我们scrapy子页面的请求与抓取吧
(2) 抓取网站:https://movie.douban.com/chart
(3) 创建工程
-
scrapy startproject doubandetail
-
cd doubandetail
-
scrapy genspider db movie.douban.com/chart
(4) 抓取需求与实现
抓取:
- 电影名称
- 导演
- 编剧
- 主演
- 类型
通过第一层匹配到的子页面的url进行请求详情页的url数据
(5) 配置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,
}
(6) db.py
-
概述
因为当前需要对详情页面再次请求以获取详情页数据,请求思路和之前requests一样,只是在这里我们使用
yield scrapy.Request()
进行请求 默认请求方式为get -
yield scrapy.Request(url, callback=self.parse_deatil)
-
url:为再次请求的URL地址
-
callback:请求后进行处理的回调方法
-
method: 请求方式
-
callback: 回调函数
-
errback: 报错回调
-
dont_filter: 默认False, 表示"不过滤", 该请求会重新进行发送
-
headers: 请求头
-
cookies: cookie信息
-
-
代码实现
import scrapy from doubandetail.items import DoubandetailItem import re class DbSpider(scrapy.Spider): name = 'db' # 需要注释掉 # allowed_domains = ['movie.douban.com/chart'] 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不符合当前允许,所以会出现不请求的问题
(7) items.py
import scrapy
class DoubandetailItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
name = scrapy.Field() # 电影名称
director = scrapy.Field() # 导演
screenwriter = scrapy.Field() # 编剧
to_star = scrapy.Field() # 主演
type = scrapy.Field() # 类型
(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.html
# useful for handling different item types with a single interface
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()
注意:
如果访问频率过高被禁止访问,可以携带登录后的cooki进行访问
6、下载图片
(1) 安装模块
pip3 install pillow
(2) 抓取网址
https://desk.zol.com.cn/dongman/
(3) 创建工程
-
scrapy startproject desk
-
cd desk
-
scrapy genspider img desk.zol.com.cn/dongman
(4) 配置settings.py
# 设置日志级别
LOG_LEVEL = 'ERROR'
ROBOTSTXT_OBEY = False
# 设置请求头
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'
}
(5) img.py 爬虫代码书写
思路:
抓取到详情页中图片的url地址,交给图片管道进行下载
import scrapy
from urllib.parse import urljoin
class ImgSpider(scrapy.Spider):
name = 'img'
# allowed_domains = ['desk.zol.com.cn/dongman']
start_urls = ['http://desk.zol.com.cn/dongman/']
def parse(self, resp, **kwargs):
# 先抓取到每个图片详情的url
url_list = resp.xpath('//ul[@class="pic-list2 clearfix"]/li/a/@href').extract()
# 获取到url列表后 进行循环进行每一个url详情页的请求
for url in url_list:
# 因为抓取到的url并不完整,需要进行手动拼接
# urljoin('https://desk.zol.com.cn/dongman/', '/bizhi/8301_103027_2.html')
url = urljoin('https://desk.zol.com.cn/dongman/', url)
# 拼凑完发现当前url中有下载exe的url,将其去除
if url.find('exe') != -1:
continue
yield scrapy.Request(url, callback=self.parse_detail)
# 对详情页进行解析
def parse_detail(self, resp):
# 获取当前详情页中最大尺寸图片的url
max_img_url = resp.xpath('//dd[@id="tagfbl"]/a/@href').extract()
# 判断当前最大图片的url地址,为倒数第二个,如果当前图片列表url长度小于2 则当前证明不是图片的url
if len(max_img_url) > 2:
max_img_url = urljoin('https://desk.zol.com.cn/', max_img_url[0])
# 对url页面进行请求 获取最终大图的页面
yield scrapy.Request(max_img_url, callback=self.parse_img_detail)
def parse_img_detail(self, resp):
# 解析出大图的url
img_src = resp.xpath("//img[1]/@src").extract_first()
return {'img_src': img_src}
注意:
如果抓取过程中遇到如下报错,可能是cryptography 版本问题
twisted.web._newclient.ResponseNeverReceived: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', '', 'unsafe legacy renegotiation disabled')]>]
解决:
pip uninstall cryptography
pip install cryptography==36.0.2
(6) 配置图片管道
打开Pipelines文件夹
因为我们不能再像之前存储文本一样,使用之前的管道类(Pipeline),我们需要用到新的存储图片的管道类ImagesPipeline,因此我们需要先导入该类
pipelines.py
-
导入
from scrapy.pipelines.images import ImagesPipeline
-
定义一个Images类
from itemadapter import ItemAdapter from scrapy.pipelines.images import ImagesPipeline import scrapy class Imgspipline(ImagesPipeline): # 1. 发送请求(下载图片, 文件, 视频,xxx) def get_media_requests(self, item, info): # 获取到图片的url url = item['img_src'] # 进行请求 yield scrapy.Request(url=url, meta={"url": url}) # 直接返回一个请求对象即可 # 2. 图片存储路径 def file_path(self, request, response=None, info=None, *, item=None): # 当前获取请求的url的方式有2种 # 获取到当前的url 用于处理下载图片的名称 file_name = item['img_src'].split("/")[-1] # 用item拿到url # file_name = request.meta['url'].split("/")[-1] # 用meta传参获取 return file_name # 3. 可能需要对item进行更新 def item_completed(self, results, item, info): # print('results', results) for r in results: # 获取每个图片的路径 print(r[1]['path']) return item # 一定要return item 把数据传递给下一个管道
(7) 保存数据
接着我们再定义一个保存数据的函数,并设置好存储的文件名,然后存储的路径需要在设置中(setting)文件中,添加IMAGE_STORE设置好存储的路径
开启图片管道
settings.py
ITEM_PIPELINES = {
'desk.pipelines.DeskPipeline': 300,
'desk.pipelines.Imgspipline': 400, # 开启图片管道
}
# 配置存储图片的路径
IMAGES_STORE = './imgs'