From 8b9e813ee2b3f538ee7908d61c7628744090afcd Mon Sep 17 00:00:00 2001 From: zj3D Date: Thu, 6 Mar 2025 16:40:04 +0800 Subject: [PATCH] 2501 --- .gitignore | 4 + .../10 一盘大棋/1 最基础的写法.py | 6 + .../10 一盘大棋/2 加入语言特性.py | 1 + A 代码模式/10 一盘大棋/3 Hacker.py | 1 + B 高性能模式/000 普通做法.py | 47 +++--- B 高性能模式/1.md | 134 ++++++++++++++++++ B 高性能模式/readme.md | 3 + B 高性能模式/util.py | 17 +-- 8 files changed, 176 insertions(+), 37 deletions(-) create mode 100644 B 高性能模式/1.md diff --git a/.gitignore b/.gitignore index 61f4419..1de8013 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ log.txt /test /.venv __pycache__ +B 高性能模式/log +B 高性能模式/test.py +.vscode/launch.json +.gitignore diff --git a/A 代码模式/10 一盘大棋/1 最基础的写法.py b/A 代码模式/10 一盘大棋/1 最基础的写法.py index e70362d..70cf2d0 100644 --- a/A 代码模式/10 一盘大棋/1 最基础的写法.py +++ b/A 代码模式/10 一盘大棋/1 最基础的写法.py @@ -48,3 +48,9 @@ for i in range(n): # 打印频率最高的前10个词 for tf in word_freqs[:10]: print(tf[0], '-', tf[1]) + + +''' +想到哪里写到哪里 +用的最基础的编程思想,没有使用 Python 高级语法特性、数据结构和算法 +''' \ No newline at end of file diff --git a/A 代码模式/10 一盘大棋/2 加入语言特性.py b/A 代码模式/10 一盘大棋/2 加入语言特性.py index f48b2c4..be03bdd 100644 --- a/A 代码模式/10 一盘大棋/2 加入语言特性.py +++ b/A 代码模式/10 一盘大棋/2 加入语言特性.py @@ -23,6 +23,7 @@ with open(testfilepath, encoding='utf8') as f: # 打印前10个最常见的单词 for word, freq in word_freqs.most_common(10): print(f"{word}-{freq}") + ''' 相比 A01 使用collections.Counter来计数单词频率,从而简化了代码并提高了效率。 diff --git a/A 代码模式/10 一盘大棋/3 Hacker.py b/A 代码模式/10 一盘大棋/3 Hacker.py index 2576218..1f1598f 100644 --- a/A 代码模式/10 一盘大棋/3 Hacker.py +++ b/A 代码模式/10 一盘大棋/3 Hacker.py @@ -8,6 +8,7 @@ words = re.findall('[a-z]{2,}', counts = collections.Counter(w for w in words if w not in stopwords) for (w, c) in counts.most_common(10): print(w, '-', c) + ''' 熟练的软件工程师,会如此简单完成任务 后面的例子,我们必须变的啰嗦一些,不能用这种太 hacker 的写法 diff --git a/B 高性能模式/000 普通做法.py b/B 高性能模式/000 普通做法.py index a97e3b2..484f740 100644 --- a/B 高性能模式/000 普通做法.py +++ b/B 高性能模式/000 普通做法.py @@ -1,25 +1,18 @@ """ -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为普通做法,即使用requests库通过Post请求爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` + 此方法为普通做法,即使用requests库通过Post请求爬取网页内容,再使用json提取新闻内容。 + 注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 + Args: + keywords: 用于搜索新闻的关键词列表 + begin_date: 开始日期,用于搜索 + end_date: 结束日期,用于搜索 + size: 一次请求返回的新闻或政策的最大数量 + Examples: + ``` + main(keywords=['灾害'], + begin_date='2018-01-01', + end_date='2018-12-31', + size=10) + ``` """ import util import logging @@ -47,10 +40,10 @@ def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): logging.info("开始运行普通爬取") - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) + spider = util.Spider( keywords=keywords, + begin_date=begin_date, + end_date=end_date, + size=size) pbar = tqdm.tqdm(total=size * 10, desc='普通爬取进度', unit='条', ncols=80) title_list = [] @@ -68,7 +61,7 @@ def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): if __name__ == "__main__": - main(keywords=['灾害'], + main(keywords=['经济'], begin_date='2018-01-01', end_date='2018-12-31', - size=10) + size=10) \ No newline at end of file diff --git a/B 高性能模式/1.md b/B 高性能模式/1.md new file mode 100644 index 0000000..10f04d7 --- /dev/null +++ b/B 高性能模式/1.md @@ -0,0 +1,134 @@ + +### 多线程与异步编程 + +对于 **I/O 密集型任务**,**异步编程** 通常比 **多线程** 是更好的选择。异步编程特别适合高并发的 I/O 密集型任务(如 Web 服务器、爬虫、实时通信), 特别是大量并发连接的任务。 + +多线程比相对编程简单 。 + +### 场景:**GUI 应用程序** + +在 GUI(图形用户界面)应用程序中,主线程负责处理用户交互,而其他任务(如文件读写、网络请求)需要在后台运行,以避免阻塞主线程导致界面卡顿。多线程可以与 GUI 主线程共享内存,方便更新界面状态。线程间通信简单,适合处理后台任务。GUI 框架(如 PyQt、Tkinter)通常有自己的事件循环,与异步编程的事件循环冲突。 + + +import sys +import requests +from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel +from PyQt5.QtCore import QThread, pyqtSignal + +# 工作线程:负责下载文件 +class DownloadThread(QThread): + # 自定义信号,用于通知主线程下载进度 + progress_signal = pyqtSignal(str) + + def __init__(self, url): + super().__init__() + self.url = url + + def run(self): + self.progress_signal.emit("开始下载...") + try: + response = requests.get(self.url, stream=True) + total_size = int(response.headers.get("content-length", 0)) + downloaded_size = 0 + with open("downloaded_file", "wb") as file: + for chunk in response.iter_content(chunk_size=1024): + file.write(chunk) + downloaded_size += len(chunk) + progress = f"已下载: {downloaded_size / 1024:.2f} KB / {total_size / 1024:.2f} KB" + self.progress_signal.emit(progress) + self.progress_signal.emit("下载完成!") + except Exception as e: + self.progress_signal.emit(f"下载失败: {str(e)}") + +# 主窗口 +class MainWindow(QWidget): + def __init__(self): + super().__init__() + self.init_ui() + + def init_ui(self): + self.setWindowTitle("多线程下载示例") + self.setGeometry(100, 100, 300, 150) + + # 布局 + layout = QVBoxLayout() + + # 下载按钮 + self.download_button = QPushButton("开始下载", self) + self.download_button.clicked.connect(self.start_download) + layout.addWidget(self.download_button) + + # 状态标签 + self.status_label = QLabel("点击按钮开始下载", self) + layout.addWidget(self.status_label) + + self.setLayout(layout) + + def start_download(self): + # 禁用按钮,防止重复点击 + self.download_button.setEnabled(False) + self.status_label.setText("准备下载...") + + # 创建工作线程 + self.download_thread = DownloadThread("https://example.com/large_file.zip") + self.download_thread.progress_signal.connect(self.update_status) + self.download_thread.finished.connect(self.on_download_finished) + self.download_thread.start() + + def update_status(self, message): + # 更新状态标签 + self.status_label.setText(message) + + def on_download_finished(self): + # 下载完成后启用按钮 + self.download_button.setEnabled(True) + +# 运行应用程序 +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec_()) + + + + +以下场景更适合使用 **多线程**: +- +1. **与阻塞式 API 交互**。 某些库或 API 是阻塞式的(如某些数据库驱动、硬件接口库),无法直接使用异步编程。在这种情况下,多线程可以避免阻塞主线程。 + +import threading +import time +import sqlite3 + +def query_database(): + # 模拟阻塞式数据库查询 + conn = sqlite3.connect("example.db") + cursor = conn.cursor() + cursor.execute("SELECT * FROM users") + results = cursor.fetchall() + print("查询完成,结果:", results) + conn.close() + +def main(): + print("主线程开始") + # 创建线程执行数据库查询 + thread = threading.Thread(target=query_database) + thread.start() + # 主线程继续执行其他任务 + for i in range(5): + print(f"主线程运行中... {i}") + time.sleep(1) + thread.join() + print("主线程结束") + +main() + + +1. **实时数据处理**。 在实时数据处理场景中(如音频处理、视频流处理),需要快速响应并处理数据,同时保持主线程的响应性。 + +2. **任务队列与线程池**。在需要处理大量短期任务的场景中(如 Web 服务器的请求处理),使用线程池可以高效地管理任务。 + +3. **与 C/C++ 扩展交互**。 某些 Python 库是基于 C/C++ 扩展实现的(如 `numpy`、`pandas`),这些扩展可能释放了 GIL,允许在多线程中并行运行。 + +4. **需要共享状态的场景**。 在某些场景中,多个任务需要频繁共享和修改状态(如缓存、计数器),使用多线程可以方便地共享内存。 diff --git a/B 高性能模式/readme.md b/B 高性能模式/readme.md index 7470dbf..fee0880 100644 --- a/B 高性能模式/readme.md +++ b/B 高性能模式/readme.md @@ -7,6 +7,9 @@ # 任务 +根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 +本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 + # 讨论分析 普通做法连续进行了五次测试,时间分别为34.231s、34.091s、34.164s、34.226s、33.958s,平均时间为34.134s diff --git a/B 高性能模式/util.py b/B 高性能模式/util.py index 5d7495a..88e1655 100644 --- a/B 高性能模式/util.py +++ b/B 高性能模式/util.py @@ -1,6 +1,3 @@ -""" - -""" import re import time import functools @@ -12,7 +9,7 @@ from typing import Any, Dict, List class Spider: """ - 爬虫类。 + 爬虫类 Args: keywords (List[str]): 用于搜索新闻的关键词列表 @@ -79,8 +76,7 @@ class Spider: def generate_headers(self) -> dict: """ - 生成请求头。 - + 生成请求头 Returns: dict: 请求头 """ @@ -109,6 +105,7 @@ class Spider: json=config).text time.sleep(3) return json.loads(response) + async def fetch_async(self, config: Dict[str, Any]) -> Dict[str, Any]: """ @@ -126,6 +123,7 @@ class Spider: json=config).text await asyncio.sleep(3) return json.loads(response) + def parse(self, data: Dict[str, Any]) -> List[str]: """ @@ -145,6 +143,7 @@ class Spider: title_list.append(title) # print(title) return title_list + def save(self, title_list: List[str]): """ @@ -156,11 +155,9 @@ class Spider: # 时间装饰器 def timeit(func): """ - 计算函数运行时间。 - + 计算函数运行时间 Args: func: 函数 - Return: 函数 """ @@ -185,4 +182,4 @@ def timeit_async(func): print(f'{func.__name__} cost: {time.time() - start}') return result - return wrapper + return wrapper \ No newline at end of file