【问题标题】:Headless Chrome with Python suspends when trying to download a file尝试下载文件时,带有 Python 的 Headless Chrome 会挂起
【发布时间】:2018-02-05 19:56:30
【问题描述】:

我在 Mac 上使用 Python、Jupyter、Selenium webdriver 和无头 Chrome(带有 Canary)。

我写了一个脚本来抓取一个非常旧的网站, 为了从该网站下载文件,我需要点击几个按钮,最终将我带到一个按钮,一旦点击它就会下载一个 CSV 文件

问题是,当无头 Chrome 尝试下载目标文件时,它会挂起并且什么都不做(即不下载所需的文件),即使脚本完成运行(是的,我确实在脚本末尾关闭了它)

我试过了:

  • 下载其他文件(来自不同网站)和无头 Chrome 似乎可以毫无问题地下载它们(我启用了无头 chrome 选项以成功下载文件)
  • 拍摄网站快照以确保其正确导航到下载页面(是的,其导航正确)
  • 修改用户代理(它似乎正在使用我期望的用户代理)
  • 在没有 headless 选项的情况下运行完全相同的代码 - 它使用常规 chrome 成功下载文件
  • 使用 driver.execute_script(js_that_changes_plugins_and_langs) 更改驱动程序上的插件和语言 JS 脚本,但我不太确定如何检查它是否真的在执行它(它仍然不工作)

我面临的问题:

  • 我找不到只获取最后一个下载 URL 的方法,因为它似乎使用了一些沿途生成的唯一 ID(当您转到主页和在网站中的页面之间导航时给出)所以每次会话都会发生变化
  • 导航 URL 似乎来自主页内的 iframe(以及以下 URL),我不太清楚如何检查它生成的 Javascript

我提供网站 URL 没有任何问题,但是:

  • 您必须在不同的页面上点击大约 6 次才能到达带有下载按钮的最后一页。这些点击并不直观,需要大量时间才能解释如何导航到我想要的位置
  • 此网站不是英文的,因此更难解释如何导航

与常规 Chrome 相比,我需要它是无头的,因为我们要运行代码的机器非常弱,无法运行 chrome GUI

所以我的问题是:有谁知道这可能是什么问题?或者至少,我该如何调试它?

这或多或少是我正在使用的代码:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def enable_download_in_headless_chrome(driver, download_dir):
        """
        there is currently a "feature" in chrome where
        headless does not allow file download: https://bugs.chromium.org/p/chromium/issues/detail?id=696481
        This method is a hacky work-around until the official chromedriver support for this.
        Requires chrome version 62.0.3196.0 or above.
        """

        # add missing support for chrome "send_command"  to selenium webdriver
        driver.command_executor._commands["send_command"] = ("POST", '/session/' + driver.session_id + '/chromium/send_command')

        params = {'cmd': 'Page.setDownloadBehavior', 'params': {'behavior': 'allow', 'downloadPath': download_dir}}
        command_result = driver.execute("send_command", params)
        print("response from browser:")
        for key in command_result:
            print("result:" + key + ":" + str(command_result[key]))

chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('headless')
chrome_options.add_argument('no-sandbox')
chrome_options.add_argument('disable-gpu')
chrome_options.add_argument('remote-deubgging-port=9222')
chrome_options.add_argument('disable-popup-blocking')
chrome_options.add_argument('enable-logging')
download_dir = # some path here
driver = webdriver.Chrome(chrome_options=chrome_options)
enable_download_in_headless_chrome(driver, download_dir)
ok_button = driver.find_element_by_id('the-button-name')
ok_button.click()

感谢您的帮助

【问题讨论】:

  • 你试过加user-agent吗?还有其他技术可以检测无头 Chrome 以让服务器决定做什么(例如中断下载),您可能需要查看 intoli.com/blog/making-chrome-headless-undetectable
  • 也可以看看at this,这基本上是对@hurturk 建议的跟进。
  • 具体挂在哪一点?在哪一行代码之后?您是否检查了运行浏览器进程的用户具有写入权限的“下载”目录?
  • 您可以使用 zaproxy 监控请求以进行调试。
  • 我确实尝试通过添加 user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/60.0.3112.50 Safari/537.36' chrome_options.add_argument(f'user-agent={user_agent}') 来使用用户代理,但它仍然不起作用,我需要确保您提供的链接中建议的其他选项也设置正确它挂起点击之后。我看到无头 chrome 已打开(当我 alt 选项卡时)但没有任何反应。我会看看 zaproxy 感谢您的建议:)

标签: python python-3.x selenium-webdriver google-chrome-headless


【解决方案1】:

我认为这里有太多的活动部分。如果您真的需要硒和所有其他元素 - 好吧 - 没关系。但是,我会从尽可能简单的事情开始。

在 Python 2.7 上,我使用的是 mechanize - 这样我就能够模拟与服务器的整个通信。今天这不是最好的选择,因为 python 3.X 是要走的路。我将描述我是如何处理这类问题的。只是为了给你更好的图片,然后我会尝试描述可能的工具。

所以典型的情况是登录,浏览页面,打开一些开关,然后触发下载,或者获取内容并使用beautiful soup 处理它。首先,您需要查看交换了哪些信息。转到 Web 浏览器中的开发工具,然后选择网络选项卡。 也许你知道,但这一步是强制性的,我想写一个一般性的答案。然后做你的正常工作 - 只需登录,然后做其他步骤。服务器处理的所有事情都必须传输,因此您可以将其视为网络请求。 Mechanize 很好,因为我能够准备 dict,并将其作为 post 请求发送到页面。写post - 典型的错误是发布到页面地址。因此,如果您访问了index.html,您正在该页面上执行post,而服务器希望将其发送到add_user_data.html,然后您将重定向。诸如会话 id 之类的东西,可以由标头条目或 cookie 支持 - 只需查看该模式的网络通信即可。

正如我所写,Python 2.7 即将停产。 Mechanize 不适用于 Python 3.x,因此应使用其他工具。您可以寻找机械化替代品,并查看适合您的方法。典型的答案是scrapy。这是一个有点不同的工具,更多地用于废弃网页。因此,如果您计划更大的事情,那可能是最好的选择。如果您需要单个脚本 - 我会从 httpie 开始。命令行工具/python包,很好的OSX支持,可以send formsession management也可以。我每天都在使用它,但是我的服务器是无状态的。

我会更乐意提供确切的示例,但没有服务器信息是不可能的。您能否附上您的示例会话的转储?将其匿名,我将提供示例样本,或者其他工具可能会更好?

【讨论】:

  • 不熟悉 mechanize 但是这个网站的问题是你必须经过大约 10 次点击(而且你不能绕过这些步骤)所以我必须模拟一个实际点击所有这些的用户按钮
  • @Gideon 这不是问题。它可以是 5 次、10 次或任何点击次数 - 那是脚本。右键单击 + 检查,然后选择“网络”选项卡。要开始跟踪,您需要 F5 刷新。拥有整个会话,您可以选择请求并存储它们。他们称之为har。那是带有鲸鱼内容的 JSON 文件。从那里您可以使用requestshttpie 执行所有这些操作 - 由您决定。只需编辑问题,并分享一些东西。另一种选择是指向公共方面,我们可以这样工作。
  • 我在这里可能是错的,但是随着点击生成的 URL 被赋予了一个唯一的 ID。这些 ID 是从主页的 iframe 中的代码给出的,这基本上意味着我不能只硬编码对最终 URL 的请求。知道如何检查该 iframe 吗?
  • 不太可能。一般来说,这是可能的,但从服务器端来看也是相当昂贵的。您可以拥有会话,它由 cookie 或部分 url 标识...一定要提取它,然后在下一个请求中使用。因为我不知道上下文 - 很难说更多。
【解决方案2】:

由于您没有提供下载猜测作品的 URL。 Target 很可能安装了类似 recapta 的墙以防止刮擦。因此,请确保您没有碰到这个“recapta”墙,并且如果您确实实现了通知您执行手动任务以授予访问权限的代码。

对于 js,此解决方案由 zavodnyuk here 提供:

尝试使用兼容的用户代理设置自定义用户代理(例如,从您的真实浏览器中)。 功能:{'browserName':'chrome',chromeOptions:{args:[“user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36”, "--headless", "--disable-gpu" ] } 在 js 上为 selenium/protractor 工作

我希望这能提示您正确的方向,因为互联网上没有太多关于 python 的描述。

根据comment1编辑:

在基本调试模式下,我在可能的候选定义开始时依赖打印语句。在我说 printstatement 的地方,它也可以是文件的写入行。不依赖第三方花哨的软件包,因为我大部分时间都想从代码中学习,然后是上述方法耗时但非常值得花时间的努力。比如我如何直截了当地调试:

def header_inspect(self, ID, action, data):
    print  'header_inspect, ID : %s\n, action : %s\nprocess-data : %s' % (ID, action, data)

【讨论】:

  • 目标似乎没有任何类似 recaptcha 的机制(我知道)。此外,当我在单击下载按钮之前拍摄快照时,它会正确显示按钮,所以我认为这不是问题。在发送请求之前,我会尝试检查所有 webdriver 参数,看看是否有任何奇怪的地方。我希望能够理解究竟是什么让 webdriver 暂停并且永不终止
  • 您是否知道在使用 python 运行无头 chrome 代码时是否可以看到调试日志?如果有,在哪里?当它暂停时,我不确定它卡在什么上面
  • 我的回答方法。
【解决方案3】:

由于没有具体信息,我们可以为您提供的唯一建议似乎与您如何理解正在发生的事情有关。

headed 模式 中逐步手动 进行调试怎么样?这里的赌注是你的问题在于自动化你的任务而不是无头。

使用所有导入和函数定义(例如enable_download_in_headless_chrome)执行脚本,不使用任何这些。实际上,直到download_dir = # some path here,然后在 Python Shell 中键入

>>> driver = webdriver.Chrome(chrome_options=chrome_options)

现在手动与您的浏览器交互并打开Chrome DevTools 并转到Console。确保错误will be displayed。让我们继续并输入其余的命令

>>> enable_download_in_headless_chrome(driver, download_dir)
>>> ...
>>> ok_button.click()

它说了什么?

【讨论】:

  • 如果我以 headed 模式运行整个脚本(仅注释 headless 行),它将完成整个过程并成功下载文件。一旦我添加了无头选项,它会在它完成脚本但 chrome canary 挂起并且不关闭(并且不下载我需要的文件)
  • @Gideon。傻点:在点击下载按钮前几秒钟,您是否尝试过time.sleep(或更好的WebDriverWait)?无头模式有时会非常被动,以至于在浏览过程中会造成不一致。
  • 大声笑,是的,我做到了。在我的有头脚本中,我还做了一些睡眠以使一切正常工作,然后当我切换到无头时,我仍然必须继续睡觉,所以是的
  • @Gideon。您是否考虑过在无头浏览期间通过实现一些 JavaScript 变量来采取一些反击策略?以These elements 为例。
  • 我尝试了用户代理,而不是其他的。我会检查并回复你
【解决方案4】:

试试 String downloadFilepath ="\\sd-";

chromePrefs.put("download.default_directory", downloadFilepath);

【讨论】:

    猜你喜欢
    • 2021-01-28
    • 2017-12-26
    • 1970-01-01
    • 1970-01-01
    • 2014-10-01
    • 2020-04-08
    • 1970-01-01
    • 2013-08-07
    • 2019-05-25
    相关资源
    最近更新 更多