CoderCastrov logo
CoderCastrov
Парсинг

Как я собрал 1,5 миллиона изображений и текст страниц с помощью Scrapy, Digital Ocean Spaces(S3) и Rotating Proxies

Как я собрал 1,5 миллиона изображений и текст страниц с помощью Scrapy, Digital Ocean Spaces(S3) и Rotating Proxies
просмотров
5 мин чтение
#Парсинг
Table Of Content

Применение:

У меня было требование извлечь данные и изображения с веб-сайта, на котором было 100 000 страниц и примерно 500 ГБ изображений. Метод должен был быть экономичным и эффективным.

Предложенное решение:

Я решил использовать Scrapy, который является открытым фреймворком на языке Python для парсинга веб-сайтов.

Я выбрал Digital Ocean для настройки виртуальной машины и Digital Ocean Spaces в качестве хранилища данных для загрузки изображений, чтобы клиент мог их позже скачать.

Я выбрал StormProxies в качестве решения для прокси, так как оно экономично и предоставляет неограниченное количество запросов за небольшую плату.

Реализация:

Спецификация виртуальной машины Digital Ocean:

ОС: Ubuntu 18.04 LTS

Размер: Стандартный, 4vCPU, 4 ГБ ОЗУ

DigitalOcean - Облачная платформа для разработчиков

Помогает миллионам разработчиков легко создавать, тестировать, управлять и масштабировать приложения любого размера - быстрее, чем когда-либо...

cloud.digitalocean.com

Хранилище: Подписка на DigitalOcean Spaces (5 долларов за 250 ГБ в месяц)

Создайте пространство внутри вашей подписки на DigitalOcean и запишите его название. Мы будем напрямую сохранять изображения в DigitalOcean Spaces. Scrapy совместим с любым хранилищем S3, поэтому DigitalOcean Spaces подходит без необходимости переписывать код.

Подписка на Storm Proxies:

40 потоков/1 IP с неограниченными запросами (39 долларов в месяц). Действительно экономичный вариант по сравнению с другими провайдерами.

https://stormproxies.com/

Код Scrapy:

import scrapy
from ..items import MyImage
import base64

class HubSpider(scrapy.Spider):
    name = 'spider_sample'
    allowed_domains = ['Allowed_Domain']
    start_urls = ['URL_of_Website']

    def parse(self, response):
        links = response.css('#masonry .bold a::attr(href)').extract()
        for link in links:
            url = response.urljoin(link)
            yield scrapy.Request(url=url, callback=self.parse_data)
        next_page = response.css('.pagination a[title*="Next Page"]::attr(href)').extract_first()
        if next_page:
            next_url = response.urljoin(next_page)
            yield scrapy.Request(url=next_url, callback=self.parse)

    def parse_data(self, response):
        image_data = MyImage()
        image_data["Creator"] = response.css('.pull-left span::text').extract_first()
        image_data["Name"] = ''.join(response.css('.well-inline h2::text').extract()).strip()
        a_img = ["NA"]
        img_urls = response.css('a[rel="abcd"]::attr(href)').extract_first()
        img_urls = response.urljoin(img_urls)
        a_img[0] = img_urls
        titles = response.css('a[rel="abcd"]::attr(title)').extract()
        # img_urls = [response.urljoin(i) for i in img_urls]
        image_data["image_urls"] = a_img
        # image_data["title"] = titles
        image_data["Description"] = ''.join(response.xpath('//*[@id="downloadDescription"]//text()').extract()).strip()
        try:
            image_names = img_urls.split("/")[-1]
        except IndexError:
            image_names = image_data["Creator"]
        # image_names = [i.split("/")[-1] for i in img_urls]
        try:
            image_data["category2"] = response.css('.breadcrumb li a::text').extract()[1]
            image_data["category3"] = response.css('.breadcrumb li a::text').extract()[2]
        except IndexError:
            image_data["category2"] = "NA"
            image_data["category3"] = "NA"
        image_data["category4"] = response.css('.hidden-phone::text').extract_first(default="NA")
        image_data["abcd"] = response.css('.abcd .well-small p img::attr(alt)').extract() + response.css('.abcd::text').extract()
        try:
            image_data["Downloads"] = response.css('.noborder .well-small .font-large *::text').extract()[1]
        except IndexError:
            image_data["Downloads"] = "NA"
        tags = response.css('#threadtagsarea a::text').extract()
        tags = ["#"+i for i in tags]
        image_data["Tags"] = tags if tags else "NA"
        data_1 = response.css('.abcd .well-small p span[class="bold"]::text').extract()
        info_box = {}
        for i in range(len(data_1)):
            info_box[data_1[i]] = response.css('.abcd well-small em a::text').extract()[i]
        image_data["ABCD"] = abcd if info_box else "NA"
        # image_data["image_name"] = titles
        image_data["Image_Names"] = image_names
        image_data["Link"] = response.url
        yield image_data

settings.py файл в Scrapy (оптимизирован для скорости и параллельности)

# -*- coding: utf-8 -*-

# Scrapy settings for image_downloader project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'image_downloader'

SPIDER_MODULES = ['image_downloader.spiders']
NEWSPIDER_MODULE = 'image_downloader.spiders'


# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'image_downloader (+http://www.yourdomain.com)'
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'

# Obey robots.txt rules
# ROBOTSTXT_OBEY = True

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 0.25
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
#COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#   'Accept-Language': 'en',
#}

# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    'image_downloader.middlewares.ImageDownloaderSpiderMiddleware': 543,
#}

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    'image_downloader.middlewares.ImageDownloaderDownloaderMiddleware': 543,
#}

# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
#}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
#    'image_downloader.pipelines.ImageDownloaderPipeline': 300,
#}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'


ITEM_PIPELINES = {'image_downloader.pipelines.ImageDownloaderPipeline': 300}
# 'scrapy.pipelines.images.ImagesPipeline': 1,

#DOWNLOAD_HANDLERS = {
#    'data': 'scrapy.core.downloader.handlers.datauri.DataURIDownloadHandler',
#}

REACTOR_THREADPOOL_MAXSIZE = 15
DOWNLOAD_TIMEOUT = 60

ROTATING_PROXY_LIST = [
    'Proxy_IP_from_stormproxy_1',
    'Proxy_IP_from_stormproxy_2',
    # ...
]

DOWNLOADER_MIDDLEWARES = {
    # ...
    'rotating_proxies.middlewares.RotatingProxyMiddleware': 610,
    'rotating_proxies.middlewares.BanDetectionMiddleware': 620,
    # ...
}

IMAGES_STORE = 's3://sims4amit/images/' # Это имя пространства, созданного в подписке DigitalOcean Spaces
# IMAGES_STORE = 'C:/Users/amag/Downloads/scrapy-master/image_downloader/downloads'
AWS_ACCESS_KEY_ID = "Access_key_id_digital_ocean"
AWS_SECRET_ACCESS_KEY = "secret_key_digital_ocean"
AWS_ENDPOINT_URL = 'https://nyc3.digitaloceanspaces.com'
AWS_REGION_NAME = "nyc3"
AWS_USE_SSL = False
AWS_VERIFY = False


# Obey robots.txt rules
ROBOTSTXT_OBEY = False

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

pipelines.py файл

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import scrapy
from scrapy.pipelines.images import ImagesPipeline
import os
from urllib.parse import urlparse


class ImageDownloaderPipeline(ImagesPipeline):

    def file_path(self, request, response=None, info=None):
        return 'files/' + os.path.basename(urlparse(request.url).path)

items.py файл (Пользовательский пайплайн для элементов)

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class MyImage(scrapy.Item):
    # title = scrapy.Field()
    # createDate = scrapy.Field()
    image_urls = scrapy.Field()
    # images = scrapy.Field()
    Creator = scrapy.Field()
    Name = scrapy.Field()
    Description = scrapy.Field()
    category2 = scrapy.Field()
    category3 = scrapy.Field()
    category4 = scrapy.Field()
    abcd = scrapy.Field()
    Downloads = scrapy.Field()
    Tags = scrapy.Field()
    Image_Names = scrapy.Field()
    abcd = scrapy.Field()
    Link = scrapy.Field()
    # image_name = scrapy.Field()

Я клонировал репозиторий GitHub на виртуальную машину DigitalOcean и добавил IP-адрес в список разрешенных на подписке Storm Proxies. Вы можете запустить код Scrapy в сеансе screen на Linux VM, чтобы процесс не прерывался.

Вот команда для запуска паука Scrapy:

scrapy crawl ImageDownloader -o output.csv — logfile logs.txt

Изображения будут сохранены в подписке DigitalOcean Spaces и будут доступны для загрузки позже.

Весь этот набор обходится менее чем за 10 долларов! (если прорейтить подписку на Storm Proxies). Мой скрэпер выполнился за 20 часов.

Вы можете найти полный код по ссылке https://github.com/amit1411/ImageDownloader