14、Python Scrapy Web爬虫框架【2】
1、持久化存储
爬取一页糗事百科数据
1.1、爬虫文件中进行数据解析
spiderName.py
import scrapy
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
# 数据解析:解析出段子的作者和内容
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
all_data = []
for div in div_list:
author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
content = div.xpath('./a/div/span//text()').extract()
content = ''.join(content)
1.2、将解析的数据存储到item类型的对象
import scrapy
from qiubaipro.items import QiubaiproItem
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
all_data = []
for div in div_list:
author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
content = div.xpath('./a/div/span//text()').extract()
content = ''.join(content)
#解析出来几个字段,就需要在item类中定义几个属性
# item对象当做是一个字典
item = QiubaiproItem()
item['author'] = author
item['content'] = content
1.3、将item对象提交给管道
import scrapy
from qiubaipro.items import QiubaiproItem
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
all_data = []
for div in div_list:
author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
content = div.xpath('./a/div/span//text()').extract()
content = ''.join(content)
#解析出来几个字段,就需要在item类中定义几个属性
# item对象当做是一个字典
item = QiubaiproItem()
item['author'] = author
item['content'] = content
yield item
1.4、在管道中接收item,对其进行任意形式的持久化存储
1.4.1存储到txt文件中
class QiubaiproPipeline:
fp = None
def open_spider(self,spider):
print('I am open_spider,打开文件执行一次')
self.fp = open('./qiubai.txt','w',encoding='utf-8')
# 会被调用多次
def process_item(self, item, spider):
author = item['author']
content = item['content']
self.fp.write(author + ':'+content)
return item
def close_spider(self,spider):
print('I am close_spider,关闭文件执行一次')
self.fp.close()
1.4.2存储到Mysql数据库中
进入数据库终端
mysql -uroot -p123
创建spider数据库
create database spider;
进入spider库
use spider
创建表
create table qiushibaike(author varchar(100),content varchar(10000));
查看详情
desc qiushibaike;
+---------+----------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+----------------+------+-----+---------+-------+
| author | varchar(100) | YES | | NULL | |
| content | varchar(10000) | YES | | NULL | |
+---------+----------------+------+-----+---------+-------+
import pymysql
class MysqlPipeLine:
conn = None
cursor = None
def open_spider(self, spider):
self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123',db='spider',charset='utf8')
print(self.conn)
# 会被调用多次
def process_item(self, item, spider):
author = item['author']
content = item['content']
self.cursor = self.conn.cursor()
sql = 'insert into qiushibaike values("%s","%s")'%(author,content)
try:
self.cursor.execute(sql)
self.conn.commit()
except Exception as e:
self.conn.rollback()
return item
def close_spider(self, spider):
self.conn.close()
self.cursor.close()
1.4.3存储到Redis数据库中
from redis import Redis
class RedisPipeLine:
conn = None
def open_spider(self, spider):
self.conn = Redis(host='127.0.0.1',port=6379,password='foobared')
# 会被调用多次
def process_item(self, item, spider):
self.conn.lpush('qiubaiData',item)
1.5、在配置文件中开启管道
ITEM_PIPELINES = {
# 300:管道被执行的优先级,数值越小优先级越高,管道类的优先级越高则表示该管道类优先被执行
'qiubaipro.pipelines.QiubaiproPipeline': 300,
'qiubaipro.pipelines.MysqlPipeLine': 301,
'qiubaipro.pipelines.RedisPipeLine': 302,
}
1.6、终端执行命令
scrapy crawl qiubai
1.7、数据查看
Mysql数据库中查询
select * from qiushibaike;
Rdis数据库中查询
问题:什么时候需要用到多个管道类?
实现数据备份的时候。
如何将一组数据持久化存储到不同的载体中呢?
一个管道类表示将一组数据存储到一种形式的载体中。
如果有两个管道类的,爬虫文件提交的item会不会同时提交给这多个管道类?
爬虫文件提交的item只会提交给优先级最高的那一个管道类。
如何将item提交给其他的管道类?
无法实现。却可以将优先级最高的管道类接收到的item对象传递给其他的管道类。
在process_item方法中return item操作表示将item传递给下一个即将被执行的管道类
2、手动请求发送
将多个页码对应的数据进行爬取+解析
- yield关键字在框架中只会被作用到两个地方
- 向管道提交item
- 手动发起请求
- yield scrapy.Reqeust(url,callback):
import scrapy
from qiubaipro.items import QiubaiproItem
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
start_urls = ['https://www.qiushibaike.com/text/']
url_model = 'https://www.qiushibaike.com/text/%d/'
page_num = 2
def parse(self, response):
div_list = response.xpath('//*[@id="content"]/div/div[2]/div')
all_data = []
for div in div_list:
author = div.xpath('./div[1]/a[2]/h2/text()').extract_first()
content = div.xpath('./a/div/span//text()').extract()
content = ''.join(content)
# 解析出来几个字段,就需要在item类中定义几个属性
# item 对象当做是一个字典
item = QiubaiproItem()
item['author'] = author
item['content'] = content
yield item
# 手动请求代码
if self.page_num < 6: #结束递归的条件
url = format(self.url_model % self.page_num)
self.page_num += 1
yield scrapy.Request(url=url, callback=self.parse)
start_urls列表如何帮我们自动进行get请求发送
#父类的一个方法,会被默认执行
#自动调用了一个start_reqeusts的方法,方法的模拟实现如下:
def start_requests(self):#模拟实现该方法的原始实现
for url in self.start_urls:
yield scrapy.Request(url,callback=self.parse)
如何手动发起post请求:
-
yield scrapy.FormRequest(url,formdata,callback)
-
如何让start_urls的列表元素发起post请求
-
重写start_reqeusts方法。
formdata:请求的参数
-
def start_requests(self):#模拟实现该方法的原始实现
print('i am start_requests()')
for url in self.start_urls:
yield scrapy.FormRequest(url,formdata,callback=self.parse)
-
3、请求传参
爬取4567前5页电影名称+电影描述
import scrapy
from moviepro.items import MovieproItem
class MovieSpider(scrapy.Spider):
name = 'movie'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.4567kan.com/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html']
url_model = 'https://www.4567kan.com/index.php/vod/show/class/动作/id/1/page/%d.html'
page_num = 2
def parse(self, response):
li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
for li in li_list:
item = MovieproItem()
title = li.xpath('./div/a/@title').extract_first()
item['title'] = title
detail_url = 'https://www.4567kan.com' + li.xpath('./div/a/@href').extract_first()
# 对详情页的url发起请求
# meta是一个字典,可以在请求的过程中将meta传递给callback
yield scrapy.Request(detail_url, callback=self.parse_detail, meta={'item': item})
if self.page_num < 6:
new_url = format(self.url_model % self.page_num)
self.page_num += 1
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
- 作用:可以帮助scrapy实现数据的深度爬取
- 深度爬取:爬取的数据没有存在于同一张页面
- 效果:可以让爬虫文件中多个解析的方法公用同一个item对象
- 传递item:yield scrapy.Request(url,callback,meta={'xxx':xxx})
- 接受item:response.meta['xxx']
- 配置文件:
- CONCURRENT_REQUESTS = 32 #表示框架开启的线程数量,默认开启16个
- yield关键字在框架中只会被作用到两个地方
- 向管道提交item
- 手动发起请求
4、五大核心组件
- 为后续的分布式爬虫做铺垫
引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心)
调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
结构流程图:
5、中间件的基本操作
-
中间件的作用:
- 批量拦截请求和响应
-
中间件的分类:
- 爬虫中间件
- 下载中间件【重点】
-
下载中间件:
-
拦截请求干什么?
# 拦截请求 # 参数request就是拦截到所有的请求 def process_request(self, request, spider): print('拦截的请求是:', request.url) # 可进行UA伪装 request.headers['User_Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36' # 可设置cookie # request.cookie = 'asdfasdgadfavv' return None
- 请求头的伪装
- 在中间件进行UA伪装和在配置文件进行的UA伪装区别:
- 配置文件的UA伪装是全局,可以将所有的请求设置成一个UA
- 中间件是可以将每一个请求都设置成不同的UA
- 在中间件进行UA伪装和在配置文件进行的UA伪装区别:
- 代理设置
# 拦截发生异常的请求对象 # 参数request就是拦截到异常的请求对象 # 拦截异常的请求后需要对其修正,让其成为一个正常的请求,让其重新发送 def process_exception(self, request, exception, spider): print('拦截到异常的请求对象为:', request.url) # 建议将代理的设置写到方法内部 # request.meta['proxy'] = 'https://ip:port' return request#将修正后的请求进行重新发送
- 请求头的伪装
-
拦截响应干什么?
- 篡改响应数据
# 拦截响应 # request拦截到响应对应的请求对象 # response拦截到的响应对象 def process_response(self, request, response, spider): print('拦截到的响应对象是:', response) return response
-
为什么:
-
如果请求到的数据是不满足需求的数据,则就需要进行响应数据的篡改。
-
什么是不满足需求的响应数据?
- 动态加载的数据。
-
-
开启下载中间件settings.py中配置
SPIDER_MIDDLEWARES = {
'middlepro.middlewares.MiddleproSpiderMiddleware': 543,
}