+2

Multithreading Selenium, Docker và Django (docker-compose)

Giới thiệu

Phần trước mình có giới thiệu qua cách làm việc với django, selenium và docker. Tuy nhiên do nhu cầu công việc nên chúng ta cần chạy song song giữa selenium và django, cùng với đó ta có thể chạy song song nhiều tab driver. Tuy nhiên cách này mình chỉ chạy được trên firefox. Còn chorme nó chỉ chạy một driver. Ở đây mình sẽ sử dụng Selenium Hub và Docker Hub để chạy nhiều node firefox. Đồng thời mình sử dụng Thread của Python để chạy song song tác vụ selenium và django, nhằm ngăn chặn quá trình trang web bị delay khi chạy selenium. Chúng ta sử dụng ajax để thực hiện hành động từ phía server. Ở đây chúng ta cũng có thể sử dụng channels. Nhưng trong bài viết này mình sẽ không sử dụng channels để thực hiện việc đó

image.png image.png

Cài đặt

Cài đặt project

pip install django
django-admin startproject app
django-admin startapp product

Khởi tạo project

Django

Sau khi khởi tạo project xong.

app
  app
    __init__.py
    asgi.py
    setting.py
    urls.py
    wsgi.py
  product
    __init__.py
    admin.py
    apps.py
    models.py
    test.py
    views.py
manage.py

Chỉnh sửa models.py trong app product Trong file này bạn tạo một model Product.

from django.db import models
from django.contrib.auth.models import User

class Product(models.Model):
    name = models.CharField(max_length=200)
    def __str__(self) -> str:
        return self.name

Tạo 1 file urls.pygetData.py, thư mục templates và trong templates có product.html trong app product

app
  app
    __init__.py
    asgi.py
    setting.py
    urls.py
    wsgi.py
  product
    __init__.py
    admin.py
    apps.py
    getData.py
    models.py
    urls.py
    test.py
    views.py
  templates
    product.html
manage.py

Chỉnh sửa urls.py vừa khởi tạo.

from django.conf import settings
from . import views

app_name = 'product'

urlpatterns = [
    path('', views.home, name ='products'),
    ath('crawl/',  views.getData),
]

Chinh file views.py trong app product. Ở đây chúng ta sử dụng thread để thực hiện lấy dữ liệu.

from django.shortcuts import render
from  django.http import JsonResponse
from .models import *
from .getData import *
from django.shortcuts import render
import threading

def home(request):
    products = Product.objects.all()
    return render(request, 'product.html', {"products": products})

def getDataAjax(request):
    if request.is_ajax():
        data_scrap()

def getData(request):
    thread = threading.Thread(target=getDataAjax,args=[request])
    thread.daemon = True
    thread.start()
    thread.join()
    return JsonResponse({})

Chỉnh sửa urls.py trong app

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('product.urls')),
]

Selenium

Chỉnh sửa file getData.py trong app Product. Tại đây chúng ta sử dụng selenium để thực hiện lấy dữ liệu. Và thread để lưu dữ liệu đã lấy vào database.

import time
from selenium import webdriver
from .models import *
# Import packages
from selenium import webdriver  
from bs4 import SoupStrainer
from bs4 import BeautifulSoup
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from .models import Product
import threading

def data_scrap():
    driver = webdriver.Remote("http://selenium-hub:4444/wd/hub", DesiredCapabilities.FIREFOX)
    driver.get("https://github.com/giakinh0823?tab=repositories")
    time.sleep(2)
    htmlSource = driver.page_source
    only_class = SoupStrainer("div", {"id": "user-repositories-list"})
    list_product = BeautifulSoup(htmlSource, "html.parser", parse_only=only_class)
    for item in list_product.findAll("h3", {"class": "wb-break-all"}):
        name = str(item.find("a", attrs={"itemprop": "name codeRepository"}).text)
        print(name)
        thread = threading.Thread(target=save_product, args=[name])
        thread.daemon=True
        thread.start()
        time.sleep(3)
    driver.quit()

def save_product(name):
    product = Product.objects.create(name =name)
    product.save()

Templates

Chỉnh sửa file product.html trong templates

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>

<body>
    <h1>Crawl data</h1>
    <button id="getdata" onClick="getData()">Get data</button>
    <ul>
       {% for product in products %}
            <li>{{product.name}}</li>
       {% endfor %}
    </ul>
    <script>
        const getDataAjax = () => {
            $.ajax({
                type: "GET",
                url: 'crawl/',
                data: '',
                dataType: 'json',
                success: function (data) {
                    console.log(data)
                }
            });
        }
    </script>

</body>

</html>

Docker

Đầu tiên bạn hãy tạo một file Dockerfile và docker-compose.yml trong project app

app
  app
    __init__.py
    asgi.py
    setting.py
    urls.py
    wsgi.py
  product
    __init__.py
    admin.py
    apps.py
    getData.py
    models.py
    urls.py
    test.py
    views.py
  templates
    product.html
docker-compose.yml
Dockerfile
manage.py

Chỉnh sửa file Dockerfile. Ở đây mình ta ra một thư mục product và coppy requirements.txt vào thư mục đó. Sau đó install requirements.txt và coppy tất cả project của mình vào thư mục product

FROM python:3
ENV PYTHONUNBUFFERED=1
WORKDIR /product
COPY requirements.txt /product/
RUN apt-get update \
    && apt-get -y install libpq-dev gcc
RUN pip install -r requirements.txt
COPY . .

EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Chỉnh sửa file docker-compose.yml. Ở đây ta có 1 database db có image postgres và database này có tên product và username = admin, password = admin. Chúng ta có selenium-hub với GRID_MAX_SESSION=50, trong selenium-hu ta có node-chorme và node-firefox. Với node-firefox ta có NODE_MAX_SESSION = 20. Hai node-chorme và node-firefox có depends_on là selenium-hub. Ta cũng có web có depends_on là db

version: "3.9"

services:
  db:
    image: postgres
    volumes:
      - ./data/db:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=product
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=admin

  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/product
    ports:
      - "8000:8000"
    depends_on:
      - db

  selenium-hub:
    image: selenium/hub
    ports:
      - "4444:4444"
    environment:
        GRID_MAX_SESSION: 50
        GRID_BROWSER_TIMEOUT: 300
        GRID_TIMEOUT: 300

  chrome:
    image: selenium/node-chrome
    depends_on:
      - selenium-hub
    environment:
      HUB_PORT_4444_TCP_ADDR: selenium-hub
      HUB_PORT_4444_TCP_PORT: 4444
      NODE_MAX_SESSION: 1
      NODE_MAX_INSTANCES: 1

  firefox:
    image: selenium/node-firefox
    depends_on:
      - selenium-hub
    environment:
      HUB_PORT_4444_TCP_ADDR: selenium-hub
      HUB_PORT_4444_TCP_PORT: 4444
      NODE_MAX_SESSION: 20
      NODE_MAX_INSTANCES: 20

Tạo và chỉnh sửa file requirements.txt trong project app

app
  app
    __init__.py
    asgi.py
    setting.py
    urls.py
    wsgi.py
  product
    __init__.py
    admin.py
    apps.py
    getData.py
    models.py
    urls.py
    test.py
    views.py
  templates
    product.html
docker-compose.yml
Dockerfile
requirements.txt
manage.py

requirements.txt

asgiref==3.4.1
beautifulsoup4==4.9.3
bs4==0.0.1
certifi==2021.5.30
charset-normalizer==2.0.4
colorama==0.4.4
configparser==5.0.2
crayons==0.4.0
Django==3.2.6
ftfy==6.0.3
idna==3.2
numpy==1.21.1
pandas==1.3.1
Pillow==8.3.1
python-dateutil==2.8.2
pytz==2021.1
requests==2.26.0
selenium==3.141.0
six==1.16.0
soupsieve==2.2.1
sqlparse==0.4.1
urllib3==1.26.6
wcwidth==0.2.5
webdriver-manager==3.4.2
Django>=3.0,<4.0
psycopg2-binary>=2.8
psycopg2==2.9.1

Build và Run project

docker-compose build
docker-compose run web python manage.py makemigrations
docker-compose run web python manage.py migrate
docker-compose up

Sau đó bạn vào http://127.0.0.1:8000/ để xem kết quả

Cảm ơn các bạn đã quan tâm


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.