Skip to main content

Command Palette

Search for a command to run...

Firestoreへのスクレイピングデータ挿入と重複チェック

Updated
2 min readView as Markdown
Firestoreへのスクレイピングデータ挿入と重複チェック

TL;DR

スクレイピングで取得したデータをFirestoreに保存する際、重複データの挿入を避ける方法について解説します。この記事では、URLフィールドを一意の識別子として使用し、重複チェックを行いながら効率的にデータを保存する方法を紹介します。

  • Private Key の改行コードで詰まった

  • Firestore に一括で書き込みができなくて詰まった

  • Apps Script のライブラリの仕様を理解する必要があった

などを書いていきます。

Firestoreの設定

まず、Firestoreの設定を行います。以下の情報を取得し、Firestoreインスタンスを設定します。

  • Firebase Email

  • Firebase Private Key

  • Firebase Project ID

var dataHandler = {
  getFirestoreInstance: function() {
    return FirestoreApp.getFirestore(
      CONFIG.FIREBASE_CONFIG.EMAIL,
      CONFIG.FIREBASE_CONFIG.PRIVATE_KEY,
      CONFIG.FIREBASE_CONFIG.PROJECT_ID
    );
  }
};

コードには記載されていないけど、Firebase の Private Key の扱いに四苦八苦していたのですが、以下のコメントが解決してくれました。

var PRIVATE_KEY = PropertiesService.getScriptProperties().getProperty('private_key').replace(/\\n/g, '\n');

これでOKだった。

既存データの重複チェック

既存のURLをFirestoreから取得し、キャッシュに保存します。キャッシュを使用することで、毎回Firestoreにクエリを送信する必要がなくなり、効率的な重複チェックが可能です。

getExistingUrlsFromFirestore: function() {
  const firestore = this.getFirestoreInstance();
  const existingUrls = new Set();
  const documents = firestore.getDocuments('products');

  documents.forEach(doc => {
    if (doc.fields.url) {
      existingUrls.add(doc.fields.url.stringValue);
    }
  });

  return existingUrls;
},

getExistingUrlsFromCache: function() {
  const cache = CacheService.getScriptCache();
  const cachedData = cache.get('existingUrls');
  return cachedData ? new Set(JSON.parse(cachedData)) : null;
},

cacheExistingUrls: function(urls) {
  const cache = CacheService.getScriptCache();
  cache.put('existingUrls', JSON.stringify(Array.from(urls)), 21600); // 6時間キャッシュ
},

checkAndFilterNewItems: function(items) {
  let existingUrls = this.getExistingUrlsFromCache();
  if (!existingUrls) {
    existingUrls = this.getExistingUrlsFromFirestore();
    this.cacheExistingUrls(existingUrls);
  }
  return items.filter(item => !existingUrls.has(item.url));
}

Firestoreへのデータ挿入

新しいアイテムのみをFirestoreに挿入します。重複するアイテムはスキップします。 firestore.batch が利用できないことから、1件ずつ書き込みすることに...500件程度を1度の処理で行ったところ、タイムアウトしました。

Apps Script の無料枠の仕様を考えると、360秒以内に抑える必要があるので、書き込み最大件数を300に減らした。

storeProductData: function(newItems) {
  const firestore = this.getFirestoreInstance();
  const documents = firestore.getDocuments('products');
  let maxId = 0;
  if (documents.length > 0) {
    maxId = Math.max(...documents.map(doc => doc.fields.id.integerValue));
  }
  let nextId = maxId + 1;

  let newDocumentCount = 0;
  let duplicateDocumentCount = 0;

  newItems.forEach(item => {
    const encodedUrl = encodeURIComponent(item.url);
    const docPath = 'products/' + encodedUrl;

    try {
      const existingDoc = firestore.getDocument(docPath);
      if (existingDoc) {
        utils.logInfo('Document already exists, skipping: ' + encodedUrl);
        duplicateDocumentCount++;
      }
    } catch (e) {
      if (e.message.includes('not found')) {
        try {
          firestore.createDocument(docPath, {
            id: nextId++,
            title: item.title,
            url: item.url,
            image: item.image,
            price: item.price,
            startPrice: item.startPrice,
            bidCount: item.bidCount,
            category: item.category,
            sellerID: item.sellerID,
            dateAdded: new Date(),
            isDateFetched: false
          });
          newDocumentCount++;
          utils.logInfo('Document successfully created: ' + encodedUrl);
        } catch (error) {
          utils.logError('Error creating document: ' + encodedUrl + ' - ' + error.message);
        }
      }
    }
  });

  utils.logInfo('Total documents processed: ' + newItems.length);
  utils.logInfo('New documents created: ' + newDocumentCount);
  utils.logInfo('Duplicate documents skipped: ' + duplicateDocumentCount);
}

ログの最適化

処理結果をより明確に把握できるよう、重複データと新規作成データの数をログに記録します。

utils.logInfo('Total documents processed: ' + newItems.length);
utils.logInfo('New documents created: ' + newDocumentCount);
utils.logInfo('Duplicate documents skipped: ' + duplicateDocumentCount);

ログ、まじで大事スね。

まとめ

  • Firestoreの設定: Firestoreインスタンスの取得方法。

  • 重複チェック: 既存URLをキャッシュに保存し、重複チェックを効率化。

  • データ挿入: 新規データのみをFirestoreに挿入し、重複データをスキップ。

  • ログの最適化: 新規作成数と重複数をログに記録。

これにより、スクレイピングデータを効率的にFirestoreに保存し、重複データを避ける方法を実現しました。これらの手法を活用することで、データ処理の効率を大幅に向上させることができます。

ニッチな産業なのですが、ヤフオクでやたらとAI美女とAIイラストのA4ポスターが売れていると聞いてスクレイピングしてみた結果、思った以上の市場があってビックリでした。

1日に100万円以上なんてレベルじゃない売上があるみたい。世の中、どんな需要があるのかわからないものッス🤔