API Server - Tự động lấy dữ liệu từ BIDQ.CO.KR
Checking...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.
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) |
{
"success": true,
"empty": false,
"officeno": "2378800691",
"data": {
"companyName": "주식회사 대방전공",
"representative": "김동국",
"address": "경상북도 경산시 경안로17길 22-0 (서상동)",
"businessNumber": "5148158392",
"phone": "053-638-0055"
}
}
Endpoint tối ưu cho Google Sheets - trả về array đơn giản.
["주식회사 대방전공", "김동국", "경상북도 경산시 경안로17길 22-0 (서상동)", "5148158392", "053-638-0055"]
Lấy thông tin nhiều văn phòng cùng lúc (tối đa 50).
{
"officeNumbers": ["2378800691", "4078118199", "1234567890"]
}
Kiểm tra trạng thái server.
Xem thống kê số lượng request.
Tạo một Google Sheets mới hoặc mở sheet hiện có chứa danh sách mã officeno.
Vào menu Extensions → Apps 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');
}
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 outputSTART_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ýNhấn Ctrl+S để lưu, sau đó:
processAllRows để xử lý tất cảsetupTrigger để tự động chạy mỗi giờ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.
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ị.
# 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"]}'
// 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));
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)