You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala爬虫分页获取数据失败,请求返回302跳转至登录页

解决Scala爬虫分页请求跳转问题

问题背景

使用Scala 2.13.1 + net.ruippeixotog%%scala-scraper%3.0.0的JsoupBrowser爬取目标网站:

  1. 成功从首页GET请求获取__RequestVerificationToken,携带该Token发送POST请求可拿到第一页数据
  2. 分页请求未携带Token时返回登录表单;改用Akka HTTP实现后,返回302跳转至登录页,仍无法获取分页数据

现有代码片段

首页获取Token(正常工作)

val browser = JsoupBrowser()
val homepageDoc = browser.get("example.com")
val requestVerificationToken = homepageDoc >> element("input[name='__RequestVerificationToken']") >> attr("value")

第一页请求(正常工作)

val formData = Map(
  "searchtype" -> "C",
  "__RequestVerificationToken" -> requestVerificationToken
)
val doc = browser.post(url, formData)

分页请求(失败)

val nextPageFormData = Map(
  "undefined" -> "",
  "sortby" -> "",
  "stype" -> "a",
  "pidx" -> "2"
)
val doc = browser.post(url, nextPageFormData)

Akka HTTP尝试(返回302)

implicit val actorSystem: ActorSystem = ActorSystem()
implicit val executionContext: ExecutionContextExecutor = actorSystem.dispatcher

val browser = JsoupBrowser()
val homepageDoc = browser.get("example.com")
val requestVerificationToken = homepageDoc >> element("input[name='__RequestVerificationToken']") >> attr("value")

val url = "searchpage.com"
val postBody = "undefined=&sortby=&stype=a&pidx=2"

val request = HttpRequest(HttpMethods.POST, url, Seq(RawHeader("__requestverificationtoken",requestVerificationToken)), HttpEntity(ContentTypes.`application/x-www-form-urlencoded`, postBody))
val responseFuture = Http(actorSystem).singleRequest(request)
responseFuture
  .onComplete {
    case Success(res) => println(res)
      Unmarshal(res.entity.toStrict(5 seconds)).value.map { result =>
        val htmlStr = result.data.utf8String
        val browser = JsoupBrowser()
        val doc = browser.parseString(htmlStr)
        println(doc)
      }
    case Failure(e) => println(e)
  }

核心问题分析

  1. 会话一致性丢失:JsoupBrowser默认维护Cookie会话,但分页请求若使用新实例或Cookie未正确携带,后端会判定为未授权
  2. 验证Token未正确传递:部分网站分页请求仍需携带__RequestVerificationToken,即使前端表单看似没有要求
  3. Akka HTTP未处理Cookie与跳转:Akka HTTP默认不自动管理Cookie,也不会自动跟进302跳转,导致会话中断

解决方案

方案1:修复scala-scraper分页请求

  • 复用同一个JsoupBrowser实例,确保Cookie会话延续
  • 分页请求强制携带__RequestVerificationToken
  • 可打印browser.cookies确认Cookie是否正常携带

修正后的分页代码:

// 复用初始化好的browser实例,不要新建
val nextPageFormData = Map(
  "undefined" -> "",
  "sortby" -> "",
  "stype" -> "a",
  "pidx" -> "2",
  "__RequestVerificationToken" -> requestVerificationToken // 强制添加Token
)
val doc = browser.post(url, nextPageFormData)

方案2:修复Akka HTTP请求

  • 从首页请求中提取Cookie,在后续请求中携带
  • 配置Akka HTTP自动跟进302跳转
  • 确保Token放在Form数据中(部分网站仅识别Form中的Token,而非Header)

修正后的Akka HTTP代码:

implicit val actorSystem: ActorSystem = ActorSystem()
implicit val executionContext: ExecutionContextExecutor = actorSystem.dispatcher
implicit val timeout: Timeout = 10 seconds

// 第一步:获取首页Cookie和Token
val homepageRequest = HttpRequest(HttpMethods.GET, "example.com")
val homepageResponseFuture = Http(actorSystem).singleRequest(homepageRequest)

homepageResponseFuture.flatMap { homepageRes =>
  // 提取Cookie
  val cookies = homepageRes.headers.collect {
    case `Set-Cookie`(cookieStr) => cookieStr.split(";").head // 提取核心Cookie键值对
  }.mkString("; ")

  // 解析首页HTML获取Token
  Unmarshal(homepageRes.entity.toStrict(5 seconds)).to[String].map { html =>
    val doc = JsoupBrowser().parseString(html)
    doc >> element("input[name='__RequestVerificationToken']") >> attr("value")
  }.flatMap { token =>
    // 构造分页请求,携带Cookie和Token(放在Form中)
    val postBody = s"undefined=&sortby=&stype=a&pidx=2&__RequestVerificationToken=$token"
    val pageRequest = HttpRequest(
      HttpMethods.POST,
      "searchpage.com",
      Seq(
        RawHeader("Cookie", cookies),
        RawHeader("Content-Type", "application/x-www-form-urlencoded")
      ),
      HttpEntity(ContentTypes.`application/x-www-form-urlencoded`, postBody)
    )

    // 使用Akka HTTP的superPool自动跟进302跳转
    Http(actorSystem).superPool[Unit]().ask((pageRequest, ()))
      .map(_._1)
      .flatMap { res =>
        Unmarshal(res.entity.toStrict(5 seconds)).to[String].map { html =>
          val resultDoc = JsoupBrowser().parseString(html)
          // 处理结果文档
          println(resultDoc)
        }
      }
  }
}.recover {
  case e => println(s"请求失败: ${e.getMessage}")
}

额外排查点

  • 查看浏览器DevTools中的分页请求细节:确认实际请求是否携带Cookie、Token,以及请求头/Form数据的完整内容
  • 部分网站会检查Referer头,可在请求中添加Referer为首页URL
  • 若Token有有效期,分页请求需确保使用的Token未过期,必要时重新从首页获取

内容的提问来源于stack exchange,提问作者Zaryab Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 08:14:53