Stack: Bun >= 1.2.5 · TypeScript · Elysia v1.2 · Puppeteer + Rebrowser + Stealth · Mongoose/Typegoose · MongoDB · @elysiajs/cron
Service name:search-tool
Default port:33033
Version:2.0.6
| Công cụ | Phiên bản tối thiểu |
|---|---|
| Bun | >= 1.2.5 |
| Node.js | 20+ (dùng khi Puppeteer cần) |
| Docker & Docker Compose | Latest |
| Google Chrome | Installed system-wide |
Lưu ý: Dự án ưu tiên chạy bằng Bun runtime thay vì Node.js thuần. Một số tính năng Chrome launch dùng chrome-launchernên Chrome phải được cài trên máy.
.env: Chỉnh sửa theo hướng dẫn mục 1.3.http://localhost:33033GET http://localhost:33033/
# Trả về: { name: "Search tool", version: "2.0.6", environment: "development", browserSession: 0 }
GET http://localhost:33033/health
# Trả về: { status: "ok", time: "...", uptime: ... }
GET http://localhost:33033/swagger
# Swagger UI tự động tạo từ Elysia.env)| Biến | Ví dụ | Mô tả |
|---|---|---|
PORT | 33033 | Port HTTP server lắng nghe |
NODE_ENV | development | Môi trường chạy (development / production) |
API_KEY | #!6f1!0... | API Key bảo vệ các endpoint nội bộ |
IP_ALLOWED | 127.0.0.1,11.0.3.11 | Danh sách IP được phép gọi (whitelist) |
WEBHOOK_API_KEY | 255d00cc... | Secret key xác thực webhook callback từ API server |
GET_PROXY_API | http://127.0.0.1:33034/api/get-proxy | URL endpoint để lấy proxy từ API server |
PROXY_API_KEY | (nếu cần) | API key cho endpoint GET_PROXY_API |
DB_MONGO_HOST | search_tool_mongodb | Host MongoDB |
DB_MONGO_PORT | 27017 | Port MongoDB |
DB_MONGO_USERNAME | root | Username MongoDB |
DB_MONGO_PASSWORD | PwDev123 | Password MongoDB |
DB_MONGO_DATABASE | search-tool-tools | Tên database MongoDB |
CRON_SEARCH_LIMIT | 10 | Số tác vụ search chạy đồng thời tối đa |
CRON_TASK_LIMIT | 14 | Số tác vụ proxy check chạy đồng thời tối đa |
BROWSER_LIMIT_PER_PROCESS | 1 | Số browser session mỗi process |
BROWSER_LIMIT | 22 | Tổng số Chrome sessions tối đa. Công thức mặc định: (CRON_SEARCH_LIMIT + CRON_TASK_LIMIT) * BROWSER_LIMIT_PER_PROCESS |
SEARCH_API_KEY | (cần điền) | API key cho searchapi.io |
SEARCH_API_URL | https://searchapi.io/api/v1/search | URL của Search API bên ngoài |
LOG_LEVEL | info | Mức log: error, warn, info, debug |
LOG_SERVICE | app | Tên service mặc định trong log |
LOG_TIMESTAMP | true | Có gắn timestamp vào log hay không |
LOG_COLORIZED | true | Có format màu cho console log |
LOG_WRITE_TO_FILE | true | Có ghi log ra file không |
LOG_WRITE_TO_CONSOLE | true | Có xuất log ra console không |
LOG_DIR | storage/log | Thư mục chứa log files |
RUNTIME | bun | Runtime sử dụng (bun hoặc node) |
searchesLưu danh sách task tìm kiếm keyword Google cần thực hiện.
| Field | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
_id | String (ObjectID hex) | Yes | ID task |
keyword | String | Yes | Từ khoá cần tìm kiếm |
result_num | Number | Yes | Số kết quả tối đa cần lấy |
device | Number | Yes | Thiết bị: 0 = Desktop, 1 = Mobile |
device_os | Number | Yes | OS: 0 = Windows/Android, 1 = MacOS/iOS |
engine | Number | Yes | Công cụ tìm kiếm (xem enum Engine) |
webhook_url | String (URI) | Yes | URL để gửi kết quả về sau khi hoàn thành |
proxy_url | String (URI) | Yes | URL proxy để gọi lấy proxy từ API server |
call_webhook_count | Number | No | Số lần đã gọi webhook, mặc định 0 |
proxy | Object | No | Thông tin proxy đang dùng: {host, port, username, password} |
search_data | Object | No | Kết quả tìm kiếm (xem Search Data bên dưới) |
status | Number | No | 0 = PENDING, 1 = PROCESSING, 2 = COMPLETED |
result_status | Number | No | 0 = UNKNOWN, -1 = ERROR, -2 = NOT_FOUND, -3 = CAPTCHA, 1 = SUCCESS |
search_youtube | Boolean | No | Tìm kiếm kết quả YouTube, mặc định true |
search_image | Boolean | No | Tìm kiếm kết quả hình ảnh, mặc định true |
geo_location | Object | No | Vị trí địa lý: {latitude, longitude} |
retry_count | Number | No | Số lần retry do lỗi thông thường, mặc định 0 |
captcha_retry_count | Number | No | Số lần retry do captcha, mặc định 0 |
device_os_retry_count | Number | No | Số lần retry do thay đổi OS, mặc định 0 |
createdAt | Datetime | Auto | Thời điểm tạo (Typegoose TimeStamps) |
updatedAt | Datetime | Auto | Thời điểm cập nhật |
search_data:{
"ads": [
{ "url": "https://...", "rank": 1 }
],
"search": [
{ "url": "https://...", "rank": 1, "title": "...", "description": "..." }
]
}0 = Google VN1 = Google (quốc tế)2 = Coc Coc0 = PENDING (chờ xử lý)1 = PROCESSING (đang xử lý)2 = COMPLETED (đã hoàn thành)0 = UNKNOWN (chưa biết)-1 = ERROR (lỗi xử lý)-2 = NOT_FOUND (không tìm thấy kết quả)-3 = CAPTCHA (bị chặn bởi captcha)1 = SUCCESS (thành công)proxiesLưu danh sách proxy cần rotate/kiểm tra định kỳ.
| Field | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
_id | String (ObjectID hex) | Yes | ID proxy task |
webhook_url | String (URI) | Yes | URL gửi kết quả rotate về |
status | Number | No | 0 = PENDING, 1 = PROCESSING, 2 = COMPLETED |
status_code | Number | No | HTTP status code từ lần rotate gần nhất |
response | String | No | Response body từ rotate URL |
is_error | Boolean | No | true nếu rotate gặp lỗi |
rotate_url | String | Yes | URL endpoint để trigger rotate IP |
ip_public | String | No | IP public sau khi rotate |
auth_token | String | No | Token xác thực nhà cung cấp proxy |
region | String | No | Region của proxy |
call_webhook_count | Number | No | Số lần đã gọi webhook |
disable_auto_rotate | Boolean | No | true = tắt tự động rotate |
ip | String | No | IP của proxy |
port | Number | No | Port của proxy |
username | String | No | Username xác thực |
password | String | No | Password xác thực |
createdAt | Datetime | Auto | Thời điểm tạo |
updatedAt | Datetime | Auto | Thời điểm cập nhật |
search_anchor_textsLưu danh sách task tìm kiếm anchor text cho từng domain.
| Field | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
_id | String (ObjectID hex) | Yes | ID task |
anchor_key | Array[String] | Yes | Danh sách từ khoá anchor cần tìm trên trang |
domain | String | Yes | Domain cần crawl anchor text |
webhook_url | String (URI) | Yes | URL để gửi kết quả về |
proxy_url | String (URI) | Yes | URL để lấy proxy từ API server |
call_webhook_count | Number | No | Số lần đã gọi webhook |
proxy | Object | No | Proxy đang sử dụng: {host, port, username, password} |
search_data | Object | No | Kết quả anchor text (xem bên dưới) |
status | Number | No | 0 = PENDING, 1 = PROCESSING, 2 = COMPLETED |
result_status | Number | No | 0 = UNKNOWN, -1 = ERROR, -2 = NOT_FOUND, -3 = CAPTCHA, 1 = SUCCESS |
createdAt | Datetime | Auto | Thời điểm tạo |
updatedAt | Datetime | Auto | Thời điểm cập nhật |
search_data:{
"url": "https://example.com",
"anchor_text": [
{ "key": "casino", "link": "https://...", "element": "a" }
]
}+------------------------------------------------------------------+
| SEARCH TOOL (Bun + Elysia) |
| Port: 33033 |
| |
| Entry Point: src/index.ts |
| - connectDatabase() -> MongoDB |
| - Clean tmp/user-data folder |
| - Start Elysia server |
| |
| +--------------------+ +-----------------------------------+ |
| | Swagger UI | | API Routes (/api/*) | |
| | /swagger | | | |
| +--------------------+ | /api/searches (Search Tasks) | |
| | /api/proxies (Proxy Tasks) | |
| +--------------------+ | /api/search-anchor-texts | |
| | Health Endpoints | | /api/test (Dev/Debug) | |
| | GET / | +-----------------------------------+ |
| | GET /health | |
| | GET /logs | |
| +--------------------+ |
| |
| +-------------------------------------------------------------+ |
| | Cron Jobs (Every Second) | |
| | | |
| | search-cron -> searchCron.start() | |
| | search-cron-webhook -> searchCron.runWebhook() | |
| | proxy-cron -> crontProxy.start() | |
| | proxy-cron-webhook -> crontProxy.runWebhook() | |
| | anchor-text-cron -> searchCron.start() | |
| | anchor-text-cron-webhook -> searchCron.runWebhook() | |
| +-------------------------------------------------------------+ |
| |
| +-------------------------------------------------------------+ |
| | Browser Manager | |
| | - Manages Chrome sessions pool | |
| | - Max sessions: BROWSER_LIMIT (default 22) | |
| | - Uses Puppeteer + Stealth plugin | |
| | - Ghost cursor for human-like mouse movement | |
| | - Support Desktop/Mobile device emulation | |
| +-------------------------------------------------------------+ |
+---+-------------------------------+-----------------------------+
| |
v v
+--------+ +-------------------+
|MongoDB | | External Services |
|(search-| | |
| tool- | | - searchapi.io |
| tools) | | - Search API |
| | | (API Server) |
|searches| | - Webhook URLs |
|proxies | | (API Server ) |
|search_ | +-------------------+
|anchor_ |
|texts |
+--------+API Server
-> POST /api/searches (tạo task)
-> Cron (every 1s) picks PENDING tasks up to CRON_SEARCH_LIMIT
-> BrowserManager.newSession() -> Chrome launch
-> Navigate to Google / Coc Coc
-> Scrape search results (ads + organic)
-> Update task status = COMPLETED, save search_data
-> Webhook callback to webhook_url (API Server)
-> API Server records result in keyword_resultsAPI Server
-> POST /api/proxies (tạo proxy task)
-> Cron picks PENDING proxy tasks
-> HTTP GET rotate_url (trigger IP rotation)
-> Parse response, confirm ip_public
-> Webhook callback to webhook_url
-> API Server updates proxy recordtools/
├── src/
│ ├── index.ts # Entry point chính
│ │ - connectDatabase() # Kết nối MongoDB
│ │ - Clean tmp/user-data # Dọn dẹp Chrome profile cũ
│ │ - Khởi tạo Elysia app
│ │ - Register Swagger, errorHandler
│ │ - Register routes (apiRoutes)
│ │ - Expose GET /, /health, /logs
│ │
│ ├── api/
│ │ ├── index.ts # Ghép tất cả route controllers
│ │ │ # Prefix: /api
│ │ │
│ │ ├── search/
│ │ │ ├── search.controller.ts # Routes: GET /, POST /, DELETE /, DELETE /:id
│ │ │ │ # POST /reset-status-all, POST /reset-process-running
│ │ │ ├── search.dto.ts # CreateSearchSchema, UpdateSearchSchema
│ │ │ ├── search.model.ts # Mongoose model: Search (collection: searches)
│ │ │ ├── search.service.ts # findAll, createMany, delete, deleteAll, resetStatus
│ │ │ └── search.cron.ts # Cron logic: pick tasks, run browser, scrape, webhook
│ │ │
│ │ ├── proxy/
│ │ │ ├── proxy.controller.ts # Routes: GET /, POST /, DELETE /, DELETE /:id
│ │ │ ├── proxy.dto.ts # CreateProxySchema
│ │ │ ├── proxy.model.ts # Mongoose model: Proxy (collection: proxies)
│ │ │ ├── proxy.service.ts # findAll, create, delete, deleteAll, resetStatus
│ │ │ ├── proxy.cron.ts # Cron: rotate proxy, call webhook
│ │ │ └── cron/ # (sub-cron logic nếu có)
│ │ │
│ │ ├── search-anchor-text/
│ │ │ ├── search-anchor-text.controller.ts # Routes: GET/, POST/, DELETE/, DELETE/:id
│ │ │ │ # POST /reset-status-all, reset-process-running
│ │ │ ├── search-anchor-text.dto.ts # CreateSearchAnchorTextSchema, UpdateSearchAnchorTextSchema
│ │ │ ├── search-anchor-text.model.ts # Mongoose model: SearchAnchorText
│ │ │ ├── search-anchor-text.service.ts # CRUD + reset
│ │ │ └── search-anchor-text.cron.ts # Cron: crawl anchor text, webhook
│ │ │
│ │ └── test/
│ │ └── test.controller.ts # Debug endpoints:
│ │ # GET /test -> open browser, go to google
│ │ # POST /test/proxy -> return hardcoded proxy info
│ │ # GET /test/check-browser -> launch Chrome, screenshot
│ │ # DELETE /test/clear-browser-sessions
│ │
│ ├── config/
│ │ └── database/
│ │ └── mongodb.config.ts # MongoDB connection setup (mongoose)
│ │
│ ├── services/
│ │ └── (shared services nếu có)
│ │
│ ├── types/
│ │ └── global/ # Global type definitions
│ │
│ └── utils/
│ ├── createElysia.ts # Factory tạo Elysia instance với cấu hình chuẩn
│ ├── env.ts # loadEnv() -> parse tất cả biến môi trường
│ ├── error.middleware.ts # Global error handler (Elysia onError)
│ ├── index.ts # Re-export utils
│ ├── logger.ts # Logger với level, file rotation, colorize
│ ├── browser_manager/ # Quản lý Chrome browser pool
│ │ ├── index.ts # BrowserManager class
│ │ ├── device.ts # Device.Type, Device.Engine enums
│ │ └── ... # Session, page management
│ ├── captcha/ # Captcha solving utilities
│ ├── file/ # File utilities
│ └── number/ # Number utilities
│
├── plugin/ # Chrome extension files
│ ├── _locales/ # Ngôn ngữ extension
│ ├── css/
│ ├── img/
│ ├── js/
│ └── sounds/
│
├── storage/
│ └── log/ # Log files (tạo runtime)
│
├── tmp/ # Temp folder (Chrome user-data, screenshots)
│
├── package.json # Dependencies
├── tsconfig.json # TypeScript config
├── .env.example # Template môi trường
├── docker-compose.yml # Docker MongoDB
└── .prettierrc.json # Code formatter configprotect: true trên webhook cron → chỉ 1 instance chạy tại 1 thời điểmCRON_SEARCH_LIMIT / CRON_TASK_LIMIT giới hạn số task concurrentbrowserManager.sessionsCount theo dõi số session đang mởBROWSER_LIMIT ngăn mở quá nhiều Chrome cùng lúcpuppeteer-extra-plugin-stealth để bypass bot detectiontmp/user-data được xóa khi khởi động lại server