思路
1. 确定数据结构 item 2. 写爬虫程序 spider ① 每一页的每一个详情页 url ② 翻页 ③ 详情页匹配目标数据 3. 管道处理数据 piplines ① 保存到 excel ② 下载图片 4. 配置设置 settings
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
import scrapy
class BizhiItem(scrapy.Item):
image_urls = scrapy.Field() # 如果要调用 ImagesPipeline 的 get_media_requests并且不重写它的返回值(是一个Requests请求对象列表),必须要用 imag_urls 这个数据名(而且该数据必须传送来一个 list列表)!
image = scrapy.Field() # 暂时用不到 # list类型,用于保存下载的image对象,该字段无需赋值,由scrapy自动完成
如果涉及下列方式下载图片
1.用 ImagesPipeline 的默认方法下载或者不修改 ImagesPipeline 的 get_media_requests的返回值
2.重写 ImagesPipeline 的 get_media_requests 方法,但不修改其返回值(即返回值还是一个 Request请求对象列表)
那么item中存储图片路径字段必须是 list! 即:这里要下载的图片(即使只有一张) url 必须命名为 image_urls 放到 list 中传送给 item
除非重写 ImagesPipeline 的 get_media_requests 的方法的返回值(本是一个请求对象的列表,修改为返回单个Request请求对象即可)
import scrapy
from ..items import BizhiItem
class PhotoSpider(scrapy.Spider):
name = 'photo'
allowed_domains = ['wallpaperscraft.com']
# start_urls = ['http://wallpaperscraft.com']
domain = 'https://wallpaperscraft.com'
page_url = 'https://wallpaperscraft.com/all/page'
page = 1
def start_requests(self):
main_url = 'https://wallpaperscraft.com/all/page1'
yield scrapy.Request(url=main_url, callback=self.parse_page_html)
def parse_page_html(self, response):
detail_urls = response.xpath('/html/body/div[1]/div[2]/div[2]/div/div[2]/div[1]/ul/li/a/@href').extract()
# print(detail_urls)
for detail_url in detail_urls:
detail_url = self.domain + detail_url
print('正在请求详情页:' + detail_url + '...')
yield scrapy.Request(url=detail_url, callback=self.parse_detail_html)
if self.page < 21:
self.page += 1
page_url = self.page_url + str(self.page)
yield scrapy.Request(url=page_url, callback=self.parse_page_html)
def parse_detail_html(self, response):
image_url = response.xpath('/html/body/div[1]/div[2]/div[2]/div/div[2]/div[1]/div[1]/img/@src').extract_first()
print('请求下载: ' + image_url)
yield BizhiItem(image_urls=[image_url])
# 如果要用 ImagesPipeline 的默认方法下载或者不修改 ImagesPipeline 的 get_media_requests的返回值
# 那么这里要下载的图片(即使只有一张) url 必须命名为 image_urls 放到 list 中传送给 item
# 除非重写 ImagesPipeline 的 get_media_requests 的方法的返回值(是一个请求对象的列表)
import os.path
import xlwt
# 保存到 excel
class BizhiPipeline:
def open_spider(self, spider):
self.workbook = xlwt.Workbook()
self.worksheet = self.workbook.add_sheet('sheet1')
self.line_cnt = 0
self.col_name = ['image_urls']
# 写入表头
self.worksheet.write(self.line_cnt, 0, self.col_name[0])
self.line_cnt += 1
def process_item(self, item, spider):
try:
# 写入数据
for i in range(1):
self.worksheet.write(self.line_cnt, i, item[self.col_name[i]][0])
self.line_cnt += 1
self.workbook.save('wall_paper.xls')
return item # 必须加,否则其他管道就无法获得item了!
except Exception as e:
print('写入失败!有残缺数据!已自动跳过!')
from scrapy.pipelines.images import ImagesPipeline
from bizhi import settings # 记得把根目录标记为: 源/根 后导入(此处没用到)
class DownloadImagePipeline(ImagesPipeline):
def get_media_requests(self, item, info):
# 直接推送 request请求(重写了返回值)
# yield scrapy.Request(url=item['img_url'], meta={'folder': item['img_url'].split('/')[-1].split('_')[0],
# 'name': item['img_url'].split('/')[-1]})
request_list = super().get_media_requests(item, info) # 调用父类的方法获得请求对象列表
for request in request_list:
request.item = item # 给 request请求对象 增加一个属性 item,用于在其他函数中可以用 item
# request.item = item 直接给每一个 Request对象 添加一个 item属性;
# request.meta = {"item": item} 这里不行!meta为只读属性!想法是给Requests的meta属性添加值,效果一样,都是为了传参用,但只能是在构造函数中 scrapy.Request(meta=) 才能用,其他地方不允许修改值
return request_list
# 或直接
# urls = ItemAdapter(item).get(self.images_urls_field, [])
# return [Request(u,meta = {'item' : item}) for u in urls] # 加个meta
# 重命名,若不重写这函数,图片名为哈希,就是一串乱七八糟的名字
def file_path(self, request, response=None, info=None):
# category = request.item['image_urls'][0].split('/')[-1].split('_')[0]
# dir_category = os.path.join(settings.IMAGES_STORE,category) # 没必要
# name = request.item['image_urls'][0].split('/')[-1]
# print(name)
# file_path = os.path.join(dir_category, name)
# return file_path
# 在 settings.py 中设置了 IMAGES_STORE = 'D:\img6'后,储存路径就会定位到此处,再加入类别目录即可
category = request.item['image_urls'][0].split('/')[-1].split('_')[0]
name = request.item['image_urls'][0].split('/')[-1]
file_path = os.path.join(category, name)
return file_path
# request.meta = {"item": item} 这里不行!meta为只读属性!
想法是给Requests的meta属性添加值,效果一样,都是为了传参用,
但只能是在构造函数中 scrapy.Request(meta=) 才能用,其他地方不允许修改值
from bizhi import settings # 记得把根目录标记为: 源/根 后导入
原因: 这个报错的意思是:试图在顶级包(top-level package)之外进行相对导入。也就是说相对导入只适用于顶级包之内的模块
由于在"顶层模块"之外引用包,这里用到"顶层模块"的概念,“顶层模块” 是这执行文件同级的文件
from . import XXX
或者
from .. import XXX
时会遇到这样两个错误:
SystemError: Parent module '' not loaded, cannot perform relative impor
和
ValueError: attempted relative import beyond top-level package
其实这两个错误的原因归根结底是一样的:在涉及到相对导入时,package所对应的文件夹必须正确的被python解释器视作package,而不是普通文件夹。否则由于不被视作package,无法利用package之间的嵌套关系实现python中包的相对导入。
文件夹被python解释器视作package需要满足两个条件:
1、文件夹中必须有__init__.py文件,该文件可以为空,但必须存在该文件
2、不能作为顶层模块来执行该文件夹中的py文件(即不能作为主函数的入口 模块的__name__ 不能等于__main__)
很多时候就是导入和当前执行的py文件同级的package中的模块时报错: attempted relative import beyond top-level package
此时,该包作为顶层模块(和执行文件同级),已经不被视为一个package了,需要将他们的父级目录标记为源/根
补充:在"from YY import XX"这样的代码中,无论是XX还是YY,只要被python解释器视作package,就会首先调用该package的__init__.py文件。如果都是package,则调用顺序是YY,XX。
也就是说 你不能在一个x.py 文件中 执行 from .模块名 import * 同时运行 python x.py
另外,练习中“from . import XXX”和“from … import XXX”中的’.‘和’…‘,可以等同于linux里的shell中’.‘和’…'的作用,表示当前工作目录的package和上一级的package。
Pycharm中的解决方案:把根目录标记为: 源/根 后导入(右键相应文件夹,选择 Mark Directory as Sources 即可)
BOT_NAME = 'bizhi'
SPIDER_MODULES = ['bizhi.spiders']
NEWSPIDER_MODULE = 'bizhi.spiders'
ROBOTSTXT_OBEY = False
ITEM_PIPELINES = {
'bizhi.pipelines.BizhiPipeline': 300,
# 'scrapy.pipelines.images.ImagesPipeline':1, #自动保存时使用,引入ImagesPipeline,优先级设为最高
'bizhi.pipelines.DownloadImagePipeline':1,
}
DOWNLOAD_DELAY = 2
RANDOMIZE_DOWNLOAD_DELAY = True
IMAGES_STORE = 'D:\img6'
from scrapy import cmdline
cmdline.execute('scrapy crawl photo'.split(' '))
Scrapy 基础链接: Python爬虫|Scrapy 基础用法