python 如何使用QNetworkAccessManager通过https下载文件

oaxa6hgo  于 2023-02-18  发布在  Python
关注(0)|答案(2)|浏览(222)

我尝试用QtNetwork写一个类来下载一个文件而不冻结我的GUI。这似乎可以用http URL(用“http://webcode.me“测试),但不能用我的例子中的https URL。

import os
from typing import Optional
import urllib.parse

from PyQt5.QtCore import pyqtSignal, QByteArray, QFile, QObject, QUrl
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest

class AsyncDownloader(QObject):
    def __init__(self, url: str, filename: str, parent=None):
        super().__init__(parent)
        self.net_mgr = QNetworkAccessManager()
        self.req = QNetworkRequest(QUrl(url))
        self.fetch_task: Optional[QNetworkReply] = None
        self.data: Optional[QByteArray] = None
        self.file = QFile(filename)

    def start_fetch(self):
        self.fetch_task = self.net_mgr.get(self.req)
        self.fetch_task.downloadProgress.connect(self.on_progress)
        self.fetch_task.finished.connect(self.on_finished)

    def on_progress(self, bytes_received: int, bytes_total: int):
        print(f"bytes received {bytes_received} (total {bytes_total})")

    def on_finished(self):
        print("finished")
        self.data = self.fetch_task.readAll()
        if not self.file.open(QFile.WriteOnly):
            raise IOError(f"Unable to write to {self.file.fileName}")
        self.file.write(self.data)
        self.file.close()
        print(f"file written to {self.file.fileName()}")

if __name__ == '__main__':
    from pathlib import Path
    from PyQt5.QtWidgets import QApplication

    dl_path = os.path.join(str(Path.home()), "test_dl")
    os.makedirs(dl_path, exist_ok=True)
    app = QApplication([])
    downloader = AsyncDownloader(
        "https://github.com/PiRK/Electrum-ABC-Build-Tools/releases/download/v1.0/tor-linux",
        os.path.join(dl_path, "tor")
    )
    downloader.start_fetch()

    app.exec_()

我收到的错误(或警告?)包括:

qt.network.ssl: QSslSocket: cannot resolve EVP_PKEY_base_id
qt.network.ssl: QSslSocket: cannot resolve SSL_get_peer_certificate
qt.network.ssl: QSslSocket: cannot call unresolved function SSL_get_peer_certificate
bytes received 0 (total 0)
finished
file written to /home/myname/test_dl/tor

写入的文件为空。
我尝试在self.net_mgr = ....后面添加以下行:

parsed_url = urllib.parse.urlparse(url)
    if parsed_url.scheme == "https":
        self.net_mgr.connectToHostEncrypted(parsed_url.hostname)

这没有帮助。
使用wget时下载工作正常:

$ wget "https://github.com/PiRK/Electrum-ABC-Build-Tools/releases/download/v1.0/tor-linux"
...
tor-linux                                    100%[=============================================================================================>]  15,34M   985KB/s    in 16s

2023-02-16 16:36:51 (969 KB/s) - ‘tor-linux’ saved [16090880/16090880]
bnlyeluc

bnlyeluc1#

QNetworkAccessManager无法支持HTTPS之后,我使用了基于Python的multiprocessing标准库和requests库(不是stdlib,但urllib.request的官方Python文档推荐)的替代解决方案。
唯一的缺点是,我没有得到任何下载进度信息。

import multiprocessing
import requests

class Downloader:
    """URL downloader designed to be run as a separate process and to communicate
    with the main process via a Queue.

    The queue can be monitored for the following messages (as str objects):

      - "@started@"
      - "@HTTP status@ {status code} {reason}"
        (e.g "@HTTP status@ 200 OK")
      - "@content size@ {size in bytes}"
      - "@finished@"
    """

    def __init__(self, url: str, filename: str):
        self.url = url
        self.filename = filename

        self.queue = multiprocessing.Queue()

    def run_download(self):
        self.queue.put("@started@")
        r = requests.get(url)
        self.queue.put(f"@HTTP status@ {r.status_code} {r.reason}")
        self.queue.put(f"@content size@ {len(r.content)}")
        with open(self.filename, "wb") as f:
            f.write(r.content)
        self.queue.put("@finished@")

if __name__ == '__main__':
    from pathlib import Path
    from PyQt5.QtWidgets import QApplication
    from PyQt5.QtCore import QTimer
    import os
    import sys

    url = sys.argv[1]
    fname = sys.argv[2]

    dl_path = os.path.join(str(Path.home()), "test_dl")
    os.makedirs(dl_path, exist_ok=True)
    app = QApplication([])

    downloader = Downloader(
        url,
        os.path.join(dl_path, fname)
    )
    process = multiprocessing.Process(target=downloader.run_download)

    def read_queue():
        while not downloader.queue.empty():
            msg = downloader.queue.get()
            print(msg)

    timer = QTimer()
    timer.timeout.connect(read_queue)
    timer.timeout.connect(lambda: print("."))

    process.start()
    timer.start(500)

    app.exec_()

下面是一个156 MB文件的输出示例:

$ python downloader.py "https://stsci-opo.org/STScI-01GGF8H15VZ09MET9HFBRQX4S3.png" large_img_https.png
@started@
.
.
.
.
.
.
.
.
.
@HTTP status@ 200 OK
@content size@ 159725397
@finished@
.
.
.
^Z
[3]+  Stopped                 python downloader.py "https://stsci-opo.org/STScI-01GGF8H15VZ09MET9HFBRQX4S3.png" large_img_https.png
$ kill %3
8yoxcaq7

8yoxcaq72#

To download a file via HTTPS using QNetworkAccessManager in Qt, you can use the following steps:

Create a QNetworkAccessManager object.
Create a QNetworkRequest object with the URL of the file you want to download.
Set the SSL configuration of the QNetworkRequest object. If you are using a self-signed certificate, you may need to ignore SSL errors.
Call the QNetworkAccessManager::get() function with the QNetworkRequest object to initiate the download.
When the download is complete, the QNetworkAccessManager::finished() signal is emitted with a QNetworkReply object. You can then read the downloaded data from the QNetworkReply object and save it to a file.
Here's some sample code that demonstrates how to download a file via HTTPS using QNetworkAccessManager:



QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QUrl url("https://example.com/file-to-download");
QNetworkRequest request(url);

// Set SSL configuration
QSslConfiguration config = request.sslConfiguration();
config.setProtocol(QSsl::TlsV1_2);
request.setSslConfiguration(config);

// Ignore SSL errors
connect(manager, &QNetworkAccessManager::sslErrors, this, [](QNetworkReply *reply, const QList<QSslError> &errors){
    reply->ignoreSslErrors();
});

QNetworkReply *reply = manager->get(request);

// Connect to the finished() signal
connect(reply, &QNetworkReply::finished, this, [reply]() {
    if (reply->error() == QNetworkReply::NoError) {
        // Read downloaded data and save to file
        QByteArray data = reply->readAll();
        QFile file("downloaded-file");
        file.open(QIODevice::WriteOnly);
        file.write(data);
        file.close();
    } else {
        // Handle error
    }
    reply->deleteLater();
});

相关问题