├── 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 |
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 | 
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 | 
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
--------------------------------------------------------------------------------