背景#
従来、仕事でウェブクローラーを書いたことはありませんが、最近上司から birdeye から 2 つのトークンの歴史的な価格情報をクロールする仕事を任されました。フロントエンドの立場からは、リクエストはすべてウェブページから送信されるという直感がありますので、このウェブサイトのコンソールでリクエストのコードを書いて、データを取得した後にローカルのエンドポイントにリクエストを送信してデータを持っていけばいいと思います。
元のリクエスト情報を取得する#
Google Chrome のコンソールは非常に便利な機能を提供しており、簡単にリクエストをコピーすることができます。
ブラウザのコンソールでスクリプトを書く理由は、ローカルでスクリプトを書いてリクエストを送信する場合、多くの追加のことを考慮する必要があるからです。
たとえば、このリクエストにはどのようなリクエストヘッダーやリクエストメソッドが必要かを分析する必要がありますが、試行錯誤のコストが非常に高いです。そうであれば、単純にfetch
リクエストをコピーして、ブラウザのコンソールで実行すれば、ほとんどの場合うまくいくでしょう。必要なものはすべて持っているはずですので、データ処理に集中できます。
実行してみると、うまくいきます
スクリプトの作成#
// クロールしたいトークン
const token = "xxx";
// 開始日時
const startDate = new Date("2023-01-01");
// 終了日時
const endDate = new Date("2023-01-02");
// 30分間隔
const type = "30m";
(async () => {
const getToken = (start, end) => {
return new Promise((resolve) => {
fetch(
`https://api.birdeye.so/amm/ohlcv?address=${token}¤cy=usd&type=${type}&time_from=${start}&time_to=${end}`,
{
headers: {
accept: "application/json, text/plain, */*",
"accept-language": "zh-CN",
"agent-id": "93bd6afd-cbf4-4acc-a014-b62d88374ea6",
"cf-be":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2Nzk0Njk3ODIsImV4cCI6MTY3OTQ3MDA4Mn0.eWkKWRUsPKMZCqNJB5KL_Z_Eurn4wCMN5dyzCR5oJ_U",
"sec-ch-ua": '"Not;A=Brand";v="99", "Chromium";v="106"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"x-cypress-is-xhr-or-fetch": "true",
Referer: "https://birdeye.so/",
"Referrer-Policy": "strict-origin-when-cross-origin",
},
body: null,
method: "GET",
}
)
.then((res) => res.json())
.then((res) => {
try {
resolve(res.data.items);
} catch (error) {
console.error(error);
}
});
});
};
for (
let currentDate = startDate;
currentDate <= endDate;
currentDate.setDate(currentDate.getDate() + 1)
) {
const startTimestamp = Math.floor(currentDate.getTime() / 1000);
const endTimestamp = startTimestamp + 86399;
try {
console.log("データを読み込んでいます...", [startTimestamp, endTimestamp]);
// サーバーへの負荷を軽減するために一時停止
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
const data = await getToken(startTimestamp, endTimestamp);
console.log(
`${new Date(startTimestamp * 1000).toLocaleString()} - ${new Date(
endTimestamp * 1000
).toLocaleString()} データの読み込みが完了しました`
);
// データをローカルのエンドポイントに送信する
fetch("http://localhost:3000", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
} catch (error) {
console.log(error, "データの読み込みに失敗しました");
// ここが非常に重要で、このログを出力することで、リクエスト回数が制限されている場合に備えます。失敗した後、このログに基づいて上記の startDate を調整して、しばらくしてからデータのクロールを再開します
console.log(startTimestamp, endTimestamp);
break;
}
}
})();
ローカルのエンドポイントサービス#
import express from "express";
import bodyParser from "body-parser";
import cors from "cors";
const origin = "https://birdeye.so";
const filePath = "./data.json";
const app = express();
app.use(bodyParser.json());
app.use(cors({ origin }));
app.post("/", async (req, res) => {
// データの形式を自分で処理する
console.log(req.body);
res.send("ok");
});
app.listen(3000);
結論#
最初は Cypress
を使用してスクリプトを実装しようと思っていましたが、この機能は一時的なものであり、後続の要件もありません。簡単に実装することにしました。幸いにも、Google Chrome のコンソールには非常に便利な機能が備わっているため、リクエストヘッダーや Cookie を 1 つずつ確認する必要はありませんでした。