我从「关注贴吧工具」中学会了什么

放假前最后一篇博客.

一. 背景

大概一年前在 GitHub 上面看到一个用 Python 实现贴吧签到的程序, 后来通过抓包我实现了发帖丶关注贴吧和取关贴吧功能.

这个学期实训之前在贴吧看到有人问「批量关注贴吧」的软件, 尽管已经可以通过代码来实现这个功能, 但是总不可能直接把代码丢给别人用, 后来不知道怎么就选择用 PyQt 来开发, 碰巧这两周的实训内容是 Qt.

下面是成果, 的确是很丑, 而且如果网络出现错误, 或者某一次请求出错, 就有可能出现问题, 这些我都没有做好.

现在记录一下自己在这几天学到的东西.

二. Python 中的多线程

原来对 Python 中的多线程一点也不懂, 可是发送网络请求不能在主线程中进行, 因为这些操作可能很耗时, 就会导致程序无响应, 用户体验极差.

虽然在廖雪峰老师的博客中看了多线程的内容, 但是我掌握的内容也只是满足这个应用的需求.

希望程序同时做几件事, 有两种方法, 分别是多进程和多线程, 因为多进程中操作的可能不是同一个对象, 而我想要的是在主线程之外做某件事, 而且可以修改主线程里面的数据. 比如我希望获取到「我关注的吧」, 并且不会让程序卡住, 最后在主线程中得到所关注贴吧的数据.

所以多线程可以满足我的需求, 多线程的条件是:

  • 新建一个类, 继承自线程类
  • 重写 run() 方法
  • 在主线程中实例化并调用 start() 方法

在 PyQt 中需要继承 QThread 类, 具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class my_thread_login(QThread):# 继承自 QThread 类

login_success = pyqtSignal()
login_failure = pyqtSignal()

# 构造方法
def __init__(self, tb=None, parent=None):
super(my_thread_login, self).__init__(parent)
self.tb = tb

# 重写 run() 方法
def run(self):
try:
self.tb.profile()
self.login_success.emit()
except:
self.login_failure.emit()

主线程中需要启用子线程的时候:

1
2
self.my_thread_login = my_thread_login(self.tieba)# 实例化
self.my_thread_login.start()# 调用 start() 方法

通过上面的方法就实现了在子线程中进行耗时的操作了, 而且在实例化的时候将主线程的一个对象传过去, 这样主线程中也可以得到获取到的数据.

三. MVC

对于这个程序而言:

  • M 是程序中保存信息的对象, 在这个程序中就是 TieBa 类, 包含了 BDUSS 之类的信息.
  • V 就是用户界面, 也就是用 PyQt 写出来的界面
  • C 是 control 的缩写, 作用是连接 M 和 V

很庆幸那天早上看到了那个视频, 虽然不是中文的, 但是代码写得很清楚. 如果我没有看, 也许我写不出这个程序, 也可能我的代码会非常混乱. 因为我会把所有内容写在一起, 比如: 在 MainWindow 类中包含 TieBa 对象, 然后定义信号和槽, 点击按钮对 TieBa 对象进行操作, 可是这样我就不知道怎么实现多线程了.

这个程序主要有 MainWindow, Control 和 TieBa, 下面看看它们分别是干嘛的.

1. Control 类

在这个程序里面, MainWindow 类中有一个 Control 类, Control 类构造函数中接受一个 MainWindow 对象, 同时初始化一个 TieBa 类.

如下:

1
2
3
def __init__(self, MainWindow):
self.tieba = TieBa("")
self.mainwindow = MainWindow

这样在 Control 对象中就可以访问到 MainWindow 和 TieBa, 在必要时改变界面的样子或者修改数据.

Control 类主要是进行各种操作, 也就是在这里启用子线程. 上面也已经说过, 只需要实例化一个线程类的对象, 然后调用 start() 方法. 别忘记把 TieBa 对象传进去, 因为子线程需要访问到其中的数据, 而且发送请求之后也要更新数据.

2. MainWindow 类

MainWindow 对象中怎么跟 Control 联系起来呢 ?

答案是将 MainWindow 中的信号和 Control 中的方法连接起来.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 绑定登陆按钮
self.btn_login.clicked.connect(self.controlor.login)

# 更新我喜欢的吧
self.btn_up_mylike.clicked.connect(self.controlor.up_mylikes)

# 获取本地列表
self.btn_get_local_list.clicked.connect(self.controlor.get_local_list)

# 开始关注贴吧
self.btn_to_like.clicked.connect(self.controlor.to_like)

# 批量取关按钮
self.btn_to_del.clicked.connect(self.controlor.to_del)

这样 MainWindow 类就只需要负责界面的设计, 将各种操作放到 Control 类, 编写代码的时候思路也不用那么混乱.

3. TieBa 类

这个类没什么好说的了, 就是包含 BDUSS 等数据, 同时有发帖丶签到之类的方法, 主要是在 Control 中被调用.

我对 MVC 理解不深, 不知道 TieBa 中的方法是不是应该放到 Control 中实现.

四. 子线程更新主界面

程序在子线程中发送请求, 获取到数据之后肯定要显示在用户界面中, 之前是将 MainWindow 传递到子线程中, 结果发现达不到效果(也不知道为什么).

后来实现的方法是, 在线程类中定义信号, 在 Control 定义更新界面的方法, 然后将两者连接起来.

1
2
3
4
5
6
self.my_thread_login = my_thread_login(self.tieba)
self.my_thread_login.start()
self.my_thread_login.login_success.connect(self.login_yes)
# self.login_yes 是 Control 类的方法.
# self.my_thread_login.login_success 是线程类中信号
self.my_thread_login.login_failure.connect(self.login_no)

当子线程完成网络请求之后, 就发出成功或者失败的信号, 调用 Control 类中的方法更新界面, 这就达到了效果.

五. 总结

  • Python 的基础部分我还没有掌握好, Java 也半途而废了…
  • 多线程很有用, 但是需要一些额外的知识, 以后的 《操作系统》 要认真学
  • 写程序的思路还是很重要的, 不过我对 MVC 的理解还是太肤浅了
  • 知道了在 PyQt 中如何更新界面, 但是换一种编程语言就不懂了