4

I'm building a program which uses QWebEngineView and QUrl to display a website in my PyQt5 app (running on Windows 10). However, I now want to be able to download a CSV file from the same website, but being a noob I can't seem to figure out how.

I'm familiar with using requests, urllib.request, urllib3, etc. for downloading files, but for this, I specifically want to do it with the QWebEngineView, as the user will have authenticated the request previously in the pyqt5 window. The code to show the website in the first place goes like this:

self.view = QWebEngineView(self)
self.view.load(QUrl(url))
self.view.loadFinished.connect(self._on_load_finished)
self.hbox.addWidget(self.view)

Does anyone have any suggestion on how this can be achieved?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
codeacker
  • 85
  • 1
  • 11
  • you could provide the url, and if necessary the credentials. QWebEngineView is used as a browser, you just have to click on the file to download it – eyllanesc May 03 '19 at 05:52
  • The website is [link](https://www.vendhq.com/) but I unfortunately can't provide the credentials. There is no button to click, just a url created in my program which if I print it and copy & paste into chrome downloads the file if I am already on the same page as my program (interestingly it closes the tab as well, doesn't display anything). I have tried just doing a `.load(QUrl(url))`, but it just doesn't do anything... I assume I need to somehow set a download directory? – codeacker May 03 '19 at 06:25
  • okay, but when you get the url it is no longer necessary to be authenticated – eyllanesc May 03 '19 at 06:27
  • I thought that originally, but if I try to run `urllib.request.urlretrieve(url, "test.csv")` it just crashes the program (although that code works fine with an image I tried). – codeacker May 03 '19 at 06:31
  • Does the web page you indicate generate the download url or does your script do it? – eyllanesc May 03 '19 at 06:31
  • My script does it, but I can print it out and copy into chrome which works fine, so I was assuming the issue wasn't there. – codeacker May 03 '19 at 06:32
  • Ok, I'll understand, for the download you need to be logged in https://secure.vendhq.com ?, you could share a url of .csv – eyllanesc May 03 '19 at 06:34
  • try my answer.... – eyllanesc May 03 '19 at 06:43

1 Answers1

7

In QWebEngineView by default the downloads are not handled, to enable it you have to use the downloadRequested signal of QWebEngineProfile, this transports a QWebEngineDownloadItem that you have to accept if you want the download to start:

from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)

        self.view = QtWebEngineWidgets.QWebEngineView()
        self.view.page().profile().downloadRequested.connect(
            self.on_downloadRequested
        )
        url = "https://domain/your.csv"
        self.view.load(QtCore.QUrl(url))
        hbox = QtWidgets.QHBoxLayout(self)
        hbox.addWidget(self.view)

    @QtCore.pyqtSlot("QWebEngineDownloadItem*")
    def on_downloadRequested(self, download):
        old_path = download.url().path()  # download.path()
        suffix = QtCore.QFileInfo(old_path).suffix()
        path, _ = QtWidgets.QFileDialog.getSaveFileName(
            self, "Save File", old_path, "*." + suffix
        )
        if path:
            download.setPath(path)
            download.accept()


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

If you want to make a direct download you can use the download method of QWebEnginePage:

self.view.page().download(QtCore.QUrl("https://domain/your.csv"))

Update:

@QtCore.pyqtSlot("QWebEngineDownloadItem*")
def on_downloadRequested(self, download):
    old_path = download.url().path()  # download.path()
    suffix = QtCore.QFileInfo(old_path).suffix()
    path, _ = QtWidgets.QFileDialog.getSaveFileName(
        self, "Save File", old_path, "*." + suffix
    )
    if path:
        download.setPath(path)
        download.accept()
        download.finished.connect(self.foo)

def foo(self):
    print("finished")
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • thanks for this answer, much appreciated. For completeness could you include a function which triggers on download completion? – codeacker May 08 '19 at 19:39
  • 1
    @codeacker use `download.finished.connect(foo_function)` https://doc.qt.io/qt-5/qwebenginedownloaditem.html#finished – eyllanesc May 08 '19 at 19:49
  • I have added this directly after `download.accept()` which works perfectly and runs the function (just a print), but then the program immediately crashes and the file no longer downloads. If I comment out that line, the file downloads but (obviously) doesn't run the function. Do I have that line in the correct place? Please excuse the noob questions! – codeacker May 09 '19 at 05:41
  • @codeacker Without a [mcve] I could not tell you where the error is, I will only limit myself to showing an example in my update of my answer. If it does not work for you then you must provide the MCVE if you want help. – eyllanesc May 09 '19 at 05:49
  • It appears we now have setDownloadDirectory() / setDownloadFileName() instead of setPath() for QWebEngineDownloadRequest class in Qt 6.2. – StarterKit Dec 01 '21 at 13:21