Этот пример получает изображения из http://books.toscrape.com/
и использует pipeline
для добавления в подпапки, используя первый символ имени файла.
I настройки Я устанавливаю путь к Master
Это может быть относительный
'IMAGES_STORE': 'Master',
или абсолютный путь
'IMAGES_STORE': '/full/path/to/Master',
Эта папка должна существовать до запуска кода. Если не существует, pipeline
не создаст его и не загрузит. Но pipeline
автоматически создаст подпапки, поэтому вам не понадобится makedirs()
.
В parser
Я добавлю name
и album
в item
, поэтому эти значения будут отправлены в pipeline
def parse (self, response): print ('url:', response.url)
#open_in_browser(response) # to see url in web browser
# download images and convert to JPG (even if it is already JPG)
for url in response.css('img::attr(src)').extract():
url = response.urljoin(url)
image = url.rsplit('/')[-1] # get first char from image name
yield {'image_urls': [url], 'name': 'books', 'album': image[0]}
В pipeline
в get_media_requests()
Я получаю значения от item
и введите meta
, чтобы отправить его на file_path
, который генерирует локальный путь для файла (в IMAGES_STORE
).
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
# send `meta` to `file_path()`
yield scrapy.Request(image_url, meta={'name': item['name'], 'album': item['album']})
В pipeline
в full_path()
Я получаю значения из meta
и, наконец, я создаю путь name/album/image.jpg
. Первоначально pipeline
использовать hashcode
в качестве имени файла
def file_path(self, request, response=None, info=None):
# get `meta`
name = request.meta['name']
album = request.meta['album']
image = request.url.rsplit('/')[-1]
#print('file_path:', request.url, request.meta, image)
return '%s/%s/%s' % (name, album, image)
И это сохраняет изображение в IMAGES_STORE/name/album/image.jpg
Минимальный рабочий пример.
Вы можете поставить все код в один файл и запустить его как обычный скрипт - python script.py
- без создания scrapy
проекта. Таким образом, каждый может легко протестировать этот код.
import scrapy
from scrapy.pipelines.files import FilesPipeline
from scrapy.pipelines.images import ImagesPipeline
#from scrapy.commands.view import open_in_browser
#import json
class MySpider(scrapy.Spider):
name = 'myspider'
#allowed_domains = []
# see page created for scraping: http://toscrape.com/
start_urls = ['http://books.toscrape.com/'] #'http://quotes.toscrape.com']
def parse(self, response):
print('url:', response.url)
#open_in_browser(response) # to see url in web browser
# download images and convert to JPG (even if it is already JPG)
for url in response.css('img::attr(src)').extract():
url = response.urljoin(url)
image = url.rsplit('/')[-1] # get first char from image name
yield {'image_urls': [url], 'name': 'books', 'album': image[0]}
# --- pipelines ---
import os
# --- original code --- # needed only if you use `image_guid`
#import hashlib
#from scrapy.utils.python import to_bytes
# --- original code ---
class RenameImagePipeline(ImagesPipeline):
'''Pipeline to change file names - to add folder name'''
def get_media_requests(self, item, info):
# --- original code ---
#for image_url in item['image_urls']:
# yield scrapy.Request(image_url)
# --- original code ---
for image_url in item['image_urls']:
# send `meta` to `file_path()`
yield scrapy.Request(image_url, meta={'name': item['name'], 'album': item['album']})
def file_path(self, request, response=None, info=None):
# --- original code ---
#image_guid = hashlib.sha1(to_bytes(request.url)).hexdigest()
#return 'full/%s.jpg' % (image_guid,)
# --- original code ---
# get `meta`
name = request.meta['name']
album = request.meta['album']
image = request.url.rsplit('/')[-1]
#image_guid = hashlib.sha1(to_bytes(request.url)).hexdigest()
print('file_path:', request.url, request.meta, image) #, image_guid)
#return '%s/%s/%s.jpg' % (name, album, image_guid)
return '%s/%s/%s' % (name, album, image)
# --- run without project and save in `output.csv` ---
from scrapy.crawler import CrawlerProcess
c = CrawlerProcess({
'USER_AGENT': 'Mozilla/5.0',
# save in file CSV, JSON or XML
'FEED_FORMAT': 'csv', # csv, json, xml
'FEED_URI': 'output.csv', #
# download images to `IMAGES_STORE/full` (standard folder) and convert to JPG (even if it is already JPG)
# it needs `yield {'image_urls': [url]}` in `parse()` and both ITEM_PIPELINES and IMAGES_STORE to work
#'ITEM_PIPELINES': {'scrapy.pipelines.images.ImagesPipeline': 1}, # used standard ImagePipeline (download to IMAGES_STORE/full)
'ITEM_PIPELINES': {'__main__.RenameImagePipeline': 1}, # used Pipeline create in current file (needs __main___)
#'IMAGES_STORE': '/full/path/to/valid/dir', # this folder has to exist before downloading
'IMAGES_STORE': 'Master', # this folder has to exist before downloading
})
c.crawl(MySpider)
c.start()
BTW: Используя
import scrapy
print(scrapy.__file__)
, вы можете найти исходный код и посмотреть, как он выглядит в оригинал ImagePipeline
. В полном примере выше я поместил некоторый оригинальный код в комментарии.
Вкл. Linux У меня есть
/usr/local/lib/python3.7/dist-packages/scrapy/
и
/usr/local/lib/python3.7/dist-packages/scrapy/pipelines/images.py
/usr/local/lib/python3.7/dist-packages/scrapy/pipelines/files.py
Кстати : ImagePipeline
сжимает все изображения в JPG
- событие, если вы загружаете JPG
. Если вы хотите сохранить исходное изображение, вам может потребоваться FilePipeline
вместо ImagePipeline
. И FILE_STORE
вместо IMAGE_STORE
.
КСТАТИ: Иногда возникает проблема с Pipeline
, поскольку он не отображает сообщения об ошибках (scrapy
перехватывает ошибки и не не отображается), поэтому трудно распознать ошибку кода в Pipeline
.
РЕДАКТИРОВАТЬ: Тот же пример, но с FilesPipeline
(и FILE_STORES
и item['file_urls']
).
Я добавил комментарии с фразой "instead of"
, чтобы показать различия.
import scrapy
from scrapy.pipelines.files import FilesPipeline
from scrapy.pipelines.images import ImagesPipeline
#from scrapy.commands.view import open_in_browser
#import json
class MySpider(scrapy.Spider):
name = 'myspider'
#allowed_domains = []
# see page created for scraping: http://toscrape.com/
start_urls = ['http://books.toscrape.com/'] #'http://quotes.toscrape.com']
def parse(self, response):
print('url:', response.url)
#open_in_browser(response) # to see url in web browser
# download all types of files (without converting images to JPG)
for url in response.css('img::attr(src)').extract():
url = response.urljoin(url)
image = url.rsplit('/')[-1] # get first char from image name
#yield {'image_urls': [url], 'name': 'books', 'album': image[0]}
yield {'file_urls': [url], 'name': 'books', 'album': image[0]} # <--- file_urls instead of image_urls
# --- pipelines ---
import os
#class RenameImagesPipeline(ImagesPipeline):
class RenameFilesPipeline(FilesPipeline): # <-- FilesPipeline instead of ImagesPipeline
'''Pipeline to change file names - to add folder name'''
def get_media_requests(self, item, info):
#for image_url in item['image_urls']:
for image_url in item['file_urls']: # <--- file_urls instead of image_urls
# send `meta` to `file_path()`
yield scrapy.Request(image_url, meta={'name': item['name'], 'album': item['album']})
def file_path(self, request, response=None, info=None):
# get `meta`
name = request.meta['name']
album = request.meta['album']
image = request.url.rsplit('/')[-1]
print('file_path:', request.url, request.meta, image)
return '%s/%s/%s' % (name, album, image)
# --- run without project and save in `output.csv` ---
from scrapy.crawler import CrawlerProcess
c = CrawlerProcess({
'USER_AGENT': 'Mozilla/5.0',
# save in file CSV, JSON or XML
'FEED_FORMAT': 'csv', # csv, json, xml
'FEED_URI': 'output.csv', #
# --- images ---
# download images to `IMAGES_STORE/full` (standard folder) and convert to JPG (even if it is already JPG)
# it needs `yield {'image_urls': [url]}` in `parse()` and both ITEM_PIPELINES and IMAGES_STORE to work
#'ITEM_PIPELINES': {'scrapy.pipelines.images.ImagesPipeline': 1}, # used standard ImagesPipeline (download to IMAGES_STORE/full)
#'ITEM_PIPELINES': {'__main__.RenameImagesPipeline': 1},
#'IMAGES_STORE': '/full/path/to/valid/dir', # this folder has to exist before downloading
# --- files ---
# download files to `FILES_STORE/full` (standard folder) (without converting images)
# it needs `yield {'file_urls': [url]}` in `parse()` and both ITEM_PIPELINES and FILES_STORE to work
#'ITEM_PIPELINES': {'scrapy.pipelines.files.FilesPipeline': 1}, # used standard FilesPipeline (download to FILES_STORE/full)
'ITEM_PIPELINES': {'__main__.RenameFilesPipeline': 1}, # <--- RenameFilesPipeline instead of RenameImagesPipeline
'FILES_STORE': 'Master', # this folder has to exist before downloading # <--- FILES_STORE instead of IMAGES_STORE
})
c.crawl(MySpider)
c.start()