Chrome Driver 破解反爬

背景

过年回家买机票,各个机票网站上的价格都稍有差别,于是就利用 web-crawler 爬一爬
过程中发现,爬取 qunar 的信息时,qunar 识别了 chrome headless,故意返回了错误的数据
这时只能面向 Google 编程了

解决

理论上,selenium+chromedriver 是可以完全模拟真是浏览器的
但是实际上 chromedriver 在启动的时候,会跟普通的浏览器有些不一样的地方

  1. UserAgent 会带上 headless 标识
    解决

    1
    2
    3
    4
    5
    val options = new ChromeOptions()
    options.addArguments(
    "--headless",
    s"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36"
    )
  2. js 全局对象有值 : window.navigator.webdriver = true
    解决: 通过 driver.executeCdpCommand 在页面加载时期做一些初始化逻辑

    1
    2
    3
    4
    5
    6
    7
    8
    val driver = new ChromeDriver(buildOptions(requestSetting))
    val map = new util.HashMap[String, Object]()
    map.put("source", """
    |Object.defineProperty(navigator, 'webdriver', {
    | get: () => false,
    |});
    |""".stripMargin)
    driver.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", map)
  3. 获取 XhrRequest Response
    解决:

    • 通过 performanceLog 拿到 requestId
    1
    val performanceLog = driver.manage().logs().get(LogType.PERFORMANCE)
    • 通过 driver.executeCdpCommand("Network.getResponseBody", cdpMap) 来重放 XhrRequest
    1
    2
    3
    val cdpMap = new util.HashMap[String, Object]()
    cdpMap.put("requestId", requestId)
    driver.executeCdpCommand("Network.getResponseBody", cdpMap).asScala.toMap

PS.

  • selenium 版本 "org.seleniumhq.selenium" % "selenium-chrome-driver" % "4.0.0-alpha-3"

  • 完整代码

参考

MAKING CHROME HEADLESS UNDETECTABLE