使用Apify,我试图从一个需要登录的网站上抓取搜索结果:
- 获取登录页面以获取登录令牌(这是登录表单中的一个隐藏表单字段(
- 发出模拟登录的请求
- 在网站上运行搜索以了解搜索结果的页数
- 爬网搜索结果的每一页
我很难弄清楚如何在步骤1-4之间共享相同的cookie(也称为会话(。我更喜欢的是在我的代码中进行干净的分离,比如:
Apify.main(async () => {
await clearApifyCache();
// what should this code be to enable sharing sessions
// between each crawl step?
const context = ?????
const loginToken = await getLoginToken(context); // Step 1
await login(context, loginToken); // Step 2
const pageCount = await getPageCount(context); // Step 3
const results = await getJobList(context, pageCount); // Step 4
await Promise.all([writeCsv(results), writeHtml(results)]);
});
我可以把它构建得更像一个状态机,其中有一个Apify.CheerioCrawler
实例,每个请求都向RequestQueue
添加下一个请求。我甚至可以通过让handlePageFunction
迭代生成器函数来模拟我想要的代码流。这可能是我下一步要尝试的。
但是,有没有一种更简单的方法可以让我使用常规async
/await
来维护一个更简单的过程流?好吧,如果没有,我只是想知道我是否错过了一种在同一爬网程序的不同运行或不同爬网程序实例之间共享会话状态的明显方法。FWIW,我只爬了大约20页,所以perf不是什么大不了的。
Apify包含一个SessionPool,它正是用于该用例的。您可以为代码创建一个自定义会话池,它将共享一个通用的命名键值存储,例如:
const sessionPool = await Apify.openSessionPool({
persistStateKeyValueStoreId: 'your-named-storage',
persistStateKey: 'sessions' // by default it's SDK_SESSION_POOL_STATE
});
sessionPool.getSession(); // gets or create a new session
sessionPool.addSession(); // explicitly adds a session
这允许您在需要时创建新会话,并为那些已经成功的会话存储会话(以及Cookie和userData
(,这些会话可以在运行之间重复使用。
但由于useSessionPool
参数默认为true
,因此它被抽象为CheerioCrawler。您可以使用sessionPoolOptions
对其进行配置。
整个过程可以在不依赖会话的情况下解决,只需使用标签并传递loginToken即可:
const requestQueue = await Apify.openRequestQueue();
await requestQueue.addRequest({
url: 'https://example.com/login',
method: 'POST',
payload: 'username=value1&password=value2',
userData: {
label: 'LOGIN',
}
});
new CheerioCrawler({
requestQueue,
handlePageFunction: async (context) => {
const { request, response } = context;
const { label } = request.userData;
switch (label) {
case 'LOGIN': {
const loginToken = await getLoginToken(context); // Step 1
// add the request for step 2, so it can keep track of where it is
await requestQueue.addRequest({
url: 'https://example.com/search',
method: 'GET',
headers: {
cookie: response.headers['set-cookie'], // if it uses cookies
someTokenHeader: loginToken, // plug it somewhere. if it's a payload, use payload. if it's in the URL, add to the `url` property
},
userData: {
label: 'SEARCH',
loginToken,
}
});
break;
}
case 'SEARCH': {
const { loginToken } = request.userData;
await login(context, loginToken);
const pageCount = await getPageCount(context); // Step 3
await requestQueue.addRequest({
url: 'https://example.com/details',
userData: {
...request.userData, // merge the previous information here
label: 'DETAILS',
pageCount,
}
});
break;
}
case 'DETAILS': {
// since it's the last step, we can push the data
const { pageCount } = request.userData;
const results = await getJobList(context, pageCount);
await Apify.pushData(results);
break;
}
}
}
})