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万円以上なんてレベルじゃない売上があるみたい。世の中、どんな需要があるのかわからないものッス🤔

