最简单的爬虫

背景

从来没有在工作中写过爬虫，前不久领导给了个活从 birdeye 上爬两个代币的历史价格信息。作为前端，最直接的感受就是请求都是从网页过去的，那么我应该可以在这个网站的控制台，去编写请求的代码，这样拿到数据后再去请求我本地的接口，把数据携带过去就可以了。

获取原始请求信息

谷歌浏览器控制台提供了很便捷的功能，可以快速复制一个请求下来。

之所以在浏览器控制台写脚本，是因为在本地写脚本去请求，需要考虑很多额外的事情。
比如分析这个请求需要携带什么请求头、请求方法等等，试错成本很高。既然如此直接复制一个 fetch 请求，在浏览器控制台里直接运行，大概率都是行得通的，该有的都会带上，可以聚焦于数据处理上。

尝试运行，可行

编写脚本

Copy
// 想要爬取的代币 token
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}&currency=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;
}
}
})();

本地接口服务

Copy
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 来塞脚本进去实现的，这样对工程化很友好。但是这个功能毕竟是临时性的，后面也没有多的需求了。就简单实现好了；还好谷歌的控制台提供了很方便的东西，不然一个一个看请求头、看 cookie 真挺麻烦的。