├── LICENSE ├── Qt └── WeChat │ ├── AddMe.txt │ ├── WeChat.pro │ ├── WeChat.pro.user │ ├── WeChat.py │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindow.ui │ ├── mergy.spec │ └── ui转py.bat ├── README.md ├── WeChat.py ├── icon.ico ├── main.py ├── requirements.txt ├── tk └── WeChat_tk.py ├── yf.png ├── 抓取文章图.png └── 效果图.png /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Qt/WeChat/AddMe.txt: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if __name__ == "__main__": 4 | app = QtWidgets.QApplication(sys.argv) 5 | MainWindow = QtWidgets.QMainWindow() 6 | ui = Ui_Form() 7 | ui.setupUi(MainWindow) 8 | MainWindow.show() 9 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /Qt/WeChat/WeChat.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2019-02-23T11:54:42 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui widgets 8 | 9 | TARGET = WeChat 10 | TEMPLATE = app 11 | 12 | # The following define makes your compiler emit warnings if you use 13 | # any feature of Qt which has been marked as deprecated (the exact warnings 14 | # depend on your compiler). Please consult the documentation of the 15 | # deprecated API in order to know how to port your code away from it. 16 | DEFINES += QT_DEPRECATED_WARNINGS 17 | 18 | # You can also make your code fail to compile if you use deprecated APIs. 19 | # In order to do so, uncomment the following line. 20 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 21 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 22 | 23 | CONFIG += c++11 24 | 25 | SOURCES += \ 26 | main.cpp \ 27 | mainwindow.cpp 28 | 29 | HEADERS += \ 30 | mainwindow.h 31 | 32 | FORMS += \ 33 | mainwindow.ui 34 | 35 | # Default rules for deployment. 36 | qnx: target.path = /tmp/$${TARGET}/bin 37 | else: unix:!android: target.path = /opt/$${TARGET}/bin 38 | !isEmpty(target.path): INSTALLS += target 39 | -------------------------------------------------------------------------------- /Qt/WeChat/WeChat.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {a59e6a57-da79-4cae-a44b-15af72be8378} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | true 45 | 0 46 | 8 47 | true 48 | 1 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | ProjectExplorer.Project.PluginSettings 57 | 58 | 59 | -fno-delayed-template-parsing 60 | 61 | true 62 | 63 | 64 | 65 | ProjectExplorer.Project.Target.0 66 | 67 | Desktop Qt 5.11.1 MinGW 32bit 68 | Desktop Qt 5.11.1 MinGW 32bit 69 | qt.qt5.5111.win32_mingw53_kit 70 | 0 71 | 0 72 | 0 73 | 74 | C:/Users/Administrator/Desktop/Qt/build-WeChat-Desktop_Qt_5_11_1_MinGW_32bit-Debug 75 | 76 | 77 | true 78 | qmake 79 | 80 | QtProjectManager.QMakeBuildStep 81 | true 82 | 83 | false 84 | false 85 | false 86 | 87 | 88 | true 89 | Make 90 | 91 | Qt4ProjectManager.MakeStep 92 | 93 | false 94 | 95 | 96 | 97 | 2 98 | Build 99 | 100 | ProjectExplorer.BuildSteps.Build 101 | 102 | 103 | 104 | true 105 | Make 106 | 107 | Qt4ProjectManager.MakeStep 108 | 109 | true 110 | clean 111 | 112 | 113 | 1 114 | Clean 115 | 116 | ProjectExplorer.BuildSteps.Clean 117 | 118 | 2 119 | false 120 | 121 | Debug 122 | Debug 123 | Qt4ProjectManager.Qt4BuildConfiguration 124 | 2 125 | true 126 | 127 | 128 | C:/Users/Administrator/Desktop/Qt/build-WeChat-Desktop_Qt_5_11_1_MinGW_32bit-Release 129 | 130 | 131 | true 132 | qmake 133 | 134 | QtProjectManager.QMakeBuildStep 135 | false 136 | 137 | false 138 | false 139 | true 140 | 141 | 142 | true 143 | Make 144 | 145 | Qt4ProjectManager.MakeStep 146 | 147 | false 148 | 149 | 150 | 151 | 2 152 | Build 153 | 154 | ProjectExplorer.BuildSteps.Build 155 | 156 | 157 | 158 | true 159 | Make 160 | 161 | Qt4ProjectManager.MakeStep 162 | 163 | true 164 | clean 165 | 166 | 167 | 1 168 | Clean 169 | 170 | ProjectExplorer.BuildSteps.Clean 171 | 172 | 2 173 | false 174 | 175 | Release 176 | Release 177 | Qt4ProjectManager.Qt4BuildConfiguration 178 | 0 179 | true 180 | 181 | 182 | C:/Users/Administrator/Desktop/Qt/build-WeChat-Desktop_Qt_5_11_1_MinGW_32bit-Profile 183 | 184 | 185 | true 186 | qmake 187 | 188 | QtProjectManager.QMakeBuildStep 189 | true 190 | 191 | false 192 | true 193 | true 194 | 195 | 196 | true 197 | Make 198 | 199 | Qt4ProjectManager.MakeStep 200 | 201 | false 202 | 203 | 204 | 205 | 2 206 | Build 207 | 208 | ProjectExplorer.BuildSteps.Build 209 | 210 | 211 | 212 | true 213 | Make 214 | 215 | Qt4ProjectManager.MakeStep 216 | 217 | true 218 | clean 219 | 220 | 221 | 1 222 | Clean 223 | 224 | ProjectExplorer.BuildSteps.Clean 225 | 226 | 2 227 | false 228 | 229 | Profile 230 | Profile 231 | Qt4ProjectManager.Qt4BuildConfiguration 232 | 0 233 | true 234 | 235 | 3 236 | 237 | 238 | 0 239 | 部署 240 | 241 | ProjectExplorer.BuildSteps.Deploy 242 | 243 | 1 244 | Deploy Configuration 245 | 246 | ProjectExplorer.DefaultDeployConfiguration 247 | 248 | 1 249 | 250 | 251 | false 252 | false 253 | 1000 254 | 255 | true 256 | 257 | false 258 | false 259 | false 260 | false 261 | true 262 | 0.01 263 | 10 264 | true 265 | 1 266 | 25 267 | 268 | 1 269 | true 270 | false 271 | true 272 | valgrind 273 | 274 | 0 275 | 1 276 | 2 277 | 3 278 | 4 279 | 5 280 | 6 281 | 7 282 | 8 283 | 9 284 | 10 285 | 11 286 | 12 287 | 13 288 | 14 289 | 290 | 2 291 | 292 | WeChat 293 | 294 | Qt4ProjectManager.Qt4RunConfiguration:C:/Users/Administrator/Desktop/Qt/WeChat/WeChat.pro 295 | true 296 | 297 | WeChat.pro 298 | 299 | C:/Users/Administrator/Desktop/Qt/build-WeChat-Desktop_Qt_5_11_1_MinGW_32bit-Debug 300 | 3768 301 | false 302 | true 303 | false 304 | false 305 | true 306 | 307 | 1 308 | 309 | 310 | 311 | ProjectExplorer.Project.TargetCount 312 | 1 313 | 314 | 315 | ProjectExplorer.Project.Updater.FileVersion 316 | 18 317 | 318 | 319 | Version 320 | 18 321 | 322 | 323 | -------------------------------------------------------------------------------- /Qt/WeChat/WeChat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'mainwindow.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.10 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_MainWindow(object): 15 | def setupUi(self, MainWindow): 16 | MainWindow.setObjectName("MainWindow") 17 | MainWindow.setEnabled(True) 18 | MainWindow.resize(787, 739) 19 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 20 | sizePolicy.setHorizontalStretch(0) 21 | sizePolicy.setVerticalStretch(0) 22 | sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) 23 | MainWindow.setSizePolicy(sizePolicy) 24 | MainWindow.setMinimumSize(QtCore.QSize(620, 520)) 25 | MainWindow.setMouseTracking(False) 26 | icon = QtGui.QIcon() 27 | icon.addPixmap(QtGui.QPixmap("../../icon.jpg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 28 | MainWindow.setWindowIcon(icon) 29 | self.centralWidget = QtWidgets.QWidget(MainWindow) 30 | self.centralWidget.setObjectName("centralWidget") 31 | self.gridLayout = QtWidgets.QGridLayout(self.centralWidget) 32 | self.gridLayout.setContentsMargins(11, 11, 11, 11) 33 | self.gridLayout.setSpacing(6) 34 | self.gridLayout.setObjectName("gridLayout") 35 | self.tabWidget = QtWidgets.QTabWidget(self.centralWidget) 36 | self.tabWidget.setObjectName("tabWidget") 37 | self.tab = QtWidgets.QWidget() 38 | self.tab.setObjectName("tab") 39 | self.layoutWidget = QtWidgets.QWidget(self.tab) 40 | self.layoutWidget.setGeometry(QtCore.QRect(10, 394, 715, 236)) 41 | self.layoutWidget.setObjectName("layoutWidget") 42 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget) 43 | self.horizontalLayout_2.setContentsMargins(11, 11, 11, 11) 44 | self.horizontalLayout_2.setSpacing(6) 45 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 46 | self.tableWidget_result = QtWidgets.QTableWidget(self.layoutWidget) 47 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 48 | sizePolicy.setHorizontalStretch(0) 49 | sizePolicy.setVerticalStretch(0) 50 | sizePolicy.setHeightForWidth(self.tableWidget_result.sizePolicy().hasHeightForWidth()) 51 | self.tableWidget_result.setSizePolicy(sizePolicy) 52 | self.tableWidget_result.viewport().setProperty("cursor", QtGui.QCursor(QtCore.Qt.IBeamCursor)) 53 | self.tableWidget_result.setAutoFillBackground(False) 54 | self.tableWidget_result.setFrameShape(QtWidgets.QFrame.StyledPanel) 55 | self.tableWidget_result.setFrameShadow(QtWidgets.QFrame.Sunken) 56 | self.tableWidget_result.setLineWidth(1) 57 | self.tableWidget_result.setMidLineWidth(1) 58 | self.tableWidget_result.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) 59 | self.tableWidget_result.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) 60 | self.tableWidget_result.setAutoScroll(True) 61 | self.tableWidget_result.setAlternatingRowColors(True) 62 | self.tableWidget_result.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) 63 | self.tableWidget_result.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) 64 | self.tableWidget_result.setGridStyle(QtCore.Qt.SolidLine) 65 | self.tableWidget_result.setRowCount(5) 66 | self.tableWidget_result.setColumnCount(2) 67 | self.tableWidget_result.setObjectName("tableWidget_result") 68 | item = QtWidgets.QTableWidgetItem() 69 | self.tableWidget_result.setHorizontalHeaderItem(0, item) 70 | item = QtWidgets.QTableWidgetItem() 71 | self.tableWidget_result.setHorizontalHeaderItem(1, item) 72 | self.tableWidget_result.horizontalHeader().setSortIndicatorShown(False) 73 | self.tableWidget_result.horizontalHeader().setStretchLastSection(True) 74 | self.tableWidget_result.verticalHeader().setCascadingSectionResizes(False) 75 | self.horizontalLayout_2.addWidget(self.tableWidget_result) 76 | spacerItem = QtWidgets.QSpacerItem(10, 20, QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) 77 | self.horizontalLayout_2.addItem(spacerItem) 78 | self.label_notes = QtWidgets.QLabel(self.layoutWidget) 79 | self.label_notes.setMinimumSize(QtCore.QSize(200, 25)) 80 | self.label_notes.setMaximumSize(QtCore.QSize(200, 16777215)) 81 | font = QtGui.QFont() 82 | font.setFamily("华文楷体") 83 | font.setPointSize(10) 84 | self.label_notes.setFont(font) 85 | self.label_notes.setAutoFillBackground(False) 86 | self.label_notes.setFrameShape(QtWidgets.QFrame.Panel) 87 | self.label_notes.setFrameShadow(QtWidgets.QFrame.Sunken) 88 | self.label_notes.setText("") 89 | self.label_notes.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) 90 | self.label_notes.setObjectName("label_notes") 91 | self.horizontalLayout_2.addWidget(self.label_notes) 92 | self.layoutWidget1 = QtWidgets.QWidget(self.tab) 93 | self.layoutWidget1.setGeometry(QtCore.QRect(10, 140, 715, 237)) 94 | self.layoutWidget1.setObjectName("layoutWidget1") 95 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget1) 96 | self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) 97 | self.horizontalLayout.setContentsMargins(11, 11, 11, 11) 98 | self.horizontalLayout.setSpacing(6) 99 | self.horizontalLayout.setObjectName("horizontalLayout") 100 | self.formLayout = QtWidgets.QFormLayout() 101 | self.formLayout.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) 102 | self.formLayout.setSpacing(6) 103 | self.formLayout.setObjectName("formLayout") 104 | self.Label_target = QtWidgets.QLabel(self.layoutWidget1) 105 | self.Label_target.setObjectName("Label_target") 106 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.Label_target) 107 | self.LineEdit_target = QtWidgets.QLineEdit(self.layoutWidget1) 108 | self.LineEdit_target.setMinimumSize(QtCore.QSize(200, 25)) 109 | self.LineEdit_target.setStatusTip("") 110 | self.LineEdit_target.setObjectName("LineEdit_target") 111 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.LineEdit_target) 112 | self.Label_user = QtWidgets.QLabel(self.layoutWidget1) 113 | self.Label_user.setLayoutDirection(QtCore.Qt.LeftToRight) 114 | self.Label_user.setObjectName("Label_user") 115 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.Label_user) 116 | self.LineEdit_user = QtWidgets.QLineEdit(self.layoutWidget1) 117 | self.LineEdit_user.setMinimumSize(QtCore.QSize(200, 25)) 118 | self.LineEdit_user.setObjectName("LineEdit_user") 119 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.LineEdit_user) 120 | self.Label_pwd = QtWidgets.QLabel(self.layoutWidget1) 121 | self.Label_pwd.setObjectName("Label_pwd") 122 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.Label_pwd) 123 | self.LineEdit_pwd = QtWidgets.QLineEdit(self.layoutWidget1) 124 | self.LineEdit_pwd.setMinimumSize(QtCore.QSize(200, 25)) 125 | self.LineEdit_pwd.setText("") 126 | self.LineEdit_pwd.setEchoMode(QtWidgets.QLineEdit.Password) 127 | self.LineEdit_pwd.setObjectName("LineEdit_pwd") 128 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.LineEdit_pwd) 129 | self.gapLabel = QtWidgets.QLabel(self.layoutWidget1) 130 | self.gapLabel.setObjectName("gapLabel") 131 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.gapLabel) 132 | self.LineEdit_timegap = QtWidgets.QLineEdit(self.layoutWidget1) 133 | self.LineEdit_timegap.setMinimumSize(QtCore.QSize(200, 25)) 134 | self.LineEdit_timegap.setObjectName("LineEdit_timegap") 135 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.LineEdit_timegap) 136 | self.wechatLabel = QtWidgets.QLabel(self.layoutWidget1) 137 | self.wechatLabel.setObjectName("wechatLabel") 138 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.wechatLabel) 139 | self.LineEdit_wechat = QtWidgets.QLineEdit(self.layoutWidget1) 140 | self.LineEdit_wechat.setMinimumSize(QtCore.QSize(200, 25)) 141 | self.LineEdit_wechat.setObjectName("LineEdit_wechat") 142 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.LineEdit_wechat) 143 | self.Label_time = QtWidgets.QLabel(self.layoutWidget1) 144 | self.Label_time.setObjectName("Label_time") 145 | self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.Label_time) 146 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 147 | self.horizontalLayout_3.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) 148 | self.horizontalLayout_3.setSpacing(6) 149 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 150 | self.lineEdit_timeStart = QtWidgets.QLineEdit(self.layoutWidget1) 151 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) 152 | sizePolicy.setHorizontalStretch(0) 153 | sizePolicy.setVerticalStretch(0) 154 | sizePolicy.setHeightForWidth(self.lineEdit_timeStart.sizePolicy().hasHeightForWidth()) 155 | self.lineEdit_timeStart.setSizePolicy(sizePolicy) 156 | self.lineEdit_timeStart.setMinimumSize(QtCore.QSize(20, 0)) 157 | self.lineEdit_timeStart.setAlignment(QtCore.Qt.AlignCenter) 158 | self.lineEdit_timeStart.setObjectName("lineEdit_timeStart") 159 | self.horizontalLayout_3.addWidget(self.lineEdit_timeStart) 160 | self.lineEdit_timeEnd = QtWidgets.QLineEdit(self.layoutWidget1) 161 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) 162 | sizePolicy.setHorizontalStretch(0) 163 | sizePolicy.setVerticalStretch(0) 164 | sizePolicy.setHeightForWidth(self.lineEdit_timeEnd.sizePolicy().hasHeightForWidth()) 165 | self.lineEdit_timeEnd.setSizePolicy(sizePolicy) 166 | self.lineEdit_timeEnd.setMinimumSize(QtCore.QSize(50, 0)) 167 | self.lineEdit_timeEnd.setAlignment(QtCore.Qt.AlignCenter) 168 | self.lineEdit_timeEnd.setObjectName("lineEdit_timeEnd") 169 | self.horizontalLayout_3.addWidget(self.lineEdit_timeEnd) 170 | spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 171 | self.horizontalLayout_3.addItem(spacerItem1) 172 | self.label = QtWidgets.QLabel(self.layoutWidget1) 173 | self.label.setMinimumSize(QtCore.QSize(60, 0)) 174 | self.label.setAlignment(QtCore.Qt.AlignCenter) 175 | self.label.setObjectName("label") 176 | self.horizontalLayout_3.addWidget(self.label) 177 | self.lineEdit_keyword = QtWidgets.QLineEdit(self.layoutWidget1) 178 | self.lineEdit_keyword.setMinimumSize(QtCore.QSize(20, 0)) 179 | self.lineEdit_keyword.setObjectName("lineEdit_keyword") 180 | self.horizontalLayout_3.addWidget(self.lineEdit_keyword) 181 | self.formLayout.setLayout(5, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3) 182 | self.progressBar = QtWidgets.QProgressBar(self.layoutWidget1) 183 | self.progressBar.setProperty("value", 0) 184 | self.progressBar.setObjectName("progressBar") 185 | self.formLayout.setWidget(7, QtWidgets.QFormLayout.SpanningRole, self.progressBar) 186 | self.label_total_Page = QtWidgets.QLabel(self.layoutWidget1) 187 | self.label_total_Page.setText("") 188 | self.label_total_Page.setObjectName("label_total_Page") 189 | self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_total_Page) 190 | self.horizontalLayout.addLayout(self.formLayout) 191 | spacerItem2 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 192 | self.horizontalLayout.addItem(spacerItem2) 193 | self.verticalLayout = QtWidgets.QVBoxLayout() 194 | self.verticalLayout.setContentsMargins(0, -1, -1, -1) 195 | self.verticalLayout.setSpacing(6) 196 | self.verticalLayout.setObjectName("verticalLayout") 197 | self.pushButton_start = QtWidgets.QPushButton(self.layoutWidget1) 198 | self.pushButton_start.setMinimumSize(QtCore.QSize(20, 50)) 199 | self.pushButton_start.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 200 | self.pushButton_start.setIconSize(QtCore.QSize(24, 24)) 201 | self.pushButton_start.setObjectName("pushButton_start") 202 | self.verticalLayout.addWidget(self.pushButton_start) 203 | self.checkBox = QtWidgets.QCheckBox(self.layoutWidget1) 204 | self.checkBox.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 205 | self.checkBox.setStatusTip("") 206 | self.checkBox.setAutoFillBackground(True) 207 | self.checkBox.setChecked(True) 208 | self.checkBox.setObjectName("checkBox") 209 | self.verticalLayout.addWidget(self.checkBox) 210 | self.pushButton_stop = QtWidgets.QPushButton(self.layoutWidget1) 211 | self.pushButton_stop.setMinimumSize(QtCore.QSize(20, 50)) 212 | self.pushButton_stop.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 213 | self.pushButton_stop.setObjectName("pushButton_stop") 214 | self.verticalLayout.addWidget(self.pushButton_stop) 215 | self.horizontalLayout.addLayout(self.verticalLayout) 216 | spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 217 | self.horizontalLayout.addItem(spacerItem3) 218 | self.label_head = QtWidgets.QLabel(self.tab) 219 | self.label_head.setGeometry(QtCore.QRect(10, 10, 715, 124)) 220 | self.label_head.setMinimumSize(QtCore.QSize(0, 120)) 221 | self.label_head.setObjectName("label_head") 222 | self.tabWidget.addTab(self.tab, "") 223 | self.tab_2 = QtWidgets.QWidget() 224 | self.tab_2.setObjectName("tab_2") 225 | self.Label_time_2 = QtWidgets.QLabel(self.tab_2) 226 | self.Label_time_2.setGeometry(QtCore.QRect(90, 190, 36, 21)) 227 | self.Label_time_2.setObjectName("Label_time_2") 228 | self.lineEdit_keyword_2 = QtWidgets.QLineEdit(self.tab_2) 229 | self.lineEdit_keyword_2.setGeometry(QtCore.QRect(130, 190, 158, 20)) 230 | self.lineEdit_keyword_2.setMinimumSize(QtCore.QSize(20, 0)) 231 | self.lineEdit_keyword_2.setObjectName("lineEdit_keyword_2") 232 | self.label_head_2 = QtWidgets.QLabel(self.tab_2) 233 | self.label_head_2.setGeometry(QtCore.QRect(10, 0, 715, 124)) 234 | self.label_head_2.setMinimumSize(QtCore.QSize(0, 120)) 235 | self.label_head_2.setObjectName("label_head_2") 236 | self.pushButton_stop_2 = QtWidgets.QPushButton(self.tab_2) 237 | self.pushButton_stop_2.setGeometry(QtCore.QRect(520, 230, 80, 50)) 238 | self.pushButton_stop_2.setMinimumSize(QtCore.QSize(20, 50)) 239 | self.pushButton_stop_2.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 240 | self.pushButton_stop_2.setObjectName("pushButton_stop_2") 241 | self.pushButton_start_2 = QtWidgets.QPushButton(self.tab_2) 242 | self.pushButton_start_2.setGeometry(QtCore.QRect(520, 154, 80, 50)) 243 | self.pushButton_start_2.setMinimumSize(QtCore.QSize(20, 50)) 244 | self.pushButton_start_2.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 245 | self.pushButton_start_2.setIconSize(QtCore.QSize(24, 24)) 246 | self.pushButton_start_2.setObjectName("pushButton_start_2") 247 | self.tabWidget.addTab(self.tab_2, "") 248 | self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) 249 | MainWindow.setCentralWidget(self.centralWidget) 250 | self.menuBar = QtWidgets.QMenuBar(MainWindow) 251 | self.menuBar.setGeometry(QtCore.QRect(0, 0, 787, 23)) 252 | self.menuBar.setObjectName("menuBar") 253 | MainWindow.setMenuBar(self.menuBar) 254 | self.mainToolBar = QtWidgets.QToolBar(MainWindow) 255 | self.mainToolBar.setObjectName("mainToolBar") 256 | MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar) 257 | 258 | self.retranslateUi(MainWindow) 259 | self.tabWidget.setCurrentIndex(0) 260 | self.pushButton_start.clicked.connect(MainWindow.Start_Run) # type: ignore 261 | self.pushButton_stop.clicked.connect(MainWindow.Stop_Run) # type: ignore 262 | self.pushButton_start_2.clicked.connect(MainWindow.Start_Run_2) # type: ignore 263 | self.pushButton_stop_2.clicked.connect(MainWindow.Stop_Run_2) # type: ignore 264 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 265 | 266 | def retranslateUi(self, MainWindow): 267 | _translate = QtCore.QCoreApplication.translate 268 | MainWindow.setWindowTitle(_translate("MainWindow", "微信公众号文章 by 小锋学长")) 269 | self.tableWidget_result.setSortingEnabled(False) 270 | item = self.tableWidget_result.horizontalHeaderItem(0) 271 | item.setText(_translate("MainWindow", "Title")) 272 | item = self.tableWidget_result.horizontalHeaderItem(1) 273 | item.setText(_translate("MainWindow", "URL")) 274 | self.label_notes.setWhatsThis(_translate("MainWindow", "调试窗口")) 275 | self.Label_target.setText(_translate("MainWindow", "目标公众号英文名")) 276 | self.LineEdit_target.setPlaceholderText(_translate("MainWindow", "为空则默认新华社(xinhuashefabu1)")) 277 | self.Label_user.setText(_translate("MainWindow", "个人公众号账号")) 278 | self.LineEdit_user.setPlaceholderText(_translate("MainWindow", "为空则自动打开页面后手动输入")) 279 | self.Label_pwd.setText(_translate("MainWindow", "个人公众号密码")) 280 | self.LineEdit_pwd.setPlaceholderText(_translate("MainWindow", "为空则自动打开页面后手动输入")) 281 | self.gapLabel.setText(_translate("MainWindow", "查询间隔(s)")) 282 | self.LineEdit_timegap.setPlaceholderText(_translate("MainWindow", "为空则默认为5s,一页约10条,越短越快被限制")) 283 | self.wechatLabel.setText(_translate("MainWindow", "微信uin和key")) 284 | self.LineEdit_wechat.setPlaceholderText(_translate("MainWindow", "URL全复制进来;下载评论和阅读数时需要,通过Fiddler抓包微信uin和key(约20分钟失效一次)")) 285 | self.Label_time.setText(_translate("MainWindow", "时间范围(年)")) 286 | self.lineEdit_timeStart.setPlaceholderText(_translate("MainWindow", "1999")) 287 | self.lineEdit_timeEnd.setPlaceholderText(_translate("MainWindow", "2019")) 288 | self.label.setText(_translate("MainWindow", "关键词")) 289 | self.pushButton_start.setText(_translate("MainWindow", "启动(*^▽^*)")) 290 | self.checkBox.setWhatsThis(_translate("MainWindow", "记住密码")) 291 | self.checkBox.setText(_translate("MainWindow", "记住密码")) 292 | self.pushButton_stop.setText(_translate("MainWindow", "终止 ̄へ ̄")) 293 | self.label_head.setText(_translate("MainWindow", "****************************************************************************************************\n" 294 | "* 程序原理:\n" 295 | ">> 通过selenium登录获取token和cookie,再自动爬取和下载\n" 296 | "* 使用前提: \n" 297 | ">> 申请一个微信公众号(https://mp.weixin.qq.com)\n" 298 | "开源链接:https://github.com/1061700625/WeChat_Article\n" 299 | " Copyright © SXF 本软件禁止一切形式的商业活动\n" 300 | "****************************************************************************************************")) 301 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", " 公众号搜文章 ")) 302 | self.Label_time_2.setText(_translate("MainWindow", "关键词")) 303 | self.label_head_2.setText(_translate("MainWindow", "****************************************************************************************************\n" 304 | "* demo说明:\n" 305 | ">> 现在“公众号搜文章”页填完整信息\n" 306 | ">> 再在本页填入关键词\n" 307 | ">> 点击“启动”即可\n" 308 | " Copyright © SXF 本软件禁止一切形式的商业活动\n" 309 | "****************************************************************************************************")) 310 | self.pushButton_stop_2.setText(_translate("MainWindow", "终止 ̄へ ̄")) 311 | self.pushButton_start_2.setText(_translate("MainWindow", "启动(*^▽^*)")) 312 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", " 关键词搜文章 ")) 313 | -------------------------------------------------------------------------------- /Qt/WeChat/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /Qt/WeChat/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | MainWindow::MainWindow(QWidget *parent) : 5 | QMainWindow(parent), 6 | ui(new Ui::MainWindow) 7 | { 8 | ui->setupUi(this); 9 | } 10 | 11 | MainWindow::~MainWindow() 12 | { 13 | delete ui; 14 | } 15 | -------------------------------------------------------------------------------- /Qt/WeChat/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class MainWindow; 8 | } 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = nullptr); 16 | ~MainWindow(); 17 | 18 | private: 19 | Ui::MainWindow *ui; 20 | }; 21 | 22 | #endif // MAINWINDOW_H 23 | -------------------------------------------------------------------------------- /Qt/WeChat/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | true 7 | 8 | 9 | 10 | 0 11 | 0 12 | 787 13 | 739 14 | 15 | 16 | 17 | 18 | 0 19 | 0 20 | 21 | 22 | 23 | 24 | 620 25 | 520 26 | 27 | 28 | 29 | false 30 | 31 | 32 | 微信公众号文章 by 小锋学长 33 | 34 | 35 | 36 | ../../icon.jpg../../icon.jpg 37 | 38 | 39 | 40 | 41 | 42 | 43 | 0 44 | 45 | 46 | 47 | 公众号搜文章 48 | 49 | 50 | 51 | 52 | 10 53 | 394 54 | 715 55 | 236 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 0 64 | 0 65 | 66 | 67 | 68 | IBeamCursor 69 | 70 | 71 | false 72 | 73 | 74 | QFrame::StyledPanel 75 | 76 | 77 | QFrame::Sunken 78 | 79 | 80 | 1 81 | 82 | 83 | 1 84 | 85 | 86 | Qt::ScrollBarAsNeeded 87 | 88 | 89 | QAbstractScrollArea::AdjustToContents 90 | 91 | 92 | true 93 | 94 | 95 | true 96 | 97 | 98 | QAbstractItemView::ScrollPerPixel 99 | 100 | 101 | QAbstractItemView::ScrollPerPixel 102 | 103 | 104 | Qt::SolidLine 105 | 106 | 107 | false 108 | 109 | 110 | 5 111 | 112 | 113 | 2 114 | 115 | 116 | false 117 | 118 | 119 | true 120 | 121 | 122 | false 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | Title 132 | 133 | 134 | 135 | 136 | URL 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | Qt::Horizontal 145 | 146 | 147 | QSizePolicy::Preferred 148 | 149 | 150 | 151 | 10 152 | 20 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 200 162 | 25 163 | 164 | 165 | 166 | 167 | 200 168 | 16777215 169 | 170 | 171 | 172 | 173 | 华文楷体 174 | 10 175 | 176 | 177 | 178 | 调试窗口 179 | 180 | 181 | false 182 | 183 | 184 | QFrame::Panel 185 | 186 | 187 | QFrame::Sunken 188 | 189 | 190 | 191 | 192 | 193 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 10 203 | 140 204 | 715 205 | 237 206 | 207 | 208 | 209 | 210 | QLayout::SetDefaultConstraint 211 | 212 | 213 | 214 | 215 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 216 | 217 | 218 | 219 | 220 | 目标公众号英文名 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 200 229 | 25 230 | 231 | 232 | 233 | 234 | 235 | 236 | 为空则默认新华社(xinhuashefabu1) 237 | 238 | 239 | 240 | 241 | 242 | 243 | Qt::LeftToRight 244 | 245 | 246 | 个人公众号账号 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 200 255 | 25 256 | 257 | 258 | 259 | 为空则自动打开页面后手动输入 260 | 261 | 262 | 263 | 264 | 265 | 266 | 个人公众号密码 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 200 275 | 25 276 | 277 | 278 | 279 | 280 | 281 | 282 | QLineEdit::Password 283 | 284 | 285 | 为空则自动打开页面后手动输入 286 | 287 | 288 | 289 | 290 | 291 | 292 | 查询间隔(s) 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 200 301 | 25 302 | 303 | 304 | 305 | 为空则默认为5s,一页约10条,越短越快被限制 306 | 307 | 308 | 309 | 310 | 311 | 312 | 微信uin和key 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 200 321 | 25 322 | 323 | 324 | 325 | URL全复制进来;下载评论和阅读数时需要,通过Fiddler抓包微信uin和key(约20分钟失效一次) 326 | 327 | 328 | 329 | 330 | 331 | 332 | 时间范围(年) 333 | 334 | 335 | 336 | 337 | 338 | 339 | 6 340 | 341 | 342 | QLayout::SetDefaultConstraint 343 | 344 | 345 | 346 | 347 | 348 | 0 349 | 0 350 | 351 | 352 | 353 | 354 | 20 355 | 0 356 | 357 | 358 | 359 | Qt::AlignCenter 360 | 361 | 362 | 1999 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 0 371 | 0 372 | 373 | 374 | 375 | 376 | 50 377 | 0 378 | 379 | 380 | 381 | Qt::AlignCenter 382 | 383 | 384 | 2019 385 | 386 | 387 | 388 | 389 | 390 | 391 | Qt::Horizontal 392 | 393 | 394 | 395 | 40 396 | 20 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 60 406 | 0 407 | 408 | 409 | 410 | 关键词 411 | 412 | 413 | Qt::AlignCenter 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 20 422 | 0 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 0 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | Qt::Horizontal 449 | 450 | 451 | 452 | 20 453 | 20 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 0 462 | 463 | 464 | 465 | 466 | 467 | 20 468 | 50 469 | 470 | 471 | 472 | PointingHandCursor 473 | 474 | 475 | 启动(*^▽^*) 476 | 477 | 478 | 479 | 24 480 | 24 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | PointingHandCursor 489 | 490 | 491 | 492 | 493 | 494 | 记住密码 495 | 496 | 497 | true 498 | 499 | 500 | 记住密码 501 | 502 | 503 | true 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 20 512 | 50 513 | 514 | 515 | 516 | PointingHandCursor 517 | 518 | 519 | 终止 ̄へ ̄ 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | Qt::Horizontal 529 | 530 | 531 | 532 | 20 533 | 20 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 10 544 | 10 545 | 715 546 | 124 547 | 548 | 549 | 550 | 551 | 0 552 | 120 553 | 554 | 555 | 556 | **************************************************************************************************** 557 | * 程序原理: 558 | >> 通过selenium登录获取token和cookie,再自动爬取和下载 559 | * 使用前提: 560 | >> 申请一个微信公众号(https://mp.weixin.qq.com) 561 | 开源链接:https://github.com/1061700625/WeChat_Article 562 | Copyright © SXF 本软件禁止一切形式的商业活动 563 | **************************************************************************************************** 564 | 565 | 566 | 567 | 568 | 569 | 关键词搜文章 570 | 571 | 572 | 573 | 574 | 90 575 | 190 576 | 36 577 | 21 578 | 579 | 580 | 581 | 关键词 582 | 583 | 584 | 585 | 586 | 587 | 130 588 | 190 589 | 158 590 | 20 591 | 592 | 593 | 594 | 595 | 20 596 | 0 597 | 598 | 599 | 600 | 601 | 602 | 603 | 10 604 | 0 605 | 715 606 | 124 607 | 608 | 609 | 610 | 611 | 0 612 | 120 613 | 614 | 615 | 616 | **************************************************************************************************** 617 | * demo说明: 618 | >> 现在“公众号搜文章”页填完整信息 619 | >> 再在本页填入关键词 620 | >> 点击“启动”即可 621 | Copyright © SXF 本软件禁止一切形式的商业活动 622 | **************************************************************************************************** 623 | 624 | 625 | 626 | 627 | 628 | 520 629 | 230 630 | 80 631 | 50 632 | 633 | 634 | 635 | 636 | 20 637 | 50 638 | 639 | 640 | 641 | PointingHandCursor 642 | 643 | 644 | 终止 ̄へ ̄ 645 | 646 | 647 | 648 | 649 | 650 | 520 651 | 154 652 | 80 653 | 50 654 | 655 | 656 | 657 | 658 | 20 659 | 50 660 | 661 | 662 | 663 | PointingHandCursor 664 | 665 | 666 | 启动(*^▽^*) 667 | 668 | 669 | 670 | 24 671 | 24 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 0 684 | 0 685 | 787 686 | 23 687 | 688 | 689 | 690 | 691 | 692 | TopToolBarArea 693 | 694 | 695 | false 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | pushButton_start 704 | clicked() 705 | MainWindow 706 | Start_Run() 707 | 708 | 709 | 707 710 | 282 711 | 712 | 713 | 396 714 | 104 715 | 716 | 717 | 718 | 719 | pushButton_stop 720 | clicked() 721 | MainWindow 722 | Stop_Run() 723 | 724 | 725 | 707 726 | 412 727 | 728 | 729 | 471 730 | 241 731 | 732 | 733 | 734 | 735 | pushButton_start_2 736 | clicked() 737 | MainWindow 738 | Start_Run_2() 739 | 740 | 741 | 593 742 | 251 743 | 744 | 745 | 786 746 | 250 747 | 748 | 749 | 750 | 751 | pushButton_stop_2 752 | clicked() 753 | MainWindow 754 | Stop_Run_2() 755 | 756 | 757 | 587 758 | 312 759 | 760 | 761 | 801 762 | 301 763 | 764 | 765 | 766 | 767 | 768 | Start_Run() 769 | Stop_Run() 770 | Table_Update() 771 | Start_Run_2() 772 | Stop_Run_2() 773 | 774 | 775 | -------------------------------------------------------------------------------- /Qt/WeChat/mergy.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | a = Analysis(['mergy.py'], 7 | pathex=['C:\\Users\\Administrator\\Desktop\\Qt\\WeChat'], 8 | binaries=[], 9 | datas=[], 10 | hiddenimports=[], 11 | hookspath=[], 12 | runtime_hooks=[], 13 | excludes=[], 14 | win_no_prefer_redirects=False, 15 | win_private_assemblies=False, 16 | cipher=block_cipher, 17 | noarchive=False) 18 | pyz = PYZ(a.pure, a.zipped_data, 19 | cipher=block_cipher) 20 | exe = EXE(pyz, 21 | a.scripts, 22 | a.binaries, 23 | a.zipfiles, 24 | a.datas, 25 | [], 26 | name='mergy', 27 | debug=False, 28 | bootloader_ignore_signals=False, 29 | strip=False, 30 | upx=True, 31 | runtime_tmpdir=None, 32 | console=True , icon='C:\\Users\\Administrator\\Desktop\\icon.ico') 33 | -------------------------------------------------------------------------------- /Qt/WeChat/ui转py.bat: -------------------------------------------------------------------------------- 1 | pyuic5 -o WeChat.py mainwindow.ui -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WeChat_Article 2 | 爬取微信公众号文章 3 | 4 | > Bilibili视频演示:https://www.bilibili.com/video/BV1vN411D7Y3/ 5 | 6 | **注意,除非你要断点续传,否则删除目录下conf.ini和url.json再启动!!!!** 7 | 8 | ![image](https://user-images.githubusercontent.com/31002981/217465357-d0737b23-55ec-47d3-b12c-ee8973a04291.png) 9 | 10 | 11 | ## 使用方法: 12 | 1、下载并解压[**Chrome.rar**](https://sxf1024.lanzouo.com/iJ2Rp0mwy50j); 13 | 2、下载并解压[default.zip](https://github.com/1061700625/WeChat_Article/releases); 14 | 3、将解压的Chrome放到解压的default里; 15 | 4、进入default目录,运行**main.exe**; 16 | 5、填入信息,点击“**启动**”即可。 17 | 6、如果想修改UI,可以安装这个:[Qt Designer](https://build-system.fman.io/qt-designer-download) 18 | **************************************************************************************************** 19 | 20 | ## 背景知识: 21 | 使用公众号写文章时支持搜索其他公众号的文章的方式,来实现爬取指定公众号所有文章的目的。 22 | **************************************************************************************************** 23 | 24 | ## 程序原理: 25 | 通过selenium登录获取token和cookie,再自动爬取和下载 26 | * 使用前提: 27 | 1、申请一个免费的微信公众号,个人订阅号即可(https://mp.weixin.qq.com) 28 | **************************************************************************************************** 29 | 30 | ## 更新记录: 31 | 1. 下载文章文字内容到txt 32 | 2. 下载文章图片 33 | 3. 保存HTML文件,并将图片链接指向本地 34 | 4. 添加按时间范围下载 35 | 5. 添加cookie登陆,不成功才selenium浏览器登陆 36 | 6. 增加记住密码功能 37 | 7. 修复一些问题,如requests卡死 38 | 8. 添加按关键词下载 39 | 9. 多线程优化下载速度 40 | 10. 增加断点续传功能(可能存在bug,推荐不要用) 41 | 11. 拟增加备用公众号功能(暂未完成) 42 | 12. 下载PDF格式 43 | 13. 不需要再手动下载Chrome,启动时会自动下载 44 | **************************************************************************************************** 45 | 46 | ## 使用说明: 47 | 创建虚拟环境 48 | ```bash 49 | conda create -n wechat python=3.9 -y 50 | ``` 51 | 52 | 进入虚拟环境 53 | ```bash 54 | conda activate wechat 55 | ``` 56 | 57 | 安装三方库 58 | ```bash 59 | pip install -r requirements.txt 60 | ``` 61 | > 对于mac用户,安装pyqt5可能会报错,可以尝试: 62 | > ```bash 63 | > brew install pyqt@5 64 | > cp -r /opt/homebrew/Cellar/pyqt@5/5.15.7_2/lib/python3.9/site-packages/* /Users/songxf/miniconda3/envs/wechat/lib/python3.9/site-packages/ 65 | > ``` 66 | > 然后就可以导入了: 67 | > ```bash 68 | > import PyQt5 69 | > ``` 70 | 71 | 运行脚本 72 | ```bash 73 | python main.py 74 | ``` 75 | 76 | 打包exe(生成在dist下) 77 | ```bash 78 | pyinstaller -F -w -i icon.ico main.py 79 | ``` 80 | 81 | 82 | ## 其他说明: 83 | - 爬取间隔太快,容易遇到“**访问频繁**”或“**freq_control**”,这时候可以删除**cookie.json**,再重新运行软件,**换个号**继续运行; 84 | - Qt打包完实在是太大了,有大佬会转成Tkinter吗? 85 | 86 | 87 | 欢迎关注微信公众号:xfxuezhang 88 | 89 | # 相关项目 90 | > 感谢大佬们的贡献 ♪(・ω・)ノ 91 | - [web版](https://github.com/wechat-article/wechat-article-exporter) 92 | - [QT6版](https://github.com/wooodypan/WeChat_Article) 93 | 94 | 95 | 96 | --- 97 | ## 打赏 98 | 如果这个项目帮助到了你,欢迎请我喝杯阔落👏🏻 99 | ![yf](yf.png) 100 | 101 | -------------------------------------------------------------------------------- /WeChat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'mainwindow.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.10 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_MainWindow(object): 15 | def setupUi(self, MainWindow): 16 | MainWindow.setObjectName("MainWindow") 17 | MainWindow.setEnabled(True) 18 | MainWindow.resize(787, 739) 19 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 20 | sizePolicy.setHorizontalStretch(0) 21 | sizePolicy.setVerticalStretch(0) 22 | sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) 23 | MainWindow.setSizePolicy(sizePolicy) 24 | MainWindow.setMinimumSize(QtCore.QSize(620, 520)) 25 | MainWindow.setMouseTracking(False) 26 | icon = QtGui.QIcon() 27 | icon.addPixmap(QtGui.QPixmap("../../icon.jpg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 28 | MainWindow.setWindowIcon(icon) 29 | self.centralWidget = QtWidgets.QWidget(MainWindow) 30 | self.centralWidget.setObjectName("centralWidget") 31 | self.gridLayout = QtWidgets.QGridLayout(self.centralWidget) 32 | self.gridLayout.setContentsMargins(11, 11, 11, 11) 33 | self.gridLayout.setSpacing(6) 34 | self.gridLayout.setObjectName("gridLayout") 35 | self.tabWidget = QtWidgets.QTabWidget(self.centralWidget) 36 | self.tabWidget.setObjectName("tabWidget") 37 | self.tab = QtWidgets.QWidget() 38 | self.tab.setObjectName("tab") 39 | self.layoutWidget = QtWidgets.QWidget(self.tab) 40 | self.layoutWidget.setGeometry(QtCore.QRect(10, 394, 715, 236)) 41 | self.layoutWidget.setObjectName("layoutWidget") 42 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget) 43 | self.horizontalLayout_2.setContentsMargins(11, 11, 11, 11) 44 | self.horizontalLayout_2.setSpacing(6) 45 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 46 | self.tableWidget_result = QtWidgets.QTableWidget(self.layoutWidget) 47 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 48 | sizePolicy.setHorizontalStretch(0) 49 | sizePolicy.setVerticalStretch(0) 50 | sizePolicy.setHeightForWidth(self.tableWidget_result.sizePolicy().hasHeightForWidth()) 51 | self.tableWidget_result.setSizePolicy(sizePolicy) 52 | self.tableWidget_result.viewport().setProperty("cursor", QtGui.QCursor(QtCore.Qt.IBeamCursor)) 53 | self.tableWidget_result.setAutoFillBackground(False) 54 | self.tableWidget_result.setFrameShape(QtWidgets.QFrame.StyledPanel) 55 | self.tableWidget_result.setFrameShadow(QtWidgets.QFrame.Sunken) 56 | self.tableWidget_result.setLineWidth(1) 57 | self.tableWidget_result.setMidLineWidth(1) 58 | self.tableWidget_result.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) 59 | self.tableWidget_result.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) 60 | self.tableWidget_result.setAutoScroll(True) 61 | self.tableWidget_result.setAlternatingRowColors(True) 62 | self.tableWidget_result.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) 63 | self.tableWidget_result.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) 64 | self.tableWidget_result.setGridStyle(QtCore.Qt.SolidLine) 65 | self.tableWidget_result.setRowCount(5) 66 | self.tableWidget_result.setColumnCount(2) 67 | self.tableWidget_result.setObjectName("tableWidget_result") 68 | item = QtWidgets.QTableWidgetItem() 69 | self.tableWidget_result.setHorizontalHeaderItem(0, item) 70 | item = QtWidgets.QTableWidgetItem() 71 | self.tableWidget_result.setHorizontalHeaderItem(1, item) 72 | self.tableWidget_result.horizontalHeader().setSortIndicatorShown(False) 73 | self.tableWidget_result.horizontalHeader().setStretchLastSection(True) 74 | self.tableWidget_result.verticalHeader().setCascadingSectionResizes(False) 75 | self.horizontalLayout_2.addWidget(self.tableWidget_result) 76 | spacerItem = QtWidgets.QSpacerItem(10, 20, QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) 77 | self.horizontalLayout_2.addItem(spacerItem) 78 | self.label_notes = QtWidgets.QLabel(self.layoutWidget) 79 | self.label_notes.setMinimumSize(QtCore.QSize(200, 25)) 80 | self.label_notes.setMaximumSize(QtCore.QSize(200, 16777215)) 81 | font = QtGui.QFont() 82 | font.setFamily("华文楷体") 83 | font.setPointSize(10) 84 | self.label_notes.setFont(font) 85 | self.label_notes.setAutoFillBackground(False) 86 | self.label_notes.setFrameShape(QtWidgets.QFrame.Panel) 87 | self.label_notes.setFrameShadow(QtWidgets.QFrame.Sunken) 88 | self.label_notes.setText("") 89 | self.label_notes.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) 90 | self.label_notes.setObjectName("label_notes") 91 | self.horizontalLayout_2.addWidget(self.label_notes) 92 | self.layoutWidget1 = QtWidgets.QWidget(self.tab) 93 | self.layoutWidget1.setGeometry(QtCore.QRect(10, 140, 715, 237)) 94 | self.layoutWidget1.setObjectName("layoutWidget1") 95 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget1) 96 | self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) 97 | self.horizontalLayout.setContentsMargins(11, 11, 11, 11) 98 | self.horizontalLayout.setSpacing(6) 99 | self.horizontalLayout.setObjectName("horizontalLayout") 100 | self.formLayout = QtWidgets.QFormLayout() 101 | self.formLayout.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) 102 | self.formLayout.setSpacing(6) 103 | self.formLayout.setObjectName("formLayout") 104 | self.Label_target = QtWidgets.QLabel(self.layoutWidget1) 105 | self.Label_target.setObjectName("Label_target") 106 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.Label_target) 107 | self.LineEdit_target = QtWidgets.QLineEdit(self.layoutWidget1) 108 | self.LineEdit_target.setMinimumSize(QtCore.QSize(200, 25)) 109 | self.LineEdit_target.setStatusTip("") 110 | self.LineEdit_target.setObjectName("LineEdit_target") 111 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.LineEdit_target) 112 | self.Label_user = QtWidgets.QLabel(self.layoutWidget1) 113 | self.Label_user.setLayoutDirection(QtCore.Qt.LeftToRight) 114 | self.Label_user.setObjectName("Label_user") 115 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.Label_user) 116 | self.LineEdit_user = QtWidgets.QLineEdit(self.layoutWidget1) 117 | self.LineEdit_user.setMinimumSize(QtCore.QSize(200, 25)) 118 | self.LineEdit_user.setObjectName("LineEdit_user") 119 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.LineEdit_user) 120 | self.Label_pwd = QtWidgets.QLabel(self.layoutWidget1) 121 | self.Label_pwd.setObjectName("Label_pwd") 122 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.Label_pwd) 123 | self.LineEdit_pwd = QtWidgets.QLineEdit(self.layoutWidget1) 124 | self.LineEdit_pwd.setMinimumSize(QtCore.QSize(200, 25)) 125 | self.LineEdit_pwd.setText("") 126 | self.LineEdit_pwd.setEchoMode(QtWidgets.QLineEdit.Password) 127 | self.LineEdit_pwd.setObjectName("LineEdit_pwd") 128 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.LineEdit_pwd) 129 | self.gapLabel = QtWidgets.QLabel(self.layoutWidget1) 130 | self.gapLabel.setObjectName("gapLabel") 131 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.gapLabel) 132 | self.LineEdit_timegap = QtWidgets.QLineEdit(self.layoutWidget1) 133 | self.LineEdit_timegap.setMinimumSize(QtCore.QSize(200, 25)) 134 | self.LineEdit_timegap.setObjectName("LineEdit_timegap") 135 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.LineEdit_timegap) 136 | self.wechatLabel = QtWidgets.QLabel(self.layoutWidget1) 137 | self.wechatLabel.setObjectName("wechatLabel") 138 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.wechatLabel) 139 | self.LineEdit_wechat = QtWidgets.QLineEdit(self.layoutWidget1) 140 | self.LineEdit_wechat.setMinimumSize(QtCore.QSize(200, 25)) 141 | self.LineEdit_wechat.setObjectName("LineEdit_wechat") 142 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.LineEdit_wechat) 143 | self.Label_time = QtWidgets.QLabel(self.layoutWidget1) 144 | self.Label_time.setObjectName("Label_time") 145 | self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.Label_time) 146 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 147 | self.horizontalLayout_3.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) 148 | self.horizontalLayout_3.setSpacing(6) 149 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 150 | self.lineEdit_timeStart = QtWidgets.QLineEdit(self.layoutWidget1) 151 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) 152 | sizePolicy.setHorizontalStretch(0) 153 | sizePolicy.setVerticalStretch(0) 154 | sizePolicy.setHeightForWidth(self.lineEdit_timeStart.sizePolicy().hasHeightForWidth()) 155 | self.lineEdit_timeStart.setSizePolicy(sizePolicy) 156 | self.lineEdit_timeStart.setMinimumSize(QtCore.QSize(20, 0)) 157 | self.lineEdit_timeStart.setAlignment(QtCore.Qt.AlignCenter) 158 | self.lineEdit_timeStart.setObjectName("lineEdit_timeStart") 159 | self.horizontalLayout_3.addWidget(self.lineEdit_timeStart) 160 | self.lineEdit_timeEnd = QtWidgets.QLineEdit(self.layoutWidget1) 161 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) 162 | sizePolicy.setHorizontalStretch(0) 163 | sizePolicy.setVerticalStretch(0) 164 | sizePolicy.setHeightForWidth(self.lineEdit_timeEnd.sizePolicy().hasHeightForWidth()) 165 | self.lineEdit_timeEnd.setSizePolicy(sizePolicy) 166 | self.lineEdit_timeEnd.setMinimumSize(QtCore.QSize(50, 0)) 167 | self.lineEdit_timeEnd.setAlignment(QtCore.Qt.AlignCenter) 168 | self.lineEdit_timeEnd.setObjectName("lineEdit_timeEnd") 169 | self.horizontalLayout_3.addWidget(self.lineEdit_timeEnd) 170 | spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 171 | self.horizontalLayout_3.addItem(spacerItem1) 172 | self.label = QtWidgets.QLabel(self.layoutWidget1) 173 | self.label.setMinimumSize(QtCore.QSize(60, 0)) 174 | self.label.setAlignment(QtCore.Qt.AlignCenter) 175 | self.label.setObjectName("label") 176 | self.horizontalLayout_3.addWidget(self.label) 177 | self.lineEdit_keyword = QtWidgets.QLineEdit(self.layoutWidget1) 178 | self.lineEdit_keyword.setMinimumSize(QtCore.QSize(20, 0)) 179 | self.lineEdit_keyword.setObjectName("lineEdit_keyword") 180 | self.horizontalLayout_3.addWidget(self.lineEdit_keyword) 181 | self.formLayout.setLayout(5, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3) 182 | self.progressBar = QtWidgets.QProgressBar(self.layoutWidget1) 183 | self.progressBar.setProperty("value", 0) 184 | self.progressBar.setObjectName("progressBar") 185 | self.formLayout.setWidget(7, QtWidgets.QFormLayout.SpanningRole, self.progressBar) 186 | self.label_total_Page = QtWidgets.QLabel(self.layoutWidget1) 187 | self.label_total_Page.setText("") 188 | self.label_total_Page.setObjectName("label_total_Page") 189 | self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_total_Page) 190 | self.horizontalLayout.addLayout(self.formLayout) 191 | spacerItem2 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 192 | self.horizontalLayout.addItem(spacerItem2) 193 | self.verticalLayout = QtWidgets.QVBoxLayout() 194 | self.verticalLayout.setContentsMargins(0, -1, -1, -1) 195 | self.verticalLayout.setSpacing(6) 196 | self.verticalLayout.setObjectName("verticalLayout") 197 | self.pushButton_start = QtWidgets.QPushButton(self.layoutWidget1) 198 | self.pushButton_start.setMinimumSize(QtCore.QSize(20, 50)) 199 | self.pushButton_start.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 200 | self.pushButton_start.setIconSize(QtCore.QSize(24, 24)) 201 | self.pushButton_start.setObjectName("pushButton_start") 202 | self.verticalLayout.addWidget(self.pushButton_start) 203 | self.checkBox = QtWidgets.QCheckBox(self.layoutWidget1) 204 | self.checkBox.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 205 | self.checkBox.setStatusTip("") 206 | self.checkBox.setAutoFillBackground(True) 207 | self.checkBox.setChecked(True) 208 | self.checkBox.setObjectName("checkBox") 209 | self.verticalLayout.addWidget(self.checkBox) 210 | self.pushButton_stop = QtWidgets.QPushButton(self.layoutWidget1) 211 | self.pushButton_stop.setMinimumSize(QtCore.QSize(20, 50)) 212 | self.pushButton_stop.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 213 | self.pushButton_stop.setObjectName("pushButton_stop") 214 | self.verticalLayout.addWidget(self.pushButton_stop) 215 | self.horizontalLayout.addLayout(self.verticalLayout) 216 | spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 217 | self.horizontalLayout.addItem(spacerItem3) 218 | self.label_head = QtWidgets.QLabel(self.tab) 219 | self.label_head.setGeometry(QtCore.QRect(10, 10, 715, 124)) 220 | self.label_head.setMinimumSize(QtCore.QSize(0, 120)) 221 | self.label_head.setObjectName("label_head") 222 | self.tabWidget.addTab(self.tab, "") 223 | self.tab_2 = QtWidgets.QWidget() 224 | self.tab_2.setObjectName("tab_2") 225 | self.Label_time_2 = QtWidgets.QLabel(self.tab_2) 226 | self.Label_time_2.setGeometry(QtCore.QRect(90, 190, 36, 21)) 227 | self.Label_time_2.setObjectName("Label_time_2") 228 | self.lineEdit_keyword_2 = QtWidgets.QLineEdit(self.tab_2) 229 | self.lineEdit_keyword_2.setGeometry(QtCore.QRect(130, 190, 158, 20)) 230 | self.lineEdit_keyword_2.setMinimumSize(QtCore.QSize(20, 0)) 231 | self.lineEdit_keyword_2.setObjectName("lineEdit_keyword_2") 232 | self.label_head_2 = QtWidgets.QLabel(self.tab_2) 233 | self.label_head_2.setGeometry(QtCore.QRect(10, 0, 715, 124)) 234 | self.label_head_2.setMinimumSize(QtCore.QSize(0, 120)) 235 | self.label_head_2.setObjectName("label_head_2") 236 | self.pushButton_stop_2 = QtWidgets.QPushButton(self.tab_2) 237 | self.pushButton_stop_2.setGeometry(QtCore.QRect(520, 230, 80, 50)) 238 | self.pushButton_stop_2.setMinimumSize(QtCore.QSize(20, 50)) 239 | self.pushButton_stop_2.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 240 | self.pushButton_stop_2.setObjectName("pushButton_stop_2") 241 | self.pushButton_start_2 = QtWidgets.QPushButton(self.tab_2) 242 | self.pushButton_start_2.setGeometry(QtCore.QRect(520, 154, 80, 50)) 243 | self.pushButton_start_2.setMinimumSize(QtCore.QSize(20, 50)) 244 | self.pushButton_start_2.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 245 | self.pushButton_start_2.setIconSize(QtCore.QSize(24, 24)) 246 | self.pushButton_start_2.setObjectName("pushButton_start_2") 247 | self.tabWidget.addTab(self.tab_2, "") 248 | self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) 249 | MainWindow.setCentralWidget(self.centralWidget) 250 | self.menuBar = QtWidgets.QMenuBar(MainWindow) 251 | self.menuBar.setGeometry(QtCore.QRect(0, 0, 787, 23)) 252 | self.menuBar.setObjectName("menuBar") 253 | MainWindow.setMenuBar(self.menuBar) 254 | self.mainToolBar = QtWidgets.QToolBar(MainWindow) 255 | self.mainToolBar.setObjectName("mainToolBar") 256 | MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.mainToolBar) 257 | 258 | self.retranslateUi(MainWindow) 259 | self.tabWidget.setCurrentIndex(0) 260 | self.pushButton_start.clicked.connect(self.Start_Run) # type: ignore 261 | self.pushButton_stop.clicked.connect(self.Stop_Run) # type: ignore 262 | self.pushButton_start_2.clicked.connect(self.Start_Run_2) # type: ignore 263 | self.pushButton_stop_2.clicked.connect(self.Stop_Run_2) # type: ignore 264 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 265 | 266 | def retranslateUi(self, MainWindow): 267 | _translate = QtCore.QCoreApplication.translate 268 | MainWindow.setWindowTitle(_translate("MainWindow", "微信公众号文章 by 小锋学长")) 269 | self.tableWidget_result.setSortingEnabled(False) 270 | item = self.tableWidget_result.horizontalHeaderItem(0) 271 | item.setText(_translate("MainWindow", "Title")) 272 | item = self.tableWidget_result.horizontalHeaderItem(1) 273 | item.setText(_translate("MainWindow", "URL")) 274 | self.label_notes.setWhatsThis(_translate("MainWindow", "调试窗口")) 275 | self.Label_target.setText(_translate("MainWindow", "目标公众号英文名")) 276 | self.LineEdit_target.setPlaceholderText(_translate("MainWindow", "为空则默认新华社(xinhuashefabu1)")) 277 | self.Label_user.setText(_translate("MainWindow", "个人公众号账号")) 278 | self.LineEdit_user.setPlaceholderText(_translate("MainWindow", "为空则自动打开页面后手动输入")) 279 | self.Label_pwd.setText(_translate("MainWindow", "个人公众号密码")) 280 | self.LineEdit_pwd.setPlaceholderText(_translate("MainWindow", "为空则自动打开页面后手动输入")) 281 | self.gapLabel.setText(_translate("MainWindow", "查询间隔(s)")) 282 | self.LineEdit_timegap.setPlaceholderText(_translate("MainWindow", "为空则默认为5s,一页约10条,越短越快被限制")) 283 | self.wechatLabel.setText(_translate("MainWindow", "微信uin和key")) 284 | self.LineEdit_wechat.setPlaceholderText(_translate("MainWindow", "URL全复制进来;下载评论和阅读数时需要,通过Fiddler抓包微信uin和key(约20分钟失效一次)")) 285 | self.Label_time.setText(_translate("MainWindow", "时间范围(年)")) 286 | self.lineEdit_timeStart.setPlaceholderText(_translate("MainWindow", "1999")) 287 | self.lineEdit_timeEnd.setPlaceholderText(_translate("MainWindow", "2019")) 288 | self.label.setText(_translate("MainWindow", "关键词")) 289 | self.pushButton_start.setText(_translate("MainWindow", "启动(*^▽^*)")) 290 | self.checkBox.setWhatsThis(_translate("MainWindow", "记住密码")) 291 | self.checkBox.setText(_translate("MainWindow", "记住密码")) 292 | self.pushButton_stop.setText(_translate("MainWindow", "终止 ̄へ ̄")) 293 | self.label_head.setText(_translate("MainWindow", "****************************************************************************************************\n" 294 | "* 程序原理:\n" 295 | ">> 通过selenium登录获取token和cookie,再自动爬取和下载\n" 296 | "* 使用前提: \n" 297 | ">> 申请一个微信公众号(https://mp.weixin.qq.com)\n" 298 | "开源链接:https://github.com/1061700625/WeChat_Article\n" 299 | " Copyright © SXF 本软件禁止一切形式的商业活动\n" 300 | "****************************************************************************************************")) 301 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", " 公众号搜文章 ")) 302 | self.Label_time_2.setText(_translate("MainWindow", "关键词")) 303 | self.label_head_2.setText(_translate("MainWindow", "****************************************************************************************************\n" 304 | "* demo说明:\n" 305 | ">> 现在“公众号搜文章”页填完整信息\n" 306 | ">> 再在本页填入关键词\n" 307 | ">> 点击“启动”即可\n" 308 | " Copyright © SXF 本软件禁止一切形式的商业活动\n" 309 | "****************************************************************************************************")) 310 | self.pushButton_stop_2.setText(_translate("MainWindow", "终止 ̄へ ̄")) 311 | self.pushButton_start_2.setText(_translate("MainWindow", "启动(*^▽^*)")) 312 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", " 关键词搜文章 ")) 313 | -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1061700625/WeChat_Article/e17f05a689320fcae05ebf400e5880ec1844ce30/icon.ico -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import WeChat 3 | from PyQt5 import QtCore, QtGui, QtWidgets 4 | from PyQt5.QtGui import QPixmap 5 | from PyQt5.QtCore import Qt 6 | import sys 7 | import os 8 | import re 9 | from time import sleep, localtime, time, strftime 10 | import undetected_chromedriver as uc 11 | from PyQt5.QtWidgets import QApplication 12 | from bs4 import BeautifulSoup 13 | import requests 14 | import json 15 | import urllib.parse 16 | from selenium import webdriver 17 | from selenium.webdriver.support.ui import WebDriverWait 18 | from selenium.webdriver.support import expected_conditions as EC 19 | from selenium.webdriver.common.by import By 20 | from selenium.webdriver.chrome.options import Options 21 | from selenium.webdriver.chrome.service import Service 22 | from webdriver_manager.chrome import ChromeDriverManager 23 | from math import ceil 24 | import threading 25 | import inspect 26 | import ctypes 27 | import random 28 | from goto import with_goto 29 | import configparser 30 | import pyautogui 31 | # import pdfkit 32 | 33 | ''' 34 | conf.ini 35 | [resume] 36 | rootpath = '' 37 | pagenum = 0 38 | linkbuf_cnt = 0 39 | download_cnt = 0 40 | ''' 41 | # 设置 递归调用深度 为 一百万 42 | sys.setrecursionlimit(1000000) 43 | 44 | # https://github.com/wnma3mz/wechat_articles_spider/blob/master/docs/使用的微信公众号接口.md 45 | # title_buf = [] 46 | # link_buf = [] 47 | pro_continue = 0 48 | class MyMainWindow(WeChat.Ui_MainWindow): 49 | def __init__(self): 50 | self.sess = requests.Session() 51 | self.headers = { 52 | 'Host': 'mp.weixin.qq.com', 53 | 'User-Agent': r'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0', 54 | } 55 | self.browser_path = r'Chrome/BitBrowser.exe' 56 | self.driver_path = r'Chrome/chromedriver.exe' 57 | 58 | self.initpath = os.getcwd() 59 | self.rootpath = os.getcwd() + r"/spider/" # 全局变量,存放路径 60 | self.time_gap = 5 # 全局变量,每页爬取等待时间 61 | self.timeStart = 1999 # 全局变量,起始时间 62 | 63 | self.year_now = localtime(time()).tm_year # 当前年份,用于比对时间 64 | self.timeEnd = self.year_now+1 # 全局变量,结束时间 65 | self.thread_list = [] 66 | self.label_debug_string = "" 67 | self.label_debug_cnt = 0 68 | self.total_articles = 0 # 当前文章数 69 | self.keyWord = "" 70 | self.keyword_search_mode = 0 71 | self.keyWord_2 = "" 72 | self.freq_control = 0 73 | self.download_cnt = 0 74 | self.linkbuf_cnt = 0 75 | self.download_end = 0 76 | self.isresume = self.Check_Config() 77 | self.url_json_init() 78 | self.title_buf = [] 79 | self.link_buf = [] 80 | self.wechat_uin = None 81 | self.wechat_key = None 82 | 83 | 84 | def vari_init(self): 85 | # global title_buf, link_buf 86 | self.rootpath = os.getcwd() + r"/spider/" # 全局变量,存放路径 87 | self.thread_list = [] 88 | self.label_debug_string = "" 89 | self.label_debug_cnt = 0 90 | self.total_articles = 0 # 当前文章数 91 | self.keyWord = "" 92 | self.keyword_search_mode = 0 93 | self.keyWord_2 = "" 94 | self.Label_Debug(' ') 95 | self.freq_control = 0 96 | self.download_cnt = 0 97 | self.linkbuf_cnt = 0 98 | self.download_end = 0 99 | self.title_buf.clear() # 清除缓存 100 | self.link_buf.clear() # 清除缓存 101 | self.progressBar.setMaximum(100) 102 | self.progressBar.setValue(0) 103 | 104 | def Label_Debug(self, string): 105 | if self.label_debug_cnt == 12: 106 | self.label_debug_string = "" 107 | self.label_notes.setText(self.label_debug_string) 108 | self.label_debug_cnt = 0 109 | self.label_debug_string += "\r\n" + string 110 | self.label_notes.setText(self.label_debug_string) 111 | self.label_debug_cnt += 1 112 | 113 | def Label_Debug_Clear(self): 114 | self.label_debug_string = "" 115 | self.label_notes.setText(self.label_debug_string) 116 | self.label_notes.clear() 117 | self.label_debug_cnt = 0 118 | 119 | def setupUi(self, MainWindow): 120 | super(MyMainWindow, self).setupUi(MainWindow) 121 | try: 122 | if os.path.exists(os.getcwd()+r'/login.json'): 123 | with open(os.getcwd()+r'/login.json', 'r', encoding='utf-8') as p: 124 | login_dict = json.load(p) 125 | print("登陆文件读取成功") 126 | self.Label_Debug("登陆文件读取成功") 127 | self.LineEdit_target.setText(login_dict['target']) # 公众号的英文名称 128 | self.LineEdit_user.setText(login_dict['user']) # 自己公众号的账号 129 | self.LineEdit_pwd.setText(login_dict['pwd']) # 自己公众号的密码 130 | self.LineEdit_timegap.setText(str(login_dict['timegap'])) # 每页爬取等待时间" 131 | self.lineEdit_timeEnd.setText(str(self.year_now+1)) # 结束时间为当前年 132 | self.lineEdit_timeStart.setText("1999") # 开始时间为1999 133 | QApplication.processEvents() # 刷新文本操作 134 | 135 | image_url = "http://xfxuezhang.cn/web/share/donate/yf.png" 136 | response = requests.get(image_url) 137 | if response.status_code == 200: 138 | self.label_yf.setAlignment(Qt.AlignCenter) 139 | pixmap = QPixmap() 140 | pixmap.loadFromData(response.content) 141 | # 缩放图片以适应标签的大小 142 | scaled_pixmap = pixmap.scaled(self.label_yf.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) 143 | self.label_yf.setPixmap(scaled_pixmap) 144 | print('image download ok') 145 | else: 146 | self.label_yf.setText("image url not found.") 147 | 148 | 149 | except Exception as e: 150 | print(e) 151 | 152 | def Start_Run(self): 153 | self.total_articles = 0 154 | Process_thread = threading.Thread(target=self.Process, daemon=True) 155 | Process_thread.start() 156 | self.thread_list.append(Process_thread) 157 | 158 | def Stop_Run(self): 159 | try: 160 | self.stop_thread(self.thread_list.pop()) 161 | self.stop_thread(self.thread_list.pop()) 162 | self.vari_init() # 变量复位 163 | self.Label_Debug("终止成功!") 164 | print("终止成功!") 165 | except Exception as e: 166 | self.Label_Debug("终止失败!") 167 | print(e) 168 | 169 | def Start_Run_2(self): 170 | try: 171 | os.makedirs(self.rootpath) 172 | except: 173 | pass 174 | self.keyword_search_mode = 1 175 | self.total_articles = 0 176 | Process_thread = threading.Thread(target=self.Process, daemon=True) 177 | Process_thread.start() 178 | self.thread_list.append(Process_thread) 179 | 180 | def Stop_Run_2(self): 181 | try: 182 | self.keyword_search_mode = 0 183 | self.stop_thread(self.thread_list.pop()) 184 | self.stop_thread(self.thread_list.pop()) 185 | self.vari_init() # 变量复位 186 | self.Label_Debug("终止成功!") 187 | print("终止成功!") 188 | except Exception as e: 189 | self.Label_Debug("终止失败!") 190 | print(e) 191 | 192 | def Change_IP(self): 193 | tar_url = r'https://www.douban.com' 194 | http_s = '111.26.9.26:80' 195 | if (tar_url.split(':')[0] == 'https'): 196 | proxies = {'https': http_s} 197 | else: 198 | proxies = {'http': http_s} 199 | try: 200 | # sess = requests.session() 201 | html = self.sess.get(tar_url, proxies=proxies, timeout=(30, 60)) 202 | print("* 代理有效√ *") 203 | print(html) 204 | except Exception as e: 205 | print("* 代理无效× *") 206 | print(e) 207 | pass 208 | 209 | def Check_Config(self): 210 | self.conf = configparser.ConfigParser() 211 | self.cfgpath = os.path.join(os.getcwd(), "conf.ini") 212 | if os.path.exists(self.cfgpath): 213 | print("[Yes] conf.ini") 214 | try: 215 | self.conf.read(self.cfgpath, encoding="utf8") # 读ini文件 216 | except: 217 | self.conf.read(self.cfgpath) # 读ini文件 218 | resume = self.conf.items('resume') 219 | self.rootpath = resume[0][1] 220 | self.pagenum = int(resume[1][1]) 221 | self.linkbuf_cnt = int(resume[2][1]) 222 | self.download_cnt = int(resume[3][1]) 223 | self.total_articles = int(resume[4][1]) 224 | print(self.rootpath, self.pagenum, self.linkbuf_cnt, self.download_cnt, self.total_articles) 225 | return 1 226 | else: 227 | print("[NO] conf.ini") 228 | f = open(self.cfgpath, 'w', encoding="utf-8") 229 | f.close() 230 | self.conf.add_section("resume") 231 | self.conf.set("resume", "rootpath", os.getcwd()) 232 | self.conf.set("resume", "pagenum", "0") 233 | self.conf.set("resume", "linkbuf_cnt", "0") 234 | self.conf.set("resume", "download_cnt", "0") 235 | self.conf.set("resume", "total_articles", "0") 236 | self.conf.write(open(self.cfgpath, "w")) # 删除原文件重新写入 237 | return 0 238 | 239 | def Process(self): 240 | try: 241 | username = self.LineEdit_user.text() # 自己公众号的账号 242 | pwd = self.LineEdit_pwd.text() # 自己公众号的密码 243 | query_name = self.LineEdit_target.text() # 公众号的英文名称 244 | self.time_gap = self.LineEdit_timegap.text() or 10 # 每页爬取等待时间 245 | self.time_gap = int(self.time_gap) 246 | self.timeStart = self.lineEdit_timeStart.text() or 1999 # 起始时间 247 | self.timeStart = int(self.timeStart) 248 | self.timeEnd = self.lineEdit_timeEnd.text() or self.year_now+1 # 结束时间 249 | self.timeEnd = int(self.timeEnd) 250 | self.keyWord = self.lineEdit_keyword.text() # 关键词 251 | # uin_key = self.LineEdit_wechat.text().strip() # 微信 uin,key 252 | # if uin_key: 253 | # self.wechat_uin = re.search(r'uin=(.*?)&', uin_key).group(1) 254 | # self.wechat_key = re.search(r'key=(.*?)&', uin_key).group(1) 255 | 256 | if self.checkBox.isChecked() is True and pwd != "": 257 | dicts = {'target': query_name, 'user': username, 'pwd': pwd, 'timegap': self.time_gap} 258 | with open(os.getcwd()+r'/login.json', 'w+') as p: 259 | json.dump(dicts, p) 260 | p.close() 261 | 262 | [token, cookies] = self.Login(username, pwd) 263 | self.Add_Cookies(cookies) 264 | if self.keyword_search_mode == 1: 265 | self.keyWord_2 = self.lineEdit_keyword_2.text() # 关键词 266 | self.KeyWord_Search(token, self.keyWord_2) 267 | else: 268 | [fakeid, nickname] = self.Get_WeChat_Subscription(token, query_name) 269 | if self.isresume == 0: 270 | Index_Cnt = 0 271 | while True: 272 | try: 273 | self.rootpath = os.path.join(os.getcwd(), "spider-%d" % Index_Cnt, nickname) #+ r"/spider-%d/" % Index_Cnt + nickname # !!!!!!!!!!!!!! 274 | os.makedirs(self.rootpath) 275 | self.conf.set("resume", "rootpath", self.rootpath) 276 | self.conf.write(open(self.cfgpath, "r+", encoding="utf-8")) 277 | break 278 | except: 279 | Index_Cnt = Index_Cnt + 1 280 | self.Get_Articles(token, fakeid) 281 | except Exception as e: 282 | self.Label_Debug("!!![%s]" % str(e)) 283 | print("!!![%s]" % str(e)) 284 | if "list" in str(e): 285 | self.Label_Debug("请删除cookie.json") 286 | print("请删除cookie.json") 287 | 288 | def url_json_write(self, inputdict): 289 | with open(self.url_json_path, "w+") as f: 290 | f.write(json.dumps(inputdict)) 291 | 292 | def url_json_read(self): 293 | with open(self.url_json_path, "r+") as f: 294 | json_read = json.loads(f.read()) 295 | return json_read 296 | 297 | def url_json_update(self, source, adddict): 298 | source.append(adddict) 299 | 300 | def url_json_init(self): 301 | self.url_json_path = os.path.join(os.getcwd(), "url.json") 302 | if os.path.exists(self.url_json_path): 303 | print("[Yes] url.json") 304 | if self.isresume == 0: 305 | os.remove(self.url_json_path) 306 | self.url_json_write([]) 307 | else: 308 | print("[NO] url.json") 309 | self.url_json_write([]) 310 | self.json_read = self.url_json_read() 311 | self.json_read_len = len(self.json_read) 312 | print("len(url.json):", self.json_read_len) 313 | 314 | def url_json_once(self, dict_add): 315 | self.url_json_update(self.json_read, dict_add) # {"Title": 1, "Link": 2, "Img": 3} 316 | self.url_json_write(self.json_read) 317 | self.json_read = self.url_json_read() 318 | # print("url_json_once OK") 319 | # print(self.json_read) 320 | 321 | def Login(self, username, pwd): 322 | try: 323 | if self.freq_control == 1: 324 | raise RuntimeError('freq_control=1') 325 | print(self.initpath+"/cookie.json") 326 | with open(self.initpath+"/cookie.json", 'r+') as fp: 327 | cookieToken_dict = json.load(fp) 328 | cookies = cookieToken_dict[0]['COOKIES'] 329 | token = cookieToken_dict[0]['TOKEN'] 330 | print(token) 331 | print(cookies) 332 | 333 | if cookies != "" and token != "": 334 | self.Label_Debug("cookie.json读取成功") 335 | print("cookie.json读取成功") 336 | self.Add_Cookies(cookies) 337 | 338 | html = self.sess.get(r'https://mp.weixin.qq.com/cgi-bin/home?t=home/index&lang=zh_CN&token=%s' % token, timeout=(30, 60)) 339 | if "登陆" not in html.text: 340 | self.Label_Debug("cookie有效,无需浏览器登陆") 341 | print("cookie有效,无需浏览器登陆") 342 | return token, cookies 343 | except Exception as e: 344 | print("无cookie.json或失效 -", e) 345 | self.Label_Debug("无cookie.json或失效") 346 | 347 | 348 | self.Label_Debug("正在打开浏览器,请稍等") 349 | print("正在打开浏览器,请稍等") 350 | options = Options() 351 | # options.add_argument("--headless") 352 | options.add_argument("--incognito") 353 | options.add_argument("--disable-blink-features") 354 | options.add_argument("--disable-blink-features=AutomationControlled") 355 | options.add_argument("--no-default-browser-check") 356 | options.add_argument("--allow-running-insecure-content") 357 | options.add_argument("--ignore-certificate-errors") 358 | options.add_argument("--disable-single-click-autofill") 359 | options.add_argument("--disable-autofill-keyboard-accessory-view[8]") 360 | options.add_argument("--disable-full-form-autofill-ios") 361 | browser = webdriver.Chrome(options=options, service=Service(ChromeDriverManager().install())) 362 | # browser = uc.Chrome(driver_executable_path=self.driver_path, 363 | # browser_executable_path=self.browser_path, 364 | # suppress_welcome=False) 365 | 366 | browser.maximize_window() 367 | 368 | browser.get(r'https://mp.weixin.qq.com') 369 | browser.implicitly_wait(60) 370 | # account = browser.find_element(by=By.NAME, value="account") 371 | # password = browser.find_element(by=By.NAME, value="password") 372 | # if (username != "" and pwd != ""): 373 | # account.click() 374 | # account.send_keys(username) 375 | # password.click() 376 | # password.send_keys(pwd) 377 | # browser.find_element(by=By.XPATH, value=r'//*[@id="header"]/div[2]/div/div/form/div[4]/a').click() 378 | # else: 379 | # self.Label_Debug("* 请在10分钟内手动完成登录 *") 380 | pyautogui.alert(title='请手动完成登录', text='完成登录后,点击确认!', button='确认') 381 | WebDriverWait(browser, 60 * 10, 0.5).until( 382 | EC.presence_of_element_located((By.CSS_SELECTOR, r'.weui-desktop-account__info')) 383 | ) 384 | self.Label_Debug("登陆成功") 385 | token = re.search(r'token=(.*)', browser.current_url).group(1) 386 | cookies = browser.get_cookies() 387 | with open(os.getcwd()+"/cookie.json", 'w+') as fp: 388 | temp_list = {} 389 | temp_array = [] 390 | temp_list['COOKIES'] = cookies 391 | temp_list['TOKEN'] = token 392 | temp_array.append(temp_list) 393 | json.dump(temp_array, fp) 394 | fp.close() 395 | self.Label_Debug(">> 本地保存cookie和token") 396 | print(">> 本地保存cookie和token") 397 | browser.close() 398 | return token, cookies 399 | 400 | def Add_Cookies(self, cookie): 401 | c = requests.cookies.RequestsCookieJar() 402 | for i in cookie: # 添加cookie到CookieJar 403 | c.set(i["name"], i["value"]) 404 | self.sess.cookies.update(c) # 更新session里的cookie 405 | 406 | def KeyWord_Search(self, token, keyword): 407 | self.url_buf = [] 408 | self.title_buf = [] 409 | header = { 410 | 'Content - Type': r'application/x-www-form-urlencoded;charset=UTF-8', 411 | 'Host': 'mp.weixin.qq.com', 412 | 'User-Agent': r'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36', 413 | 'Referer': 'https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit&action=edit&type=10&isMul=1&isNew=1&share=1&lang=zh_CN&token=%d' % int(token) 414 | } 415 | url = r'https://mp.weixin.qq.com/cgi-bin/operate_appmsg?sub=check_appmsg_copyright_stat' 416 | data = {'token': token, 'lang': 'zh_CN', 'f': 'json', 'ajax': 1, 'random': random.uniform(0, 1), 'url': keyword, 'allow_reprint': 0, 'begin': 0, 'count': 10} 417 | html_json = self.sess.post(url, data=data, headers=header).json() 418 | total = html_json['total'] 419 | total_page = ceil(total / 10) 420 | print(total_page, '-', total) 421 | table_index = 0 422 | for i in range(total_page): 423 | data = { 424 | 'token': token, 425 | 'lang': 'zh_CN', 426 | 'f': 'json', 427 | 'ajax': 1, 428 | 'random': random.uniform(0, 1), 429 | 'url': keyword, 430 | 'allow_reprint': 0, 431 | 'begin': i*10, 432 | 'count': 10 433 | } 434 | html_json = self.sess.post(url, data=data, headers=header).json() 435 | page_len = len(html_json['list']) 436 | # print(page_len) 437 | for j in range(page_len): 438 | self.url_buf.append(html_json['list'][j]['url']) 439 | self.title_buf.append(html_json['list'][j]['title']) 440 | print(j+1, ' - ', html_json['list'][j]['title']) 441 | table_count = self.tableWidget_result.rowCount() 442 | if (table_index >= table_count): 443 | self.tableWidget_result.insertRow(table_count) 444 | self.tableWidget_result.setItem(table_index, 0, QtWidgets.QTableWidgetItem(self.title_buf[j])) # i*20+j 445 | self.tableWidget_result.setItem(table_index, 1, QtWidgets.QTableWidgetItem(self.url_buf[j])) # i*20+j 446 | table_index = table_index + 1 447 | self.total_articles += 1 448 | with open(self.rootpath + "/spider.txt", 'a+', encoding="utf-8") as fp: 449 | fp.write('*' * 60 + '\n【%d】\n Title: ' % self.total_articles + self.title_buf[j] + '\n Link: ' + self.url_buf[j] + '\n Img: ' + '\r\n\r\n') 450 | # fp.write('\n【%d】\n' % self.total_articles + '\n' + url_buf[j] + '\r\n') 451 | fp.close() 452 | self.Label_Debug(">> 第%d条写入完成:%s" % (j + 1, self.title_buf[j])) 453 | print(">> 第%d条写入完成:%s" % (j + 1, self.title_buf[j])) 454 | print('*' * 60) 455 | self.get_content(self.title_buf, self.url_buf) 456 | self.url_buf.clear() 457 | self.title_buf.clear() 458 | 459 | def Get_WeChat_Subscription(self, token, query): 460 | if (query == ""): 461 | query = "xinhuashefabu1" 462 | url = r'https://mp.weixin.qq.com/cgi-bin/searchbiz?action=search_biz&token={0}&lang=zh_CN&f=json&ajax=1&random=0.5182749224035845&query={1}&begin=0&count=5'.format( 463 | token, query) 464 | html_json = self.sess.get(url, headers=self.headers, timeout=(30, 60)).json() 465 | fakeid = html_json['list'][0]['fakeid'] 466 | nickname = html_json['list'][0]['nickname'] 467 | self.Label_Debug("nickname: "+nickname) 468 | return fakeid, nickname 469 | 470 | def Get_Articles(self, token, fakeid): 471 | # title_buf = [] 472 | # link_buf = [] 473 | img_buf = [] 474 | 475 | Total_buf = [] 476 | url = r'https://mp.weixin.qq.com/cgi-bin/appmsg?token={0}&lang=zh_CN&f=json&ajax=1&random={1}&action=list_ex&begin=0&count=5&query=&fakeid={2}&type=9'.format(token, random.uniform(0, 1), fakeid) 477 | html_json = self.sess.get(url, headers=self.headers, timeout=(30, 60)).json() 478 | try: 479 | Total_Page = ceil(int(html_json['app_msg_cnt']) / 5) 480 | self.progressBar.setMaximum(Total_Page) 481 | QApplication.processEvents() # 刷新文本操作 482 | except Exception as e: 483 | print(e) 484 | self.Label_Debug("!! 失败信息:"+html_json['base_resp']['err_msg']) 485 | # if 'freq control' in html_json['base_resp']['err_msg']: 486 | # 可能有点问题 487 | # if self.lineEdit_user_2.text() != '' and self.lineEdit_pwd_2.text() != '': 488 | # self.freq_control = 1 489 | # self.Label_Debug("将使用备胎公众号") 490 | # username = self.lineEdit_user_2.text() # 备选公众号的账号 491 | # pwd = self.lineEdit_pwd_2.text() # 备选公众号的密码 492 | # [token, cookies] = self.Login(username, pwd) 493 | # self.Add_Cookies(cookies) 494 | # self.freq_control = 0 495 | # self.Get_Articles(token, fakeid) 496 | return 497 | table_index = 0 498 | 499 | download_thread = threading.Thread(target=self.download_content) 500 | download_thread.start() 501 | self.thread_list.append(download_thread) 502 | 503 | _buf_index = 0 504 | for i in range(Total_Page): 505 | if self.isresume == 1: 506 | i = i + self.pagenum 507 | self.Label_Debug("第[%d/%d]页 url:%s, article:%s" % (i + 1, Total_Page, self.linkbuf_cnt, self.download_cnt)) 508 | print("第[%d/%d]页 url:%s, article:%s" % (i + 1, Total_Page, self.linkbuf_cnt, self.download_cnt)) 509 | self.label_total_Page.setText("第[%d/%d]页 linkbuf_cnt:%s, download_cnt:%s" % (i + 1, Total_Page, self.linkbuf_cnt, self.download_cnt)) 510 | begin = i * 5 511 | url = r'https://mp.weixin.qq.com/cgi-bin/appmsg?token={0}&lang=zh_CN&f=json&ajax=1&random={1}&action=list_ex&begin={2}&count=5&query=&fakeid={3}&type=9'.format( 512 | token, random.uniform(0, 1), begin, fakeid) 513 | while True: 514 | try: 515 | html_json = self.sess.get(url, headers=self.headers, timeout=(30, 60)).json() 516 | break 517 | except Exception as e: 518 | print("连接出错,稍等2s", e) 519 | self.Label_Debug("连接出错,稍等2s" + str(e)) 520 | sleep(2) 521 | continue 522 | try: 523 | app_msg_list = html_json['app_msg_list'] 524 | except Exception as e: 525 | self.Label_Debug("!!!操作太频繁,5s后重试!!!") 526 | print("!!!操作太频繁,5s后重试!!!", e) 527 | sleep(5) 528 | continue 529 | # os._exit(0) 530 | 531 | if (str(app_msg_list) == '[]'): 532 | print('结束了') 533 | self.Label_Debug("结束了") 534 | break 535 | for j in range(30): 536 | try: 537 | if (app_msg_list[j]['title'] in Total_buf): 538 | self.Label_Debug("本条已存在,跳过") 539 | print("本条已存在,跳过") 540 | continue 541 | if self.keyWord != "": 542 | if self.keyWord not in app_msg_list[j]['title']: 543 | self.Label_Debug("本条不匹配关键词[%s],跳过" % self.keyWord) 544 | print("本条不匹配关键词[%s],跳过" % self.keyWord) 545 | continue 546 | article_time = int(strftime("%Y", localtime(int(app_msg_list[j]['update_time'])))) # 当前文章时间戳转为年份 547 | if (self.timeStart > article_time): 548 | self.Label_Debug("本条[%d]不在时间范围[%d-%d]内,跳过" % (article_time, self.timeStart, self.timeEnd)) 549 | print("本条[%d]不在时间范围[%d-%d]内,跳过" % (article_time, self.timeStart, self.timeEnd)) 550 | continue 551 | if(article_time > self.timeEnd): 552 | self.Label_Debug("达到结束时间,退出") 553 | print("达到结束时间,退出") 554 | self.Stop_Run() 555 | return 556 | # os._exit(0) 557 | self.title_buf.append(app_msg_list[j]['title']) 558 | self.link_buf.append(app_msg_list[j]['link']) 559 | img_buf.append(app_msg_list[j]['cover']) 560 | Total_buf.append(app_msg_list[j]['title']) 561 | 562 | table_count = self.tableWidget_result.rowCount() 563 | if(table_index >= table_count): 564 | self.tableWidget_result.insertRow(table_count) 565 | self.tableWidget_result.setItem(table_index, 0, QtWidgets.QTableWidgetItem(self.title_buf[_buf_index+j])) # i*20+j 566 | self.tableWidget_result.setItem(table_index, 1, QtWidgets.QTableWidgetItem(self.link_buf[_buf_index+j])) # i*20+j 567 | table_index = table_index + 1 568 | 569 | self.total_articles += 1 570 | dict_in = {"Title": self.title_buf[_buf_index+j], "Link": self.link_buf[_buf_index+j], "Img": img_buf[_buf_index+j]} 571 | self.url_json_once(dict_in) 572 | os.makedirs(self.rootpath, exist_ok=True) 573 | with open(self.rootpath + "/spider.txt", 'a+', encoding="utf-8") as fp: 574 | fp.write('*' * 60 + '\n【%d】\n Title: ' % self.total_articles + self.title_buf[_buf_index+j] + '\n Link: ' + self.link_buf[_buf_index+j] + '\n Img: ' + img_buf[_buf_index+j] + '\r\n\r\n') 575 | # fp.write('【%d】 ' % self.total_articles + '\n' + link_buf[j] + '\r\n') 576 | fp.close() 577 | self.Label_Debug(">> 第%d条写入完成:%s" % (self.total_articles, self.title_buf[_buf_index+j])) 578 | print(">> 第%d条写入完成:%s" % (self.total_articles, self.title_buf[_buf_index+j])) 579 | self.conf.set("resume", "total_articles", str(self.total_articles)) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 580 | self.conf.write(open(self.cfgpath, "r+", encoding="utf-8")) 581 | except Exception as e: 582 | print(">> 本页抓取结束 - ", e) 583 | _buf_index += j 584 | print(_buf_index, len(self.title_buf)) 585 | print(self.title_buf) 586 | break 587 | 588 | self.Label_Debug(">> 一页抓取结束") 589 | print(">> 一页抓取结束") 590 | # self.get_content(title_buf, link_buf) 591 | # title_buf.clear() # 清除缓存 592 | # link_buf.clear() # 清除缓存 593 | if self.isresume == 1: 594 | self.linkbuf_cnt = len(self.link_buf) + self.json_read_len 595 | else: 596 | self.linkbuf_cnt = len(self.link_buf) 597 | self.conf.set("resume", "linkbuf_cnt", str(self.linkbuf_cnt)) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 598 | self.conf.write(open(self.cfgpath, "r+", encoding="utf-8")) 599 | self.conf.set("resume", "pagenum", str(i)) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 600 | self.conf.write(open(self.cfgpath, "r+", encoding="utf-8")) 601 | sleep(self.time_gap) 602 | self.Label_Debug_Clear() 603 | self.Label_Debug(">> 列表抓取结束!!! <<") 604 | print(">> 列表抓取结束!!! <<") 605 | self.download_end = 1 606 | 607 | 608 | def Get_comment_id(self, article_url): 609 | '''获取文章id''' 610 | try: 611 | resp = requests.get(article_url).text 612 | pattern = re.compile(r'comment_id\s*=\s*"(?P\d+)"') 613 | return pattern.search(resp)['id'] 614 | except: 615 | return None 616 | 617 | def Get_Comments(self, article_url, uin, key, offset=0): 618 | '''获取文章的评论''' 619 | # TODO: 微信uin和key失效后,弹窗提示更新,更新后继续运行 620 | comments = [] 621 | if not uin or not key: 622 | return comments 623 | url = 'https://mp.weixin.qq.com/mp/appmsg_comment?' 624 | biz = re.search('__biz=(.*?)&', article_url).group(1) 625 | comment_id = self.Get_comment_id(article_url) 626 | 627 | datas = { 628 | 'action': 'getcomment', 629 | # 与文章绑定 630 | 'comment_id': str(comment_id), # !import 631 | # 与微信绑定 632 | 'uin': str(uin), # !import 633 | # 与微信绑定,约20分钟失效 634 | 'key': str(key), # !import 635 | '__biz': str(biz), # !import 636 | 'offset': str(offset), 637 | 'limit': '100', 638 | 'f': 'json', 639 | # 'scene': '0', 640 | # 'appmsgid': appmsgid, 641 | # 'idx': idx, 642 | # 'send_time': '', 643 | # 'sessionid': sessionid, 644 | # 'enterid': enterid, 645 | # 'fasttmplajax': '1', 646 | # 'pass_ticket': pass_ticket, 647 | # 'wxtoken': '', 648 | # 'devicetype': 'Windows%2B11%2Bx64', 649 | # 'clientversion': '63090551', 650 | # 'appmsg_token': '', 651 | # 'x5': '0', 652 | } 653 | params = '' 654 | for key,value in datas.items(): 655 | params += key + '=' + value + '&' 656 | url += params 657 | try: 658 | resp = requests.get(url=url).json() 659 | if resp['elected_comment_total_cnt']: 660 | for item in resp['elected_comment']: 661 | comments.append(item['nick_name'] + ": " + item['content']) 662 | except: 663 | pass 664 | return comments 665 | 666 | 667 | # 获取阅读数和点赞数(未测试) 668 | def Get_ReadsLikes(self, link): 669 | # 获得mid,_biz,idx,sn 这几个在link中的信息 670 | mid = link.split("&")[1].split("=")[1] 671 | idx = link.split("&")[2].split("=")[1] 672 | sn = link.split("&")[3].split("=")[1] 673 | _biz = link.split("&")[0].split("_biz=")[1] 674 | 675 | # fillder 中取得一些不变得信息 676 | pass_ticket = "这里也是输入你自己的数据"#从fiddler中获取 # ---------------------------------这里每次需要修改--------------------------------- 677 | appmsg_token = "这里也是输入你自己的数据"#从fiddler中获取 # ---------------------------------这里每次需要修改--------------------------------- 678 | 679 | # 目标url 680 | url = "http://mp.weixin.qq.com/mp/getappmsgext"#获取详情页的网址 681 | # 添加Cookie避免登陆操作,这里的"User-Agent"最好为手机浏览器的标识 682 | #phoneCookie = "自己的" 683 | phoneCookie = "这里也是输入你自己的数据(这虽然叫phoneCookie但其实就是fillder抓包得到的cookie)"# ---------------------------------这里每次需要修改--------------------------------- 684 | headers = { 685 | "Cookie": phoneCookie, 686 | "User-Agent": "这里也是输入你自己的数据" # ---------------------------------这里需要修改--------------------------------- 687 | } 688 | # 添加data,`req_id`、`pass_ticket`分别对应文章的信息,从fiddler复制即可。 689 | data = { 690 | "is_only_read": "1", 691 | "is_temp_url": "0", 692 | "appmsg_type": "9", 693 | 'reward_uin_count': '-1' 694 | } 695 | """ 696 | 添加请求参数 697 | __biz对应公众号的信息,唯一 698 | mid、sn、idx分别对应每篇文章的url的信息,需要从url中进行提取 699 | key、appmsg_token从fiddler上复制即可 700 | pass_ticket对应的文章的信息,也可以直接从fiddler复制 701 | """ 702 | params = { 703 | "__biz": _biz, 704 | "mid": mid, 705 | "sn": sn, 706 | "idx": idx, 707 | "key": "这里也是输入你自己的数据",# ---------------------------------这里每次需要修改--------------------------------- 708 | "pass_ticket": pass_ticket, 709 | "appmsg_token": appmsg_token, 710 | "uin": "这里也是输入你自己的数据", 711 | "wxtoken": "777", 712 | } 713 | 714 | # 使用post方法进行提交 715 | requests.packages.urllib3.disable_warnings() 716 | content = requests.post(url, headers=headers, data=data, params=params).json() 717 | # 提取其中的阅读数和点赞数 718 | # print(content["appmsgstat"]["read_num"], content["appmsgstat"]["like_num"]) 719 | try: 720 | readNum = content["appmsgstat"]["read_num"] 721 | print("阅读数:"+str(readNum)) 722 | except: 723 | readNum = 0 724 | try: 725 | likeNum = content["appmsgstat"]["like_num"] 726 | print("喜爱数:"+str(likeNum)) 727 | except: 728 | likeNum = 0 729 | try: 730 | old_like_num = content["appmsgstat"]["old_like_num"] 731 | print("在读数:"+str(old_like_num)) 732 | except: 733 | old_like_num = 0 734 | # 歇3s,防止被封 735 | time.sleep(3) 736 | return readNum, likeNum,old_like_num 737 | 738 | 739 | def download_content(self): 740 | # global link_buf, title_buf 741 | # self.pri_index = 0 742 | while 1: 743 | try: 744 | if self.download_cnt < self.linkbuf_cnt: 745 | if self.isresume == 1: 746 | self.json_read = self.url_json_read() 747 | # print("download_cnt:", self.download_cnt, "; json_read:", len(self.json_read), "; linkbuf_cnt:", self.linkbuf_cnt) 748 | self.get_content(self.json_read[self.download_cnt]["Title"], self.json_read[self.download_cnt]["Link"]) 749 | else: 750 | self.get_content(self.title_buf[self.download_cnt], self.link_buf[self.download_cnt]) 751 | self.download_cnt += 1 752 | self.conf.set("resume", "download_cnt", str(self.download_cnt)) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 753 | self.conf.write(open(self.cfgpath, "r+", encoding="utf-8")) 754 | elif self.download_cnt >= self.linkbuf_cnt and self.download_end == 1: 755 | self.Label_Debug_Clear() 756 | self.Label_Debug(">> 程序结束, 欢迎再用!!! <<") 757 | print(">> 程序结束, 欢迎再用!!! <<") 758 | break 759 | elif self.download_cnt == self.linkbuf_cnt and self.download_end == 0: 760 | sleep(2) 761 | except Exception as e: 762 | print("download_content", e) 763 | self.Label_Debug(e) 764 | 765 | 766 | def get_content(self, title_buf, link_buf): # 获取地址对应的文章内容 767 | each_title = "" # 初始化 768 | each_url = "" # 初始化 769 | if self.keyword_search_mode == 1: 770 | length = len(title_buf) 771 | else: 772 | length = 1 773 | 774 | for index in range(length): 775 | if self.keyword_search_mode == 1: 776 | each_title = re.sub(r'[\|\/\<\>\:\*\?\\\"]', "_", title_buf[index]) # 剔除不合法字符 777 | else: 778 | each_title = re.sub(r'[\|\/\<\>\:\*\?\\\"]', "_", title_buf) # 剔除不合法字符 779 | filepath = self.rootpath + "/" + each_title # 为每篇文章创建文件夹 780 | if (not os.path.exists(filepath)): # 若不存在,则创建文件夹 781 | os.makedirs(filepath) 782 | os.chdir(filepath) # 切换至文件夹 783 | 784 | download_url = link_buf[index] if self.keyword_search_mode==1 else link_buf 785 | while True: 786 | try: 787 | html = self.sess.get(download_url, headers=self.headers, timeout=(30, 60)) 788 | break 789 | except Exception as e: 790 | print("连接出错,稍等2s", e) 791 | self.Label_Debug("连接出错,稍等2s" + str(e)) 792 | sleep(2) 793 | continue 794 | # try: 795 | # pdfkit.from_file(html.text, each_title + '.pdf') 796 | # except Exception as e: 797 | # pass 798 | soup = BeautifulSoup(html.text, 'lxml') 799 | try: 800 | article = soup.find(class_="rich_media_content").find_all("p") # 查找文章内容位置 801 | No_article = 0 802 | except Exception as e: 803 | No_article = 1 804 | self.Label_Debug("本篇未匹配到文字 ->"+str(e)) 805 | print("本篇未匹配到文字 ->", e) 806 | pass 807 | try: 808 | img_urls = soup.find(class_="rich_media_content").find_all("img") # 获得文章图片URL集 809 | No_img = 0 810 | except Exception as e: 811 | No_img = 1 812 | self.Label_Debug("本篇未匹配到图片 ->" + str(e)) 813 | print("本篇未匹配到图片 ->", e) 814 | pass 815 | 816 | print("*" * 60) 817 | self.Label_Debug("*" * 30) 818 | self.Label_Debug(each_title) 819 | if No_article != 1: 820 | for i in article: 821 | line_content = i.get_text() # 获取标签内的文本 822 | # print(line_content) 823 | if (line_content != None): # 文本不为空 824 | with open(each_title + r'.txt', 'a+', encoding='utf-8') as fp: 825 | fp.write(line_content + "\n") # 写入本地文件 826 | fp.close() 827 | self.Label_Debug(">> 保存文档 - 完毕!") 828 | # print(">> 标题:", each_title) 829 | print(">> 保存文档 - 完毕!") 830 | if No_img != 1: 831 | for i in range(len(img_urls)): 832 | re_cnt = 0 833 | while True: 834 | try: 835 | pic_down = self.sess.get(img_urls[i]["data-src"], timeout=(30, 60)) # 连接超时30s,读取超时60s,防止卡死 836 | break 837 | except Exception as e: 838 | print("下载超时 ->", e) 839 | self.Label_Debug("下载超时->" + str(e)) 840 | re_cnt += 1 841 | if re_cnt > 3: 842 | print("放弃此图") 843 | self.Label_Debug("放弃此图") 844 | break 845 | if re_cnt > 3: 846 | f = open(str(i) + r'.jpeg', 'ab+') 847 | f.close() 848 | continue 849 | img_urls[i]["src"] = str(i)+r'.jpeg' # 更改图片地址为本地 850 | with open(str(i) + r'.jpeg', 'ab+') as fp: 851 | fp.write(pic_down.content) 852 | fp.close() 853 | self.Label_Debug(">> 保存图片%d张 - 完毕!" % len(img_urls)) 854 | print(">> 保存图片%d张 - 完毕!" % len(img_urls)) 855 | 856 | with open(each_title+r'.html', 'w', encoding='utf-8') as f: # 保存html文件 857 | f.write(str(soup)) 858 | f.close() 859 | self.Label_Debug(">> 保存html - 完毕!") 860 | # pdfkit.from_file('test.html','out1.pdf') 861 | print(">> 保存html - 完毕!") 862 | 863 | # 下载文章评论 864 | comments = self.Get_Comments(download_url, self.wechat_uin, self.wechat_key) 865 | with open(each_title + r'_comments.txt', 'a+', encoding='utf-8') as fp: 866 | fp.write('\n'.join(comments)) # 写入本地文件 867 | fp.close() 868 | self.Label_Debug(">> 保存评论 - 完毕!") 869 | 870 | if self.keyword_search_mode == 1: 871 | self.Label_Debug(">> 休息 %d s" % self.time_gap) 872 | print(">> 休息 %d s" % self.time_gap) 873 | sleep(self.time_gap) 874 | 875 | ################################强制关闭线程################################################## 876 | def _async_raise(self, tid, exctype): 877 | """raises the exception, performs cleanup if needed""" 878 | tid = ctypes.c_long(tid) 879 | if not inspect.isclass(exctype): 880 | exctype = type(exctype) 881 | res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) 882 | if res == 0: 883 | raise ValueError("invalid thread id") 884 | elif res != 1: 885 | # """if it returns a number greater than one, you're in trouble, 886 | # and you should call it again with exc=NULL to revert the effect""" 887 | ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) 888 | raise SystemError("PyThreadState_SetAsyncExc failed") 889 | 890 | def stop_thread(self, thread): 891 | self._async_raise(thread.ident, SystemExit) 892 | ############################################################################################### 893 | 894 | def main(): 895 | app = QtWidgets.QApplication(sys.argv) 896 | MainWindow = QtWidgets.QMainWindow() 897 | ui = MyMainWindow() 898 | ui.setupUi(MainWindow) 899 | MainWindow.show() 900 | sys.exit(app.exec_()) 901 | 902 | 903 | if __name__ == "__main__": 904 | main() 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyQt5 2 | requests 3 | bs4 4 | selenium 5 | goto-statement 6 | configparser 7 | undetected_chromedriver 8 | lxml 9 | pyautogui 10 | pyinstaller 11 | webdriver_manager -------------------------------------------------------------------------------- /tk/WeChat_tk.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import ttk 3 | import tkinter.font as tkFont 4 | from datetime import datetime 5 | 6 | class ToolTip: 7 | def __init__(self, widget, text): 8 | self.widget = widget 9 | self.text = text 10 | self.tooltip = None 11 | self.widget.bind("", self.show) 12 | self.widget.bind("", self.hide) 13 | 14 | def show(self, event=None): 15 | x, y, _, _ = self.widget.bbox("insert") 16 | x += self.widget.winfo_rootx() + 25 17 | y += self.widget.winfo_rooty() + 25 18 | self.tooltip = tk.Toplevel(self.widget) 19 | self.tooltip.wm_overrideredirect(True) 20 | self.tooltip.wm_geometry(f"+{x}+{y}") 21 | label = tk.Label(self.tooltip, text=self.text, background="#ffffe0", relief="solid", borderwidth=1) 22 | label.pack() 23 | 24 | def hide(self, event=None): 25 | if self.tooltip: 26 | self.tooltip.destroy() 27 | self.tooltip = None 28 | 29 | class PlaceholderEntry(tk.Entry): 30 | def __init__(self, master=None, placeholder="", color="grey", **kwargs): 31 | super().__init__(master, **kwargs) 32 | self.placeholder = placeholder 33 | self.placeholder_color = color 34 | self.default_fg_color = self["fg"] 35 | 36 | self.bind("", self.on_focus_in) 37 | self.bind("", self.on_focus_out) 38 | 39 | self.put_placeholder() 40 | 41 | def put_placeholder(self): 42 | self.delete(0, tk.END) 43 | self.insert(0, self.placeholder) 44 | self.config(fg=self.placeholder_color) 45 | 46 | def on_focus_in(self, event): 47 | if self.get() == self.placeholder: 48 | self.delete(0, tk.END) 49 | self.config(fg=self.default_fg_color) 50 | 51 | def on_focus_out(self, event): 52 | if not self.get(): 53 | self.put_placeholder() 54 | 55 | def get(self): 56 | content = super().get() 57 | if content == self.placeholder: 58 | return "" 59 | else: 60 | return content.strip() 61 | 62 | class Ui_MainWindow: 63 | def setupUi(self, root): 64 | root.title("微信公众号文章 by 小锋学长") 65 | root.geometry("800x700") # 固定窗口大小 66 | root.minsize(800, 700) # 最小尺寸与初始尺寸相同 67 | root.maxsize(800, 700) # 最大尺寸与初始尺寸相同 68 | root.resizable(False, False) # 禁止窗口缩放(宽度和高度都不可调整) 69 | root.configure(bg="#f0f0f0") 70 | try: 71 | root.iconbitmap("../../icon.jpg") 72 | except: 73 | pass 74 | 75 | self.header_font = tkFont.Font(family="华文楷体", size=12, weight="bold") 76 | self.label_font = tkFont.Font(family="宋体", size=10) 77 | self.entry_font = tkFont.Font(family="宋体", size=12) # 保持稍大的字体增加高度 78 | 79 | self.tab_control = ttk.Notebook(root) 80 | self.tab_control.grid(row=0, column=0, sticky='nsew') 81 | 82 | # 配置窗口权重,使内容随窗口大小调整 83 | root.grid_rowconfigure(0, weight=1) 84 | root.grid_columnconfigure(0, weight=1) 85 | 86 | self.tab1 = ttk.Frame(self.tab_control) 87 | self.tab2 = ttk.Frame(self.tab_control) 88 | self.tab_control.add(self.tab1, text=' 公众号搜文章 ') 89 | self.tab_control.add(self.tab2, text=' 关键词搜文章 ') 90 | 91 | self.setup_tab1() 92 | self.setup_tab2() 93 | 94 | def setup_tab1(self): 95 | # 顶部标题区域 96 | top_frame = tk.Frame(self.tab1, bg="#f0f0f0") 97 | top_frame.grid(row=0, column=0, columnspan=2, sticky='nsew', padx=5, pady=5) 98 | self.label_head = tk.Label(top_frame, text="****************************************************************************\n* 程序原理: 通过selenium登录获取token和cookie,再自动爬取和下载\n* 使用前提: 申请一个微信公众号 (https://mp.weixin.qq.com)\n开源链接: https://github.com/1061700625/WeChat_Article\n Copyright © SXF 本软件禁止一切形式的商业活动\n****************************************************************************", 99 | font=self.header_font, justify="left", bg="#e0f7fa", fg="#006064", padx=10, pady=10, relief="flat", wraplength=780) 100 | self.label_head.grid(row=0, column=0, sticky='nsew') 101 | 102 | # 配置顶部框架权重 103 | top_frame.grid_columnconfigure(0, weight=1) 104 | top_frame.grid_rowconfigure(0, weight=1) 105 | 106 | # 中间区域:表单 + 按钮(使用 grid 布局) 107 | middle_frame = tk.Frame(self.tab1, bg="#f0f0f0") 108 | middle_frame.grid(row=1, column=0, columnspan=2, sticky='nsew', padx=5, pady=5) 109 | 110 | # 左侧表单区域(垂直排列) 111 | form_frame = tk.Frame(middle_frame, bg="#f0f0f0") 112 | form_frame.grid(row=0, column=0, sticky='nsew', padx=(0, 5)) 113 | 114 | labels = ["目标公众号英文名", "个人公众号账号", "个人公众号密码", "查询间隔(s)", "微信uin和key", "时间范围(年)"] 115 | placeholders = [ 116 | "为空则默认新华社(xinhuashefabu1)", 117 | "为空则自动打开页面后手动输入", 118 | "为空则自动打开页面后手动输入", 119 | "为空则默认为5s,一页约10条", 120 | "URL全复制进来,通过Fiddler抓包", 121 | "" 122 | ] 123 | self.entries = {} 124 | label_width = 15 125 | for i, (label, placeholder) in enumerate(zip(labels, placeholders)): 126 | tk.Label(form_frame, text=label, font=self.label_font, bg="#f0f0f0", width=label_width, anchor='w').grid(row=i, column=0, padx=5, pady=2, sticky='w') 127 | if label == "时间范围(年)": 128 | time_frame = tk.Frame(form_frame, bg="#f0f0f0") 129 | time_frame.grid(row=i, column=1, sticky='w', pady=2) 130 | self.entries["timeStart"] = PlaceholderEntry(time_frame, placeholder="1999", width=8, font=self.entry_font) 131 | self.entries["timeStart"].grid(row=0, column=0, padx=2, pady=2) 132 | tk.Label(time_frame, text="-", bg="#f0f0f0").grid(row=0, column=1, padx=2) 133 | self.entries["timeEnd"] = PlaceholderEntry(time_frame, placeholder=str(datetime.now().year), width=8, font=self.entry_font) 134 | self.entries["timeEnd"].grid(row=0, column=2, padx=2, pady=2) 135 | tk.Label(time_frame, text="关键词", font=self.label_font, bg="#f0f0f0", width=8, anchor='w').grid(row=0, column=3, padx=5, pady=2, sticky='w') 136 | self.entries["keyword"] = tk.Entry(time_frame, width=20, font=self.entry_font) 137 | self.entries["keyword"].grid(row=0, column=4, padx=2, pady=2) 138 | else: 139 | self.entries[label] = PlaceholderEntry(form_frame, placeholder=placeholder, width=60, font=self.entry_font) 140 | self.entries[label].grid(row=i, column=1, padx=5, pady=2, sticky='w') 141 | if label == "个人公众号密码": 142 | self.entries[label].config(show='*') 143 | ToolTip(self.entries[label], placeholder) 144 | 145 | # 配置表单框架权重 146 | form_frame.grid_columnconfigure(1, weight=1) 147 | for i in range(len(labels)): 148 | form_frame.grid_rowconfigure(i, weight=0) 149 | 150 | # 右侧按钮区域(垂直排列) 151 | button_frame = tk.Frame(middle_frame, bg="#f0f0f0") 152 | button_frame.grid(row=0, column=1, sticky='ns', padx=5) 153 | 154 | self.pushButton_start = tk.Button(button_frame, text="启动 (*^▽^*)", command=self.Start_Run, bg="#4caf50", fg="white", font=self.label_font, relief="flat", padx=10, pady=5) 155 | self.pushButton_start.grid(row=0, column=0, pady=5, sticky='ew') 156 | self.CheckVar = tk.IntVar() # 创建变量 157 | self.checkBox = tk.Checkbutton(button_frame, text="记住密码", font=self.label_font, bg="#f0f0f0", variable=self.CheckVar) 158 | self.checkBox.grid(row=1, column=0, pady=5, sticky='w') 159 | self.pushButton_stop = tk.Button(button_frame, text="终止  ̄へ ̄", command=self.Stop_Run, bg="#f44336", fg="white", font=self.label_font, relief="flat", padx=10, pady=5) 160 | self.pushButton_stop.grid(row=2, column=0, pady=5, sticky='ew') 161 | 162 | # 配置按钮框架权重 163 | button_frame.grid_rowconfigure((0, 2), weight=0) 164 | button_frame.grid_rowconfigure(1, weight=1) 165 | button_frame.grid_columnconfigure(0, weight=1) 166 | 167 | # 底部区域:进度条 + 表格 + 调试信息 168 | bottom_frame = tk.Frame(self.tab1, bg="#f0f0f0") 169 | bottom_frame.grid(row=2, column=0, columnspan=2, sticky='nsew', padx=5, pady=5) 170 | 171 | self.progressBar = ttk.Progressbar(bottom_frame, orient='horizontal', mode='determinate', maximum=100) 172 | self.progressBar.grid(row=0, column=0, columnspan=2, sticky='ew', pady=(0, 5)) 173 | 174 | table_notes_frame = tk.Frame(bottom_frame, bg="#f0f0f0") 175 | table_notes_frame.grid(row=1, column=0, columnspan=2, sticky='nsew') 176 | 177 | self.tableWidget_result = ttk.Treeview(table_notes_frame, columns=('Title', 'URL'), show='headings', height=10) 178 | self.tableWidget_result.heading('Title', text='标题') 179 | self.tableWidget_result.heading('URL', text='链接') 180 | self.tableWidget_result.column('Title', width=300) # 确保不超过窗口 181 | self.tableWidget_result.column('URL', width=200) 182 | self.tableWidget_result.grid(row=0, column=0, sticky='nsew', padx=(0, 5)) 183 | 184 | self.label_notes = tk.Label(table_notes_frame, text="调试信息窗口", font=self.label_font, bg="#fff3e0", fg="#e65100", relief="sunken", anchor='nw', justify='left', wraplength=300, width=35, height=10, padx=5, pady=5) 185 | self.label_notes.grid(row=0, column=1, sticky='nsew', padx=5) 186 | 187 | # 配置底部框架权重 188 | bottom_frame.grid_rowconfigure(1, weight=1) 189 | bottom_frame.grid_columnconfigure(0, weight=1) 190 | table_notes_frame.grid_columnconfigure(0, weight=3) # 表格占更多空间 191 | table_notes_frame.grid_columnconfigure(1, weight=1) # 调试窗口占少部分 192 | table_notes_frame.grid_rowconfigure(0, weight=1) 193 | 194 | # 配置主窗口权重 195 | self.tab1.grid_rowconfigure((0, 1, 2), weight=0) 196 | self.tab1.grid_columnconfigure(0, weight=3) # 表单和表格占更多 197 | self.tab1.grid_columnconfigure(1, weight=1) # 按钮占少部分 198 | 199 | def setup_tab2(self): 200 | # 顶部标题区域 201 | top_frame = tk.Frame(self.tab2, bg="#f0f0f0") 202 | top_frame.grid(row=0, column=0, columnspan=2, sticky='nsew', padx=5, pady=5) 203 | self.label_head_2 = tk.Label(top_frame, text="****************************************************************************************************\n* Demo 说明: \n先在“公众号搜文章”页填完整信息,再在本页填入关键词,点击“启动”即可\n Copyright © SXF 本软件禁止一切形式的商业活动\n****************************************************************************************************", 204 | font=self.header_font, justify="left", bg="#e0f7fa", fg="#006064", padx=10, pady=10, relief="flat", wraplength=780) 205 | self.label_head_2.grid(row=0, column=0, sticky='nsew') 206 | 207 | # 配置顶部框架权重 208 | top_frame.grid_columnconfigure(0, weight=1) 209 | top_frame.grid_rowconfigure(0, weight=1) 210 | 211 | # 中间区域:关键词 + 按钮 212 | middle_frame = tk.Frame(self.tab2, bg="#f0f0f0") 213 | middle_frame.grid(row=1, column=0, columnspan=2, sticky='nsew', padx=5, pady=5) 214 | 215 | content_frame = tk.Frame(middle_frame, bg="#f0f0f0") 216 | content_frame.grid(row=0, column=0, sticky='w', pady=10) 217 | tk.Label(content_frame, text="关键词", font=self.label_font, bg="#f0f0f0", width=10, anchor='w').grid(row=0, column=0, padx=5, pady=2, sticky='w') # 减少标签宽度到100 218 | self.lineEdit_keyword_2 = tk.Entry(content_frame, width=35, font=self.entry_font) # 保持35,确保不超过窗口 219 | self.lineEdit_keyword_2.grid(row=0, column=1, padx=5, pady=2, sticky='w') 220 | 221 | button_frame = tk.Frame(middle_frame, bg="#f0f0f0") 222 | button_frame.grid(row=0, column=1, sticky='e', pady=10) 223 | self.pushButton_start_2 = tk.Button(button_frame, text="启动 (*^▽^*)", command=self.Start_Run_2, bg="#4caf50", fg="white", font=self.label_font, relief="flat", padx=10, pady=5) 224 | self.pushButton_start_2.grid(row=0, column=0, padx=5, pady=5, sticky='e') 225 | self.pushButton_stop_2 = tk.Button(button_frame, text="终止  ̄へ ̄", command=self.Stop_Run_2, bg="#f44336", fg="white", font=self.label_font, relief="flat", padx=10, pady=5) 226 | self.pushButton_stop_2.grid(row=1, column=0, padx=5, pady=5, sticky='e') 227 | 228 | # 配置中间框架权重 229 | middle_frame.grid_columnconfigure(0, weight=3) # 关键词占更多 230 | middle_frame.grid_columnconfigure(1, weight=1) # 按钮占少部分 231 | middle_frame.grid_rowconfigure(0, weight=1) 232 | 233 | # 配置主窗口权重 234 | self.tab2.grid_rowconfigure((0, 1), weight=0) 235 | self.tab2.grid_columnconfigure((0, 1), weight=1) 236 | 237 | def Start_Run(self): 238 | self.progressBar['value'] = 20 239 | self.label_notes.config(text="正在启动...\n请稍候...") 240 | 241 | def Stop_Run(self): 242 | self.progressBar['value'] = 0 243 | self.label_notes.config(text="已终止") 244 | 245 | def Start_Run_2(self): 246 | pass 247 | 248 | def Stop_Run_2(self): 249 | pass 250 | 251 | if __name__ == "__main__": 252 | root = tk.Tk() 253 | ui = Ui_MainWindow() 254 | ui.setupUi(root) 255 | root.mainloop() -------------------------------------------------------------------------------- /yf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1061700625/WeChat_Article/e17f05a689320fcae05ebf400e5880ec1844ce30/yf.png -------------------------------------------------------------------------------- /抓取文章图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1061700625/WeChat_Article/e17f05a689320fcae05ebf400e5880ec1844ce30/抓取文章图.png -------------------------------------------------------------------------------- /效果图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1061700625/WeChat_Article/e17f05a689320fcae05ebf400e5880ec1844ce30/效果图.png --------------------------------------------------------------------------------