株式会社WR

株式会社WR

WEB TOTAL CONSULTING

Qoo10のセール情報を自動収集——スクレイピングとAPI活用
ブログ一覧へ
技術ブログ

Qoo10のセール情報を自動収集——スクレイピングとAPI活用

Qoo10は韓国系ECプラットフォームで特にコスメ・雑貨分野で人気があります。セール情報・価格変動を自動収集してビジネスに活かす方法を解説します。

Qoo10とは——日本のEC市場での存在感

Qoo10(キューテン)は韓国・シンガポール発のグローバルECプラットフォームです。日本では特にコスメ・食品・韓国関連商品のカテゴリで高い人気を誇り、「メガ割」などのセールイベント時には通常の数倍のトラフィックが発生します。

仕入れ業者・セラーにとって、Qoo10のセール情報をリアルタイムに把握することはビジネス上重要です。本記事ではPuppeteerを使ったQoo10のセール情報収集と、公式APIが存在する場合の活用方法を解説します。


セール情報の収集——Puppeteerアプローチ

Qoo10のセールページはJavaScriptで動的にレンダリングされるため、Puppeteerが有効です。

const puppeteer = require('puppeteer');

/**
 * Qoo10のセールページから商品情報を取得
 */
async function scrapeQoo10Sale(category = 'beauty') {
    const browser = await puppeteer.launch({
        headless: true,
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });

    const page = await browser.newPage();
    await page.setViewport({ width: 1280, height: 900 });
    await page.setUserAgent(
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
        'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
    );

    try {
        const url = `https://www.qoo10.jp/gmkt.inc/Goods/GoodsList.aspx?keyword=${encodeURIComponent(category)}&sort=sale`;
        await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });

        // 商品リストが読み込まれるまで待機
        await page.waitForSelector('.goods_list', { timeout: 10000 }).catch(() => {});

        // 商品データを取得
        const items = await page.evaluate(() => {
            const elements = document.querySelectorAll('.goods_item');
            return Array.from(elements).map(el => ({
                name:         el.querySelector('.goods_name')?.textContent?.trim() ?? '',
                salePrice:    el.querySelector('.sale_price')?.textContent?.trim() ?? '',
                originalPrice:el.querySelector('.org_price')?.textContent?.trim() ?? '',
                discountRate: el.querySelector('.discount')?.textContent?.trim() ?? '',
                url:          el.querySelector('a')?.href ?? '',
                imageUrl:     el.querySelector('img')?.src ?? '',
            }));
        });

        return items.filter(item => item.name);

    } finally {
        await browser.close();
    }
}

セール商品の価格変化追跡

const fs = require('fs');

/**
 * 前回取得データと比較して値下がりした商品を検出
 */
function detectPriceDrops(currentItems, historyPath) {
    const history = fs.existsSync(historyPath)
        ? JSON.parse(fs.readFileSync(historyPath, 'utf8'))
        : {};

    const drops = [];

    currentItems.forEach(item => {
        const prevPrice = history[item.url];
        const currPrice = parsePrice(item.salePrice);

        if (prevPrice && currPrice < prevPrice) {
            drops.push({
                name:       item.name,
                prevPrice:  prevPrice,
                currPrice:  currPrice,
                dropAmount: prevPrice - currPrice,
                dropRate:   ((prevPrice - currPrice) / prevPrice * 100).toFixed(1),
                url:        item.url,
            });
        }

        // 履歴を更新
        history[item.url] = currPrice;
    });

    fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
    return drops;
}

function parsePrice(priceStr) {
    return parseInt(priceStr.replace(/[^\d]/g, ''), 10) || 0;
}

メガ割期間の特別収集

メガ割は四半期ごとに開催される大型セールで、割引率が特に大きくなります。

const cron = require('node-cron');

// 通常時:1日1回
let currentSchedule = cron.schedule('0 8 * * *', async () => {
    await runCollection('daily');
}, { timezone: 'Asia/Tokyo' });

/**
 * メガ割期間は頻度を上げる
 */
function startMegaSaleMode() {
    currentSchedule.stop();

    // メガ割期間中:30分ごとに収集
    currentSchedule = cron.schedule('*/30 * * * *', async () => {
        await runCollection('mega_sale');

        const drops = await detectSignificantDrops();
        if (drops.length > 0) {
            await notifySlack(formatDropAlert(drops));
        }
    }, { timezone: 'Asia/Tokyo' });

    console.log('メガ割モード: 30分間隔で収集中');
}

function formatDropAlert(drops) {
    const lines = drops.map(d =>
        `• ${d.name.substring(0, 30)}\n  ¥${d.prevPrice.toLocaleString()} → ¥${d.currPrice.toLocaleString()} (▼${d.dropRate}%)`
    );
    return `:fire: *メガ割 価格下落アラート*\n\n${lines.join('\n\n')}`;
}

データをLaravelへ送る

const axios = require('axios');

async function syncToLaravel(items) {
    const apiUrl = process.env.LARAVEL_API_URL;
    const token  = process.env.LARAVEL_API_TOKEN;

    const response = await axios.post(
        `${apiUrl}/api/v1/qoo10/items/sync`,
        { items },
        {
            headers: {
                'Authorization': `Bearer ${token}`,
                'Content-Type':  'application/json',
            },
            timeout: 30000,
        }
    );

    return response.data;
}
// Laravel側のAPI
class Qoo10ItemController extends Controller
{
    public function sync(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'items'               => ['required', 'array'],
            'items.*.name'        => ['required', 'string'],
            'items.*.salePrice'   => ['required', 'string'],
            'items.*.url'         => ['required', 'url'],
        ]);

        $upserted = 0;
        foreach ($validated['items'] as $item) {
            Qoo10Item::updateOrCreate(
                ['url' => $item['url']],
                [
                    'name'         => $item['name'],
                    'sale_price'   => $this->parsePrice($item['salePrice']),
                    'last_seen_at' => now(),
                ]
            );
            $upserted++;
        }

        return response()->json(['upserted' => $upserted]);
    }
}

まとめ

Qoo10のセール情報自動収集は、仕入れ判断・価格設定・競合モニタリングに役立つツールです。Puppeteerによるスクレイピングとcronによる定期実行、Slackへのアラート通知を組み合わせることで、見逃しのない自動監視体制を構築できます。

EC自動化・データ収集ツールの開発についてはお気軽にご相談ください。

Category 技術ブログ

Related Posts

関連記事

開発・技術のご相談はお気軽に

お見積りは無料です。まずはお気軽にご相談ください。

お問い合わせ →