AUTO.XUANHOA.ART

API Server - Tự động lấy dữ liệu từ BIDQ.CO.KR

Checking...

Tổng quan

Server API này cho phép tự động lấy thông tin doanh nghiệp từ trang BIDQ.CO.KR dựa trên mã số văn phòng (officeno). Hỗ trợ tích hợp với Google Sheets thông qua Apps Script.

Dữ liệu được trả về

companyName Tên công ty (업체명)
representative Người đại diện (대표자)
address Địa chỉ (주소)
businessNumber Mã số doanh nghiệp (사업자번호)
phone Số điện thoại (전화번호)
Lưu ý: Nếu không tìm thấy dữ liệu hoặc trang hiển thị "검색결과가 없습니다", tất cả các giá trị sẽ trả về "-"

API Endpoints

GET /api/scrape?officeno={officeno}

Lấy thông tin của một văn phòng/doanh nghiệp.

Parameter Type Description
officeno *required string Mã số văn phòng (VD: 2378800691)

Response Example:

{
  "success": true,
  "empty": false,
  "officeno": "2378800691",
  "data": {
    "companyName": "주식회사 대방전공",
    "representative": "김동국",
    "address": "경상북도 경산시 경안로17길 22-0 (서상동)",
    "businessNumber": "5148158392",
    "phone": "053-638-0055"
  }
}
GET /api/sheets?officeno={officeno}

Endpoint tối ưu cho Google Sheets - trả về array đơn giản.

Response Example:

["주식회사 대방전공", "김동국", "경상북도 경산시 경안로17길 22-0 (서상동)", "5148158392", "053-638-0055"]
POST /api/scrape/batch

Lấy thông tin nhiều văn phòng cùng lúc (tối đa 50).

Request Body:

{
  "officeNumbers": ["2378800691", "4078118199", "1234567890"]
}
GET /api/health

Kiểm tra trạng thái server.

GET /api/stats

Xem thống kê số lượng request.

Test API

Thử nghiệm ngay

Kết quả sẽ hiển thị ở đây...

Hướng dẫn tích hợp Google Sheets

1

Mở Google Sheets

Tạo một Google Sheets mới hoặc mở sheet hiện có chứa danh sách mã officeno.

2

Mở Apps Script

Vào menu ExtensionsApps Script

3

Dán Code Script

Xóa toàn bộ code mặc định và dán đoạn code dưới đây:

/**
 * AUTO.XUANHOA.ART - Google Sheets Integration Script
 * Tự động lấy dữ liệu từ BIDQ.CO.KR
 *
 * HƯỚNG DẪN CÀI ĐẶT:
 * 1. Cấu hình các biến CONFIG bên dưới theo nhu cầu của bạn
 * 2. Chạy function setupTrigger() một lần để tạo trigger tự động
 * 3. Hoặc chạy thủ công bằng function processAllRows()
 */

// ============ CẤU HÌNH - THAY ĐỔI THEO NHU CẦU ============
const CONFIG = {
  // Cột chứa mã officeno (A=1, B=2, C=3, ...)
  OFFICENO_COLUMN: 1,        // Cột A

  // Cột bắt đầu ghi dữ liệu (tên công ty sẽ ghi vào cột này)
  OUTPUT_START_COLUMN: 2,    // Cột B

  // Hàng bắt đầu đọc dữ liệu (thường là 2 nếu hàng 1 là tiêu đề)
  START_ROW: 2,

  // Tên sheet cần xử lý
  SHEET_NAME: 'Sheet1',

  // API URL
  API_URL: 'https://auto.xuanhoa.art/api/sheets'
};

/**
 * Lấy dữ liệu từ API cho một officeno
 */
function fetchOfficeData(officeno) {
  try {
    const url = CONFIG.API_URL + '?officeno=' + encodeURIComponent(officeno);
    const response = UrlFetchApp.fetch(url, {
      'method': 'GET',
      'muteHttpExceptions': true,
      'headers': {
        'Accept': 'application/json'
      }
    });

    const responseCode = response.getResponseCode();
    if (responseCode !== 200) {
      Logger.log('API Error: ' + responseCode);
      return ['-', '-', '-', '-', '-'];
    }

    const data = JSON.parse(response.getContentText());
    return data;

  } catch (error) {
    Logger.log('Error fetching data: ' + error.toString());
    return ['-', '-', '-', '-', '-'];
  }
}

/**
 * Xử lý tất cả các hàng chưa có dữ liệu
 */
function processAllRows() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
  if (!sheet) {
    Logger.log('Sheet not found: ' + CONFIG.SHEET_NAME);
    return;
  }

  const lastRow = sheet.getLastRow();
  let processedCount = 0;
  let skippedCount = 0;

  Logger.log('Starting processing from row ' + CONFIG.START_ROW + ' to ' + lastRow);

  for (let row = CONFIG.START_ROW; row <= lastRow; row++) {
    // Lấy giá trị officeno
    const officeno = sheet.getRange(row, CONFIG.OFFICENO_COLUMN).getValue();

    // Bỏ qua nếu ô trống
    if (!officeno || officeno.toString().trim() === '') {
      skippedCount++;
      continue;
    }

    // Kiểm tra xem đã có dữ liệu chưa (kiểm tra cột output đầu tiên)
    const existingData = sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN).getValue();
    if (existingData && existingData.toString().trim() !== '') {
      skippedCount++;
      continue;
    }

    // Gọi API lấy dữ liệu
    Logger.log('Processing row ' + row + ': ' + officeno);
    const data = fetchOfficeData(officeno.toString().trim());

    // Ghi dữ liệu vào các cột
    // data = [companyName, representative, address, businessNumber, phone]
    if (data && data.length >= 5) {
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN).setValue(data[0]);     // Tên công ty
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN + 1).setValue(data[1]); // Đại diện
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN + 2).setValue(data[2]); // Địa chỉ
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN + 3).setValue(data[3]); // Mã số DN
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN + 4).setValue(data[4]); // SĐT
    }

    processedCount++;

    // Delay nhỏ để tránh quá tải
    Utilities.sleep(200);
  }

  Logger.log('Completed! Processed: ' + processedCount + ', Skipped: ' + skippedCount);
  SpreadsheetApp.getActiveSpreadsheet().toast(
    'Hoàn thành! Đã xử lý: ' + processedCount + ' hàng',
    'AUTO.XUANHOA.ART',
    5
  );
}

/**
 * Xử lý một hàng cụ thể (dùng cho custom function)
 * Sử dụng: =GETBIDQDATA(A2)
 */
function GETBIDQDATA(officeno) {
  if (!officeno || officeno.toString().trim() === '') {
    return ['-', '-', '-', '-', '-'];
  }

  const data = fetchOfficeData(officeno.toString().trim());
  return [data];
}

/**
 * Xử lý các hàng được chọn
 */
function processSelectedRows() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const selection = sheet.getActiveRange();
  const startRow = selection.getRow();
  const numRows = selection.getNumRows();

  let processedCount = 0;

  for (let i = 0; i < numRows; i++) {
    const row = startRow + i;
    const officeno = sheet.getRange(row, CONFIG.OFFICENO_COLUMN).getValue();

    if (!officeno || officeno.toString().trim() === '') {
      continue;
    }

    Logger.log('Processing selected row ' + row + ': ' + officeno);
    const data = fetchOfficeData(officeno.toString().trim());

    if (data && data.length >= 5) {
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN).setValue(data[0]);
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN + 1).setValue(data[1]);
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN + 2).setValue(data[2]);
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN + 3).setValue(data[3]);
      sheet.getRange(row, CONFIG.OUTPUT_START_COLUMN + 4).setValue(data[4]);
    }

    processedCount++;
    Utilities.sleep(200);
  }

  SpreadsheetApp.getActiveSpreadsheet().toast(
    'Đã xử lý ' + processedCount + ' hàng được chọn',
    'AUTO.XUANHOA.ART',
    3
  );
}

/**
 * Xóa dữ liệu output để chạy lại
 */
function clearOutputData() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
  const lastRow = sheet.getLastRow();

  if (lastRow >= CONFIG.START_ROW) {
    sheet.getRange(
      CONFIG.START_ROW,
      CONFIG.OUTPUT_START_COLUMN,
      lastRow - CONFIG.START_ROW + 1,
      5
    ).clearContent();
  }

  SpreadsheetApp.getActiveSpreadsheet().toast('Đã xóa dữ liệu output', 'AUTO.XUANHOA.ART', 3);
}

/**
 * Tạo menu tùy chỉnh
 */
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu('BIDQ Scraper')
    .addItem('Xử lý tất cả hàng', 'processAllRows')
    .addItem('Xử lý hàng được chọn', 'processSelectedRows')
    .addSeparator()
    .addItem('Xóa dữ liệu output', 'clearOutputData')
    .addToUi();
}

/**
 * Thiết lập trigger tự động (chạy một lần)
 */
function setupTrigger() {
  // Xóa trigger cũ nếu có
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === 'processAllRows') {
      ScriptApp.deleteTrigger(trigger);
    }
  });

  // Tạo trigger mới - chạy mỗi giờ
  ScriptApp.newTrigger('processAllRows')
    .timeDriven()
    .everyHours(1)
    .create();

  Logger.log('Trigger đã được thiết lập - chạy mỗi giờ');
}

/**
 * Xóa tất cả triggers
 */
function removeTriggers() {
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(trigger => ScriptApp.deleteTrigger(trigger));
  Logger.log('Đã xóa tất cả triggers');
}
4

Cấu hình Script

Thay đổi các giá trị trong CONFIG theo cấu trúc sheet của bạn:

  • OFFICENO_COLUMN: Số thứ tự cột chứa mã officeno (A=1, B=2, ...)
  • OUTPUT_START_COLUMN: Cột bắt đầu ghi dữ liệu output
  • START_ROW: Hàng bắt đầu (thường là 2 nếu hàng 1 là tiêu đề)
  • SHEET_NAME: Tên sheet cần xử lý
5

Lưu và chạy

Nhấn Ctrl+S để lưu, sau đó:

  • Chạy processAllRows để xử lý tất cả
  • Hoặc chạy setupTrigger để tự động chạy mỗi giờ
  • Refresh sheet để thấy menu "BIDQ Scraper" mới
Ví dụ cấu trúc Sheet:

| A (officeno) | B (Tên CT) | C (Đại diện) | D (Địa chỉ) | E (Mã số DN) | F (SĐT) |
|--------------|------------|--------------|-------------|--------------|---------|
| 2378800691 | [auto] | [auto] | [auto] | [auto] | [auto] |
| 4078118199 | [auto] | [auto] | [auto] | [auto] | [auto] |

Tạo Script Tự Động

Chọn các cột tương ứng trong Google Sheets của bạn, hệ thống sẽ tự động tạo script phù hợp.

Ví dụ: Chọn U nếu mã officeno ở cột U

Sử dụng nâng cao

Dùng như Custom Function

Sau khi thêm script, bạn có thể sử dụng công thức trực tiếp trong ô:

=GETBIDQDATA(A2)

Công thức này sẽ trả về một hàng gồm 5 giá trị.

Gọi API từ code khác (cURL)

# Single request
curl "https://auto.xuanhoa.art/api/scrape?officeno=2378800691"

# Sheets format
curl "https://auto.xuanhoa.art/api/sheets?officeno=2378800691"

# Batch request
curl -X POST "https://auto.xuanhoa.art/api/scrape/batch" \
  -H "Content-Type: application/json" \
  -d '{"officeNumbers": ["2378800691", "4078118199"]}'

JavaScript/Fetch

// Single request
fetch('https://auto.xuanhoa.art/api/scrape?officeno=2378800691')
  .then(res => res.json())
  .then(data => console.log(data));

// Batch request
fetch('https://auto.xuanhoa.art/api/scrape/batch', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    officeNumbers: ['2378800691', '4078118199']
  })
})
  .then(res => res.json())
  .then(data => console.log(data));

Python

import requests

# Single request
response = requests.get('https://auto.xuanhoa.art/api/scrape',
                        params={'officeno': '2378800691'})
data = response.json()
print(data)

# Batch request
response = requests.post('https://auto.xuanhoa.art/api/scrape/batch',
                         json={'officeNumbers': ['2378800691', '4078118199']})
data = response.json()
print(data)