Qt實現(xiàn)Konachan基于tag的圖片下載器

剛接觸Qt不久的練手項目堵泽,Github鏈接在這里,release目錄下面是軟件發(fā)布姿染,如果無法打開軟件可以先安裝vc_redist(VS運行環(huán)境)膀息。

已實現(xiàn)的feature包括:
1.指定tag從https://konachan.com下載所有匹配的圖片措拇;
2.自定義爬取線程池最大線程數(shù)量<30(根據實驗我纪,K站在同一IP并發(fā)請求數(shù)量高于30時會ban IP,本來打算爬西刺代理的高匿IP來解決這個問題來著,但是基于幾個方面的考慮最終就沒做浅悉,一是免費IP的速度實在是慢趟据、不穩(wěn)定,二是30個線程在不打算作為一個pure spider爬人家全站的情況下已經滿足需求了仇冯,三是除了多調一個QNetworkProxy的api外也沒什么新的技術含量和練習價值又浪費時間)之宿;
3.網絡請求重發(fā)和本地圖片去重,網絡請求失敗重發(fā)重發(fā)最多三次苛坚,要下載的圖片先檢測本地是否存在(重復)比被,存在則不再下載,在更新本地圖片的時候好用省時泼舱。

界面如下圖所示:


KonachanCollecter.png

下面記錄和總結一下開發(fā)和學習過程中的思路等缀、問題和收獲。

〇娇昙、關于IDE(VS2015)和框架(Qt5.6.2)本身的一些問題

1. Qt designer打不開

原因是Qt5WebEngineWidgets.dll加載出問題尺迂,更名該動態(tài)庫可解決。

2. 缺少ui_xxx.h頭文件

VS2015集成Qt5有時會出現(xiàn)無法生成ui頭文件(ui_xxx.h)的情況冒掌,需要對.ui文件單獨編譯噪裕。

3. 生成moc_xxx.cpp文件失敗

VS2015集成Qt5有時會出現(xiàn)moc文件(moc_xxx.cpp)在解決方案中存在但實際上并沒有成功生成的情況,需要把解決方案中的moc文件移除股毫,然后重新編譯

4. 如何切換當前項目的Qt版本

首先安裝新的Qt版本膳音,并且環(huán)境變量的QTDIR值也要設置為要切換的Qt版本路徑,然后在Qt5插件的Qt Options選項中添加目標Qt版本并設為default铃诬,最后在當前解決方案上右鍵選擇Change Solution's Qt Version即可祭陷,當然源碼的兼容性就得靠自己來修改了。

一趣席、需求分析與架構設計

從根據輸入tag拼接出匹配結果頁面(根頁面)的url到下載到所有匹配圖片要經過四個步驟:

  • 請求根頁面兵志,并通過正則表達式分析根頁面得出分頁數(shù)量,獲取所有分頁頁面的url宣肚;
  • 請求分頁頁面想罕,對每個分頁頁面分析得出該頁所有圖片的概覽頁面url;
  • 請求概覽頁面霉涨,對每張圖片的概覽頁面分析得出下載頁面url弧呐;
  • 請求下載頁面,并保存到本地(下載圖片)嵌纲。

頻繁發(fā)起的https請求毫無疑問要用線程池來處理,而對于不同步驟而言腥沽,請求過程是相同的逮走,結果處理因步驟(url)而異,為了學習練手和有可能的圖站擴展(并沒有)今阳,實現(xiàn)的時候有意多態(tài)思想师溅,把請求過程放在基類中茅信,結果處理的部分抽象為純虛函數(shù),在四個派生類中各自實現(xiàn)墓臭,不過這樣做還要根據不同url創(chuàng)建不同的類蘸鲸,可以矯情地套用一下工廠模式(并沒有)。

對于線程間通信窿锉,即每次的進度更新(界面顯示)和任務添加(產生的新url)酌摇,第一個發(fā)布的版本用了任務隊列,但是要考慮線程安全(加鎖)嗡载,還要用非成員變量窑多,重構后的版本全部采用了事件循環(huán)的方式,win32下采用xxxEvent和WaitForxxxObject的一系列api洼滚,而Qt的則是信號槽埂息,信號槽(signals & slots)是Qt的核心機制之一,基于其元對象系統(tǒng)(The Meta-Object System)遥巴,具體內容留待后續(xù)再敘千康。

二、詳細設計與相關問題

1. 線程池管理線程

用于給線程池添加新任務和通知主線程進度更新铲掐,采用任務隊列的方式需要重寫QThread類的run函數(shù)來實現(xiàn)拾弃,采用信號槽的方式需要繼承QObject類然后movetothread()。

在Qt中創(chuàng)建線程有兩種方式:
一是繼承QThread類并重載run函數(shù)迹炼,開啟線程后會在新線程中執(zhí)行run函數(shù)砸彬,但是需要注意的是它的槽函數(shù)被觸發(fā)時會執(zhí)行在主線程;
二是繼承QObject類斯入,在其中定義信號和槽砂碉,然后調用movetothread移入QThread對象,這樣它的槽函數(shù)被觸發(fā)時會執(zhí)行在新線程中刻两。

關于Qt的信號槽也有幾點注意事項:
一是在類中使用signals和slots需要繼承QObject類并加上Q_OBJECT宏增蹭;
二是信號槽中對象不能是不完整類型;
三是信號參數(shù)要不少于槽參數(shù)磅摹;
四是信號和槽同時也是普通函數(shù)滋迈,只是信號函數(shù)不能由用戶實現(xiàn),調用時都一樣户誓。

class PoolManager :public QObject
{
    Q_OBJECT
public:
    PoolManager(QString path,int max);
    ~PoolManager();

public slots:
    void add_new_task(QString task);

signals:
    void new_progress(QString progress);

private:
    QString path_;
    QThreadPool thread_pool_;
};
  1. 使用QThreadPool來創(chuàng)建線程池饼灿,QThreadPool開啟線程需要傳入繼承QRunnable類的對象,創(chuàng)建一個繼承自QRunnable的基類帝美,在重寫的run函數(shù)中實現(xiàn)https請求過程碍彭,純虛函數(shù)virtual void handle_page() = 0;留待派生類實現(xiàn)。
class TaskWorker :public QObject ,public QRunnable
{
    Q_OBJECT

public:
    void run();
    TaskWorker* set_url(QString url);
    TaskWorker* set_path(QString path);
    virtual void handle_page() = 0;

signals:
    void new_task(QString task);

protected:
    QString url_;
    QString path_;
    QByteArray page_;
};
  1. https請求的實現(xiàn)可以用Qt提供的QNetworkAccessManager
    但是Qt的網絡庫不太好用庇忌,有幾個問題需要注意:

一是每個QNetworkAccessManager對象可以發(fā)起多個連接舞箍,但是并發(fā)連接數(shù)量有上限且無法配置,官方文檔說明對于http連接最多支持同時6個請求(QNetworkAccessManager queues the requests it receives. The number of requests executed in parallel is dependent on the protocol. Currently, for the HTTP protocol on desktop platforms, 6 requests are executed in parallel for one host/port combination.)皆疹,一個項目只需要一個manager(One QNetworkAccessManager should be enough for the whole Qt application.)疏橄,然而這顯然無法滿足需求,對于這個項目就不能墨守成規(guī)了略就,需要每個線程單獨創(chuàng)建一個manager捎迫;

二是QNetworkAccessManager對象在發(fā)起連接時是異步的,也就是會開一個子線程進行請求残制,可以通過信號槽的方式來異步處理請求結果立砸,但需要同步處理時可以用QEventLoop來阻塞,另外QNetworkAccessManager對象在主線程退出前不會主動釋放初茶,其子線程也不會主動退出颗祝,因此完成后要主動釋放掉QNetworkAccessManager對象來關閉子線程,不然會造成隱式內存泄露恼布;

三是關于請求返回的QNetworkReply*對象螺戳,它需要用戶去主動釋放(After the request has finished, it is the responsibility of the user to delete the QNetworkReply object at an appropriate time. Do not directly delete it inside the slot connected to finished(). You can use the deleteLater() function.),并且在調用readAll()方法時會清空緩沖區(qū)折汞,需要多次使用請求結果時注意保存倔幼;

四是關于SSL的支持需要把對應版本(32/64位)的openssl動態(tài)庫(ssleay32.dll和libeay32.dll)和Qt5Network.dll放在一起,Qt5.6.2是在Qt5.6.2\Tools\QtCreator\bin目錄下爽待;

void TaskWorker::run()
{
    QSslConfiguration ssl_config;
    ssl_config.setPeerVerifyMode(QSslSocket::VerifyNone);
    ssl_config.setProtocol(QSsl::TlsV1_2);
    QNetworkRequest request;
    request.setSslConfiguration(ssl_config);
    request.setUrl(QUrl(url_));
    QEventLoop loop;
    QNetworkAccessManager* manager = new QNetworkAccessManager;
    QNetworkReply* reply;
    bool suc = false;
    for (int i = 0;i < 3 && !suc;i++)
    {// resend when error
        reply = manager->get(request);
        connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
        loop.exec();
        if (!reply->error()) {
            suc = true;// quit loop when success
            page_ = reply->readAll();
        }
        else qInfo("Network Error(%d): %s![%s]", reply->error(), qPrintable(reply->errorString()), qPrintable(reply->url().toString()));
        delete reply; reply = nullptr;// mem release
    }
    manager->deleteLater();
    suc ? handle_page() : qInfo("Request failed: %s", qPrintable(url_));
}
  1. 對于頁面分析损同,Qt對正則表達式的支持有QRegExpQRegularExpression可供選擇,本項目采用前者鸟款,另外值得一提的是QRegExp支持同一對象存放多個pattern膏燃,匹配時可以通過cap()的參數(shù)來選擇采用哪個pattern的匹配結果。然后正則表達式這個東西用的時候經常需要查資料何什,不太好記组哩,貼一下我覺得還行的正則表達式參考文檔正則表達式30分鐘入門教程和正則表達式測試網站Regexper
  2. 最后提一下Qt的消息處理自定義方法处渣,可以用來寫日志伶贰,很方便,其中QMessageLogContext是消息發(fā)出時的附加信息如文件名罐栈、行號黍衙、函數(shù)名等等,當然除了qDebug()和qInfo()之外還有對qWarning()荠诬、qCritcal()和qFatal()消息的處理琅翻,main函數(shù)中QApplication對象的定義是Qt Widgets應用所必不可少的涯捻,相關內容留待后敘。
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    KonachanCollecter w;
    w.show();
    // change qDebug&qInfo to logger
    qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context, const QString &msg) {
        QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
        QFile file("kc_log.txt");
        file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append);
#ifdef _DEBUG
        if (type == QtDebugMsg) file.write(QString("%1: %2 (%3:%4, %5)\n").arg(time).arg(msg).arg(context.file).arg(context.line).arg(context.function).toLocal8Bit());
#endif // _DEBUG
        if (type == QtInfoMsg) file.write(QString("%1: %2\n").arg(time).arg(msg).toLocal8Bit());
        file.close();
    });
    return a.exec();
}


拋去網絡庫不談望迎,Qt的確是一個十分優(yōu)秀的框架,不但功能完善易用凌外,還擁有豐富的幫助文檔利于學習辩尊、進步和參考益缠。本來想在這篇梳理一下這段時間接觸Qt的收獲和理解傅是,但是內容較多,也還不夠深入刊驴,決定后續(xù)再另起博文記錄學習關于Qt的核心元對象系統(tǒng)疮薇、Qt Widgets界面設計以及尚未接觸但久聞大名的QML胸墙。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市按咒,隨后出現(xiàn)的幾起案子迟隅,更是在濱河造成了極大的恐慌,老刑警劉巖励七,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件智袭,死亡現(xiàn)場離奇詭異,居然都是意外死亡掠抬,警方通過查閱死者的電腦和手機吼野,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來两波,“玉大人瞳步,你說我怎么就攤上這事⊙埽” “怎么了单起?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長氛堕。 經常有香客問我馏臭,道長,這世上最難降的妖魔是什么讼稚? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任括儒,我火速辦了婚禮,結果婚禮上锐想,老公的妹妹穿的比我還像新娘帮寻。我一直安慰自己,他們只是感情好赠摇,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布固逗。 她就那樣靜靜地躺著浅蚪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烫罩。 梳的紋絲不亂的頭發(fā)上惜傲,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音贝攒,去河邊找鬼盗誊。 笑死,一個胖子當著我的面吹牛隘弊,可吹牛的內容都是我干的哈踱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼梨熙,長吁一口氣:“原來是場噩夢啊……” “哼开镣!你這毒婦竟也來了?” 一聲冷哼從身側響起咽扇,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤邪财,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后肌割,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卧蜓,經...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年把敞,在試婚紗的時候發(fā)現(xiàn)自己被綠了弥奸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡奋早,死狀恐怖盛霎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情耽装,我是刑警寧澤愤炸,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站掉奄,受9級特大地震影響规个,放射性物質發(fā)生泄漏。R本人自食惡果不足惜姓建,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一诞仓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧速兔,春花似錦墅拭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舒憾。三九已至,卻和暖如春穗熬,著一層夾襖步出監(jiān)牢的瞬間镀迂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工唤蔗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留招拙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓措译,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饰序。 傳聞我的和親對象是個殘疾皇子领虹,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內容