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 đó
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.py và getData.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