【问题标题】:How to share cookies between multiple runs of Apify SDK crawlers如何在多次运行 Apify SDK 爬虫之间共享 cookie
【发布时间】:2022-01-02 20:21:56
【问题描述】:

使用Apify,我正在尝试从需要登录的网站上抓取搜索结果:

  1. 获取登录页面以获取登录令牌(它是登录表单中的隐藏表单字段)
  2. 发出模拟登录的请求
  3. 在网站上运行搜索以了解搜索结果的页数
  4. 抓取搜索结果的每一页

我无法弄清楚如何在步骤 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 遍历generator function 来模拟我想要的代码流。这可能是我接下来要尝试的。

但是有没有一种更简单的方法可以让我使用常规的async/await 来维护一个更简单的程序流程?好吧,如果不是,我只是想知道我是否错过了一种明显的方法来在同一爬虫的不同运行或不同爬虫实例之间共享会话状态。 FWIW,我只爬了大约 20 页,所以性能没什么大不了的。

【问题讨论】:

    标签: web-scraping screen-scraping cheerio apify


    【解决方案1】:

    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),并且可以在运行之间重复使用。

    但这被抽象为 CheerioCrawler,因为 useSessionPool 参数默认为 true。您可以使用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;
           }
         }
      }
    })
    

    【讨论】:

    • 是的,我希望避免这种状态机复杂性,我必须让每一步都知道它之前和之后的步骤。听起来那是不可能的。感谢您的信息。
    • 顺便说一句,在测试这个时我遇到了一个单独的问题:由 302 响应返回的 cookie 不会在重定向到的请求中发送回服务器。 github.com/apify/apify-js/issues/1267
    • 它通常以较小的步骤完成,因此如果其中一个步骤失败,可以单独重试,而不是重新开始一切。并且能够扩展,即使它不是你主要关心的,这通常是最好的方法。在作为浏览器的情况下,您可以尝试使用page.waitForNavigationpage.waitForResponse 一次性完成所有操作,如果任何步骤失败,将重试整个过程
    猜你喜欢
    • 1970-01-01
    • 2021-09-12
    • 2019-10-31
    • 2021-04-20
    • 1970-01-01
    • 1970-01-01
    • 2021-03-23
    • 2014-11-06
    • 1970-01-01
    相关资源
    最近更新 更多