В один прекрасный день я решил написать grabber для книжного сайта и теперь хочу поделиться с вами тонкостями реализации такого рода программных решений. Вся информация представлена исключительно для ознакомительных целей
За основу взял QWebEngineView, что бы не заморачиваться с авторизацией. И внешне это выглядит так:
Sharing куков между QNetworkAccessManager и QWebEngineView
Для этого в Qt есть QWebEngineCookieStore и QNetworkCookieJar
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_ui(new Ui::MainWindow), m_store(nullptr), m_cookieJar(new QNetworkCookieJar (this)), m_networmManager(new QNetworkAccessManager(this)), m_try(0), m_currentPage(0), m_capches(1) { m_ui->setupUi(this); m_store = m_ui->webView->page()->profile()->cookieStore(); Q_ASSERT(m_store != nullptr); connect(m_store, &QWebEngineCookieStore::cookieAdded, this, &MainWindow::handleCookieAdded); m_store->loadAllCookies(); m_ui->webView->load(QUrl("http://липкиеручки.рф/")); m_networmManager->setCookieJar(m_cookieJar); connect(m_networmManager, &QNetworkAccessManager::finished, this, &MainWindow::handleImage); } void MainWindow::handleCookieAdded(const QNetworkCookie &cookie) { m_cookieJar->insertCookie(cookie); }
Когда переходим на чтение книги и нажимаем на кнопку Grab, то берется url вида:
http://липкиеручки.рф/static/or3/view/or.html?art_type=4&file=26599915&bname=Разработка веб-приложений в ReactJS&cover=%2Fstatic%2Fbookimages%2F26%2F59%2F99%2F26599923.bin.dir%2F26599923.cover.jpg&art=22880082&user=Что-то&uuid=Что-то
Вытаскиваем id файла и название:
void MainWindow::onGrabButtonClicked() { if(!parseUrl(m_ui->webView->url())) { return; } const auto paths = QStandardPaths::standardLocations(QStandardPaths::DownloadLocation); if (paths.isEmpty()) { qWarning()<<"There is no standard path to download"; return; } downloadTo(*paths.begin()); } bool MainWindow::parseUrl(const QUrl &url) { const auto query = QUrlQuery(url.query(QUrl::FullyDecoded)); if (query.isEmpty()){ return false; } static const QVector<QString> fields = { "file", "bname", "uuid" }; for (const auto& key: fields) { if (!query.hasQueryItem(key)) { qWarning()<<"Query hasn't param"<< key; return false; } } m_name = query.queryItemValue("bname", QUrl::FullyDecoded); m_file = query.queryItemValue("file"); m_format = "jpg"; return true; }
MainWindow::downloadTo настраивает QPdfWriter и QPainter
void MainWindow::downloadTo(const QString &path) { QDir dir(path); m_writer = std::make_unique<QPdfWriter>(dir.absoluteFilePath(m_name+".pdf")); QPageLayout layout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(0,0,0,0)); m_writer->setPageLayout(layout); m_writer->setResolution(96); m_writer->setTitle(m_name); m_painter = std::make_unique<QPainter>(); m_painter->begin(m_writer.get()); nextImage(); }
Скачивание страницы
Страницы скачиваются по url вида:
http://липкиеручки.рф/pages/read_book_online/?file=26599915&page=2&rt=w1280&ft=gif
| Параметр | Описание |
|---|---|
| rt | отвечает за размеры, принимает значение w640, w1280 |
| ft | формат gif или jpg |
| page | номер страницы |
| file | идентификатор файла |
Формат jpg применяется для страниц с графикой, в то же время gif для текста.
Если страницы по url: http://липкиеручки.рф/pages/read_book_online/?file=26599915&page=0&rt=w1280&ft=gif не существует, то следует запросить http://липкиеручки.рф/pages/read_book_online/?file=26599915&page=0&rt=w1280&ft=jpg
Получаем:
void MainWindow::nextImage() { QUrlQuery query; query.addQueryItem("file", m_file); query.addQueryItem("rt", "w640"); query.addQueryItem("ft", m_format); query.addQueryItem("page", QString::number(m_currentPage)); QUrl url(BasePath); url.setQuery(query); m_networmManager->get(QNetworkRequest(url)); ++m_currentPage; } void MainWindow::handleImage(QNetworkReply *reply) { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { qWarning()<<"Network error"<<reply->errorString(); if(m_try == 3) { m_painter->end(); m_painter.reset(); m_writer.reset(); return; } if (m_format == "gif") { m_format = "jpg"; } else { m_format = "gif"; } --m_currentPage; ++m_try; nextImage(); return; } m_try = 0; qDebug()<<"Write page"<<m_currentPage<<reply->url(); std::string f; if (m_format == "jpg") { f = "JPEG"; } else { f = "GIF"; } const auto data = reply->readAll(); const auto source = QImage::fromData(data, f.c_str()); if (source.isNull()) { //handleCapcha(data, reply->url()); --m_currentPage; nextImage(); return; } m_ui->pages->setText(QString::number(m_currentPage)); const auto dest = source.scaledToWidth(m_writer->width()/*, Qt::SmoothTransformation */); m_painter->drawImage(QPoint(0,0), dest); m_writer->newPage(); nextImage(); }
Капча
Капча вроде бы есть, но в тоже время нет. Выскакивает не всегда
Мы заметили странную активность с вашего компьютера. Возможно, мы ошиблись, и эта активность идёт не от вас. В таком случае, подтвердите, что вы не робот и продолжайте пользоваться нашим сайтом.
Оказалось, что можно просто перезапросить страницу и дальше продолжить скачивание изображений. Если же вам не нравится прикидываться роботом, то можно это обработать:
void MainWindow::handleCapcha(const QByteArray &page, const QUrl &url ) { ++m_capches; m_ui->webView->page()->setHtml(page, url); m_ui->captches->setText(QString::number(m_capches)); QEventLoop loop; constexpr int duration = 1000*60*5; QTimer::singleShot(duration, &loop, &QEventLoop::quit); loop.exec(); }
Тут загружаем в WebView страницу с капчей. После чего, можем ввести капчу.
Итого
Книга объемом 256 страниц в PDF со страницами A4 и DPI 96 весит 51,7 МБ против 5,8 МБ зашифрованного документа.
Код доступен на GitHubGist
