|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +""" |
| 5 | +Created on 2019年7月8日 |
| 6 | +@author: Irony |
| 7 | +@site: https://pyqt5.com https://github.com/PyQt5 |
| 8 | +@email: 892768447@qq.com |
| 9 | +@file: ScreenShotPage |
| 10 | +@description: 网页整体截图 |
| 11 | +""" |
| 12 | +import base64 |
| 13 | +import cgitb |
| 14 | +import os |
| 15 | +import sys |
| 16 | + |
| 17 | +from PyQt5.QtCore import QUrl, Qt, pyqtSlot, QSize, QTimer |
| 18 | +from PyQt5.QtGui import QImage, QPainter, QIcon, QPixmap |
| 19 | +from PyQt5.QtWebChannel import QWebChannel |
| 20 | +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings |
| 21 | +from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton,\ |
| 22 | + QGroupBox, QLineEdit, QHBoxLayout, QListWidget, QListWidgetItem,\ |
| 23 | + QProgressDialog |
| 24 | + |
| 25 | + |
| 26 | +__Author__ = "Irony" |
| 27 | +__Copyright__ = "Copyright (c) 2019" |
| 28 | +__Version__ = "Version 1.0" |
| 29 | + |
| 30 | +# 对部分内容进行截图 |
| 31 | +CODE = """ |
| 32 | +var el = $("%s"); |
| 33 | +html2canvas(el[0], { |
| 34 | + width: el.outerWidth(true), |
| 35 | + windowWidth: el.outerWidth(true), |
| 36 | +}).then(function(canvas) { |
| 37 | + window._self.saveImage(canvas.toDataURL()); |
| 38 | +}); |
| 39 | +""" |
| 40 | + |
| 41 | +# 创建交互桥梁脚本 |
| 42 | +CreateBridge = """ |
| 43 | +new QWebChannel(qt.webChannelTransport, |
| 44 | + function(channel) { |
| 45 | + window._self = channel.objects._self; |
| 46 | + } |
| 47 | +); |
| 48 | +""" |
| 49 | + |
| 50 | + |
| 51 | +class Window(QWidget): |
| 52 | + |
| 53 | + def __init__(self, *args, **kwargs): |
| 54 | + super(Window, self).__init__(*args, **kwargs) |
| 55 | + self.resize(600, 400) |
| 56 | + layout = QHBoxLayout(self) |
| 57 | + |
| 58 | + # 左侧 |
| 59 | + widgetLeft = QWidget(self) |
| 60 | + layoutLeft = QVBoxLayout(widgetLeft) |
| 61 | + # 右侧 |
| 62 | + self.widgetRight = QListWidget( |
| 63 | + self, minimumWidth=200, iconSize=QSize(150, 150)) |
| 64 | + self.widgetRight.setViewMode(QListWidget.IconMode) |
| 65 | + layout.addWidget(widgetLeft) |
| 66 | + layout.addWidget(self.widgetRight) |
| 67 | + |
| 68 | + self.webView = QWebEngineView() |
| 69 | + layoutLeft.addWidget(self.webView) |
| 70 | + |
| 71 | + # 截图方式一 |
| 72 | + groupBox1 = QGroupBox('截图方式一', self) |
| 73 | + layout1 = QVBoxLayout(groupBox1) |
| 74 | + layout1.addWidget(QPushButton('截图1', self, clicked=self.onScreenShot1)) |
| 75 | + layoutLeft.addWidget(groupBox1) |
| 76 | + |
| 77 | + # 截图方式二(采用js) |
| 78 | + groupBox2 = QGroupBox('截图方式二', self) |
| 79 | + layout2 = QVBoxLayout(groupBox2) |
| 80 | + self.codeEdit = QLineEdit( |
| 81 | + 'body', groupBox2, placeholderText='请输入需要截图的元素、ID或者class:如body、#id .class') |
| 82 | + layout2.addWidget(self.codeEdit) |
| 83 | + self.btnMethod2 = QPushButton( |
| 84 | + '', self, clicked=self.onScreenShot2, enabled=False) |
| 85 | + layout2.addWidget(self.btnMethod2) |
| 86 | + layoutLeft.addWidget(groupBox2) |
| 87 | + |
| 88 | + # 提供访问接口 |
| 89 | + self.channel = QWebChannel(self) |
| 90 | + # 把自身对象传递进去 |
| 91 | + self.channel.registerObject('_self', self) |
| 92 | + # 设置交互接口 |
| 93 | + self.webView.page().setWebChannel(self.channel) |
| 94 | + # 支持截图 |
| 95 | + settings = QWebEngineSettings.globalSettings() |
| 96 | + settings.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True) |
| 97 | + self.webView.loadStarted.connect(self.onLoadStarted) |
| 98 | + self.webView.loadFinished.connect(self.onLoadFinished) |
| 99 | + self.webView.load(QUrl("https://pyqt5.com")) |
| 100 | + |
| 101 | + def onLoadStarted(self): |
| 102 | + print('load started') |
| 103 | + self.btnMethod2.setEnabled(False) |
| 104 | + self.btnMethod2.setText('暂时无法使用(等待页面加载完成)') |
| 105 | + |
| 106 | + @pyqtSlot(bool) |
| 107 | + def onLoadFinished(self, finished): |
| 108 | + if not finished: |
| 109 | + return |
| 110 | + print('load finished') |
| 111 | + # 注入脚本 |
| 112 | + page = self.webView.page() |
| 113 | + # 执行qwebchannel,jquery,promise,html2canvas |
| 114 | + page.runJavaScript( |
| 115 | + open('Data/qwebchannel.js', 'rb').read().decode()) |
| 116 | + page.runJavaScript( |
| 117 | + open('Data/jquery.js', 'rb').read().decode()) |
| 118 | +# page.runJavaScript( |
| 119 | +# open('Data/promise-7.0.4.min.js', 'rb').read().decode()) |
| 120 | + page.runJavaScript( |
| 121 | + open('Data/html2canvas.min.js', 'rb').read().decode()) |
| 122 | + page.runJavaScript(CreateBridge) |
| 123 | + print('inject js ok') |
| 124 | + self.btnMethod2.setText('截图2') |
| 125 | + self.btnMethod2.setEnabled(True) |
| 126 | + |
| 127 | + def onScreenShot1(self): |
| 128 | + # 截图方式1 |
| 129 | + page = self.webView.page() |
| 130 | + oldSize = self.webView.size() |
| 131 | + self.webView.resize(page.contentsSize().toSize()) |
| 132 | + |
| 133 | + def doScreenShot(): |
| 134 | + rect = self.webView.contentsRect() |
| 135 | + size = rect.size() |
| 136 | + image = QImage(size, QImage.Format_ARGB32_Premultiplied) |
| 137 | + image.fill(Qt.transparent) |
| 138 | + |
| 139 | + painter = QPainter() |
| 140 | + painter.begin(image) |
| 141 | + painter.setRenderHint(QPainter.Antialiasing, True) |
| 142 | + painter.setRenderHint(QPainter.TextAntialiasing, True) |
| 143 | + painter.setRenderHint(QPainter.SmoothPixmapTransform, True) |
| 144 | + |
| 145 | + self.webView.render(painter) |
| 146 | + painter.end() |
| 147 | + self.webView.resize(oldSize) |
| 148 | + |
| 149 | + # 添加到左侧list中 |
| 150 | + item = QListWidgetItem(self.widgetRight) |
| 151 | + image = QPixmap.fromImage(image) |
| 152 | + item.setIcon(QIcon(image)) |
| 153 | + item.setData(Qt.UserRole + 1, image) |
| 154 | + |
| 155 | + # 先等一下再截图吧 |
| 156 | + QTimer.singleShot(2000, doScreenShot) |
| 157 | + |
| 158 | + def onScreenShot2(self): |
| 159 | + # 截图方式2 |
| 160 | + code = self.codeEdit.text().strip() |
| 161 | + if not code: |
| 162 | + return |
| 163 | + self.progressdialog = QProgressDialog(self, windowTitle='正在截图中') |
| 164 | + self.progressdialog.setRange(0, 0) |
| 165 | + self.webView.page().runJavaScript(CODE % code) |
| 166 | + self.progressdialog.exec_() |
| 167 | + |
| 168 | + @pyqtSlot(str) |
| 169 | + def saveImage(self, image): |
| 170 | + self.progressdialog.close() |
| 171 | + # data:image/png;base64,iVBORw0KG.... |
| 172 | + if not image.startswith('data:image'): |
| 173 | + return |
| 174 | + data = base64.b64decode(image.split(';base64,')[1]) |
| 175 | + image = QPixmap() |
| 176 | + image.loadFromData(data) |
| 177 | + # 添加到左侧list中 |
| 178 | + item = QListWidgetItem(self.widgetRight) |
| 179 | + item.setIcon(QIcon(image)) |
| 180 | + item.setData(Qt.UserRole + 1, image) |
| 181 | + |
| 182 | + |
| 183 | +if __name__ == "__main__": |
| 184 | + # 开启F12 控制台功能,需要单独通过浏览器打开这个页面 |
| 185 | + # 这里可以做个保护, 发布软件,启动时把这个环境变量删掉。防止他人通过环境变量开启 |
| 186 | + os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '9966' |
| 187 | + sys.excepthook = cgitb.enable(1, None, 5, '') |
| 188 | + app = QApplication(sys.argv) |
| 189 | + w = Window() |
| 190 | + w.show() |
| 191 | + |
| 192 | + # 打开调试页面 |
| 193 | + dw = QWebEngineView() |
| 194 | + dw.setWindowTitle('开发人员工具') |
| 195 | + dw.load(QUrl('http://127.0.0.1:9966')) |
| 196 | + dw.show() |
| 197 | + dw.move(100, 100) |
| 198 | + sys.exit(app.exec_()) |
0 commit comments