3
\$\begingroup\$

I made a project, that scrapes images asynchronously and saves them in container. I have access to them through volume. Scrapy finds images on given web page.

Any tips will be good. But first I would like to focus on docker-compose I would appreciate tips on how to improve it.

files tree

My project looks like that. celery.py for celery config, scrap_images directory for scrapy project. finals images are stored in pictures in one possible directory

.
├── celery_test.py
├── docker-compose.yaml
├── image_collector
│   ├── celery.py
│   ├── ENV_FILE
│   ├── image_collector   # overlaping
│   ├── pictures
│   │   ├── big
│   │   ├── medium
│   │   ├── small
│   │   └── tiny
│   ├── pipeline.log     # scrapy pipeline log
│   ├── scrap_images
│   │   ├── custom_loger.py
│   │   ├── __init__.py
│   │   ├── items.py
│   │   ├── middlewares.py
│   │   ├── pipelines.py
│   │   ├── settings.py
│   │   └── spiders
│   │       ├── image_spider.py
│   │       └── __init__.py
│   ├── scrapy.cfg
│   ├── spider.log   # scrapy spider log
│   └── tasks.py
├── README.md
├── requirements.txt
└── Worker

docker-compose.yaml

I had problem with path, so in docker-compose in Worker I got 2 volumes that overlap to start celery and scrapy from same workdir. I start celery with docker command, and withing same workdir i start scrapy spiders when task is received

version: '3.3'

services:
  worker:
    build:
      context: .
      dockerfile: Worker
    image: collector_worker:3.8.5
    environment:
      - HOSTNAME=broker
      - PORT=5672
      - DB_ACCES_NAME=postgres_db
    env_file:
      - ./image_collector/ENV_FILE
    volumes:
      - ./image_collector:/app/image_collector
      - ./image_collector:/app
    depends_on:
      - broker
    restart: always
    command: bash -c "mkdir -p pictures && chmod -R 777 pictures && celery -A image_collector worker --loglevel=info --autoscale=5,1"

  broker:
    image: rabbitmq:3.6.6-management
    hostname: broker
    restart: always
    environment:
      - RABBITMQ_DEFAULT_USER=admin
      - RABBITMQ_DEFAULT_PASS=mypass
    ports:
      - "5673:5672"
      - "15673:15672"

Worker

Container that runs celery, and starts spiders in current workdir position

FROM python:3.8.5

ENV PYTHONUNBUFFERED 1

COPY requirements.txt /app/requirements.txt

WORKDIR /app

RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen

RUN pip install -r requirements.txt

celery.py

Celery configuration to connect with rabbit broker

from __future__ import absolute_import, unicode_literals
from celery import Celery
import os

user = os.getenv('LOGIN', 'admin')
password = os.getenv('PASSWORD', 'mypass')
hostname = os.getenv('HOSTNAME', 'localhost')
port = os.getenv('PORT', '5673')

broker_url = f'amqp://{user}:{password}@{hostname}:{port}'
app = Celery("tasks", broker=broker_url, namespace="image_celery", include=['image_collector.tasks'])

__all__ = ("app",)


Tasks.py

I start spiders with system command and pass url to scrap that page and process in pipeline

#from scrapy.crawler import CrawlerProcess
#from .scrap_images.spiders.image_spider import ImageSpider
#from scrapy.cmdline import execute
from .celery import app

import sys
import os


@app.task
def start_spider(url):
    """Task for crawling web"""
    os.system(f"python -m scrapy crawl image_spider -a url={url}")


if __name__ == "__main__":
    url = r'https://www.wykop.pl/'
    start_spider(url)

celery_test.py

i send tasks to celery manuall at this moment, nothing special

from image_collector.tasks import start_spider

for pages in range(1000):
    url = f'https://memy.jeja.pl/nowe,0,0,3{2216 - pages:>04}.html'
    start_spider.delay(url=url)

image_spider.py

import scrapy
from ..items import ImageItem
from ..custom_loger import define_logger


class ImageSpider(scrapy.Spider):
    name = 'image_spider'
    my_logger = define_logger("spider")

    # allowed_domains = ['www.wikipedia.pl']

    def start_requests(self):
        url = getattr(self, "url")

        if url:
            self.my_logger.debug(f"Starting image parsing at: {url}")
            yield scrapy.Request(url=url, callback=self.find_images)
        else:
            self.my_logger.error(f"URL is empty")
            yield None

    def parse(self, response):
        print(f"Parsing: {self.url}")

    def find_images(self, response):
        images = response.css("img::attr(src)").extract()
        item = ImageItem()
        item['image_urls'] = []
        item['title'] = response.css("title::text").extract()[0]
        item['title'] = response.xpath("//title/text()").extract()[0]

        self.my_logger.info(f"Found :{len(images)} images")
        for img in images:
            img = str(img)
            im_url = img if img.startswith("http") else "http:" + img

            item['image_urls'].append(im_url)
            self.my_logger.debug(f"Got image: {im_url}")

        yield item

    # def save_images(self, response):
    #     image = ImageItem()
    #     self.my_logger.info(f"Got image: {response.url}")

    def err_hanlder(self, failure):
        url = failure.request.url
        callback = failure.request.callback
        errback = failure.request.errback  # should work same way as callback... ?
        # status = failure.value.response.status
        self.my_logger.error(f"Fail request: @: {url}")
```
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.