16--Scrapy02:管道
Scrapy02--管道
0. 关于管道
上一节内容,我们已经可以从spider中提取到数据. 然后通过引擎将数据传递给pipeline
那么在pipeline中如何对数据进行保存呢? 主要针对四种数据存储,展开讲解
前三个案例以:https://match.lottery.sina.com.cn/lotto/pc_zst/index?lottoType=ssq&actionType=chzs
最后一个案例以:https://desk.zol.com.cn/dongman/
1. 写入csv文件
写入文件是一个非常简单的事情. 直接在pipeline中开启文件即可
但这里要说明的是,如果只在process_item中进行处理文件是不够优雅的. 总不能有一条数据就open一次吧
class CaipiaoFilePipeline:
def process_item(self, item, spider):
with open("caipiao.txt", mode="a", encoding='utf-8') as f:
# 以追加的模式,写入文件
f.write(f"{item['qihao']},{'_'.join(item['red_ball'])},{'_'.join(item['blue_ball'])}\n")
return item
我们希望的是,能不能打开一个文件,然后就用这一个文件句柄来完成数据的保存?
答案是可以的,可以在pipeline中创建两个方法,一个是open_spider(),另一个是close_spider()
open_spider()
在爬虫开始时,执行一次
close_spider()
在爬虫结束时,执行一次
class CaipiaoFilePipeline:
def open_spider(self, spider):
# 同一个类中,其他方法要使用该变量,可放在对象中
self.f = open("caipiao.txt", mode="a", encoding='utf-8')
def close_spider(self, spider):
if self.f:
self.f.close()
def process_item(self, item, spider):
# 写入文件
self.f.write(f"{item['qihao']},{'_'.join(item['red_ball'])},{'_'.join(item['blue_ball'])}\n")
return item
# 设置settings
ITEM_PIPELINES = {
'caipiao.pipelines.CaipiaoFilePipeline': 300,
}
2. 写入mysql
有了上面的示例,写入数据库其实也就很顺其自然了
首先,在open_spider
中创建好数据库连接,在close_spider
中关闭链接. 在proccess_item
中对数据,进行保存工作
先把mysql相关设置丢到settings里
# MYSQL配置信息
MYSQL_CONFIG = {
"host": "localhost",
"port": 3306,
"user": "root",
"password": "test123456",
"database": "spider",
}
from caipiao.settings import MYSQL_CONFIG as mysql
import pymysql
class CaipiaoMySQLPipeline:
def open_spider(self,spider):
self.conn = pymysql.connect(
host=mysql["host"],
port=mysql["port"],
user=mysql["user"],
password=mysql["password"],
database=mysql["database"]
)
def close_spider(self,spider):
self.conn.close()
def process_item(self,item,spider):
# 写入文件
try:
cursor = self.conn.cursor()
sql = "insert into caipiao(qihao,red,blue) values(%s,%s,%s)"
red = ",".join(item['red_ball'])
blue = ",".join(item['blue_ball'])
cursor.execute(sql,(item['qihao'],red,blue))
self.conn.commit()
spider.logger.info(f"保存数据{item}")
except Exception as e:
self.conn.rollback()
spider.logger.error(f"保存数据库失败!",e,f"数据是: {item}") # 记录错误日志
return item
# 设置settings 开启管道
ITEM_PIPELINES = {
'caipiao.pipelines.CaipiaoMySQLPipeline': 301,
}
3. 写入mongodb
mongodb数据库和mysql如出一辙
# settings.py
MONGO_CONFIG = {
"host": "localhost",
"port": 27017,
#'has_user': True,
#'user': "python_admin",
#"password": "123456",
"db": "python"
}
ITEM_PIPELINES = {
# 三个管道可以共存~
'caipiao.pipelines.CaipiaoFilePipeline': 300,
'caipiao.pipelines.CaipiaoMySQLPipeline': 301,
'caipiao.pipelines.CaipiaoMongoDBPipeline': 302,
}
from caipiao.settings import MONGO_CONFIG as mongo
import pymongo
class CaipiaoMongoDBPipeline:
def open_spider(self,spider):
client = pymongo.MongoClient(host=mongo['host'], port=mongo['port'])
db = client[mongo['db']]
# if mongo['has_user']:
# db.authenticate(mongo['user'], mongo['password'])
self.client = client
self.collection = db['caipiao']
def close_spider(self,spider):
self.client.close()
def process_item(self,item,spider):
self.collection.insert_one({"qihao": item['qihao'],'red': item["red_ball"],'blue': item['blue_ball']})
return item
4. 文件保存
尝试使用Scrapy 来下载一些图片
图片网址: https://desk.zol.com.cn/dongman/
首先,创建好项目,完善spider,注意看 yield scrapy.Request()
import scrapy
from urllib.parse import urljoin
class ZolSpider(scrapy.Spider):
name = 'zol'
allowed_domains = ['zol.com.cn']
start_urls = ['https://desk.zol.com.cn/dongman/']
def parse(self,response,**kwargs): # scrapy自动执行这个parse -> 解析数据
# print(resp.text)
# 1. 拿到详情页的url
a_list = response.xpath("//*[@class='pic-list2 clearfix']/li/a")
for a in a_list:
href = a.xpath("./@href").extract_first()
if href.endswith(".exe"):
continue
# print(response.url) # response.url 从响应对象中,获取当前请求的url
# print(href) # '/bizhi/9109_111583_2.html'
# href = urljoin(response.url, href) # 这个拼接才是没问题的.
# 仅限于scrapy
href = response.urljoin(href) # response.url 和你要拼接的东西
# print(href)
# 2. 请求到详情页. 拿到图片的下载地址
# 发送一个新的请求
# 返回一个新的请求对象
# 我们需要在请求对象中,给出至少以下内容(spider中)
# url -> 请求的url
# method -> 请求方式
# callback -> 请求成功后.得到了响应之后. 如何解析(parse),把解析函数名字放进去
yield scrapy.Request(
url=href,
method="get",
# 当前url返回之后.自动执行的那个解析函数
callback=self.suibianqimignzi,
)
def suibianqimignzi(self,response,**kwargs):
# 在这里得到的响应就是url=href返回的响应
img_src = response.xpath("//*[@id='bigImg']/@src").extract_first()
# print(img_src)
yield {"img_src": img_src}
4.1 URL拼接
# url 拼接的逻辑: 核心就是资源文件路径是相对路径,还是绝对路径
### 总体原则:
当前请求的url (eg: https://desk.zol.com.cn/dongman/aaa) 和 子url,进行拼接
1.若子url是'/bizhi/sss.html' # 绝对路径 以'/' 开头,表示资源路径的根目录
应当和 请求url的域名,进行拼接
eg: 'https://desk.zol.com.cn/' + '/bizhi/sss.html' = 'https://desk.zol.com.cn/bizhi/sss.html'
2.若子url是'bizhi/xxx.html' # 相对路径 是拼接到 当前请求的url中 最后一层目录 的 同级目录中
应当和 将当前请求的url中 最后一层目录 删除后 ,再进行拼接
eg: 'https://desk.zol.com.cn/dongman/aaa' + 'bizhi/sss.html' = 'https://desk.zol.com.cn/dongman/bizhi/sss.html'
### 处理办法: 简单 不用自己判断处理
# 1.通用方案
from urllib.parse import urljoin
urljoin(当前请求的url, 子url)
# 2.scrapy中 响应对象提供url拼接 源码本质就是上面通用方案
response.urljoin(href) # ==> response.url + 要拼接的东西
# response.url: 从响应对象中,获取当前请求的ur
4.2 Request请求对象
### 关于Request()的参数:
url 请求地址
method 请求方式
callback 回调函数
errback 报错回调
dont_filter 默认False # 表示"不过滤",该请求会重新进行发送
headers 请求头
cookies cookie信息
meta 元数据 # 用来存储,其他地方能从 该Request对象中 获取到的数据
4.3 图片下载管道
其次,就是下载,如何在pipeline中下载一张图片呢?
在Scrapy中有一个ImagesPipeline,可以实现自动图片下载功能.
# 先安装 图片处理模块 ImagesPipeline依赖这个图片模块
pip install pillow
import scrapy
from itemadapter import ItemAdapter
# ImagesPipeline 图片专用的管道
from scrapy.pipelines.images import ImagesPipeline
# FilesPipeline 文件下载管道 两者实质和用法 差不多
from scrapy.pipelines.files import FilesPipeline
class TuPipeline:
def process_item(self, item, spider):
print(item['img_src'])
# 一个存储方案:自己发请求 + open 二进制文件
# import requests
# resp = requests.get(item['img_src'])
# with open(f'{(item['img_src'].split('/')[-1]}', 'wb') as f:
# f.write(resp.content)
return item
### scrapy方案: scrapy提供的图片管道
class MyTuPipeline(ImagesPipeline): # 重写下面三个方法
# 1. 发送请求(下载图片,文件,视频,xxx)
def get_media_requests(self, item, info):
url = item['img_src']
yield scrapy.Request(url=url, meta={"sss": url}) # 直接返回一个请求对象即可
# 2. 图片的存储路径 # 在这个过程中. 文件夹自动创建
# return 字符串 返回图片的存储路径
# 完整的路径: settings中的IMAGES_STORE + file_path()的返回值
def file_path(self, request, response=None, info=None, *, item=None):
# 准备文件夹
img_path = "dongman/imgs/kunmo/libaojun/liyijia"
# 准备文件名 根据url来切片 文件名
# 方法1:用响应对象拿url
# file_name = response.url.split("/")[-1]
# 坑: response.url 没办法正常使用 该函数位置,从返回对象获取不到url,默认为None到嘛
# 方法2:用item拿url 可以 但item 在详情页时,一般存放是多个图片的url列表 不是特别精准
# file_name = item['img_src'].split("/")[-1]
# print("item:", file_name)
# 方法3:通过请求对象中参数meta,存放该请求的url 最优方案
file_name = request.meta['sss'].split("/")[-1]
print("meta:", file_name)
real_path = img_path + "/" + file_name # 文件夹路径拼接
return real_path # 返回文件存储路径即可
# 3. item数据处理完(图片下完)后的操作 一般用于对item进行更新 和 下载完文件的信息打印记录
def item_completed(self, results, item, info):
# results:多个请求完(图片下载完)的结果 列表
# eg: [(响应状态:True 或者 False, 一堆数据的对象), (True, 对象), (True, 对象)]
for ok, info in results:
if ok:
print(info['path']) # 图片下载存放的路径
return item # 一定要return item 把数据传递给下一个管道
最后,在settings中设置
LOG_LEVEL = "WARNING"
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/101.0.4951.54 Safari/537.36'
ROBOTSTXT_OBEY = False
ITEM_PIPELINES = {
'tu.pipelines.TuPipeline': 300,
'tu.pipelines.MyTuPipeline': 301,
}
# 在下载文件(图片)时,可能出现302重定向的问题
MEDIA_ALLOW_REDIRECTS = True # 媒体_允许_重定向
# 图片存放的根目录(总路径) 配置
IMAGES_STORE = "./qiaofu"