Скачивайте изображения по абсолютному пути - PullRequest
0 голосов
/ 12 марта 2020
def parse_images(self,response):
    Name = response.meta['Name']
    album = response.meta['Album Name']
    os.makedirs(f'Master/{Name}/{album}',exist_ok=True)
    for ind,image in enumerate(response.xpath('//ul/li/a/img')):
        img = image.xpath('@srcset').extract_first().split(', ')[-1].split()[0] #image URL
        print(img)
        imageName = f'image_{ind+1}'+os.path.splitext(img)[1] #image_1.jpg
        path = os.path.join('Master',Name,album,imageName)
        abs_path = os.path.abspath(path) #Path where I want to download

Как создать конвейер для хранения изображения по абсолютному пути, который я создал, я проверил Загрузка изображений элементов , но не могу найти способ изменить место хранения.

Примечание: я предпочитаю остаться с scrapy и не использовать requests для фактической загрузки изображений

1 Ответ

1 голос
/ 12 марта 2020

Этот пример получает изображения из 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.


РЕДАКТИРОВАТЬ: Тот же пример, но с FilesPipelineFILE_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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...