网络爬虫学习:借助DeepSeek完善爬虫软件,增加停止任务功能

一、引言

我从24年11月份开始学习网络爬虫应用开发,经过2个来月的努力,终于完成了开发一款网络爬虫软件的学习目标。这几天对本次学习及应用开发进行一下回顾总结。前面已经发布了两篇日志:

网络爬虫学习:应用selenium从搜*狐搜索爬取新闻结果的数据

网络爬虫学习:应用selenium获取Edge浏览器版本号,自动下载对应版本msedgedriver,确保Edge浏览器顺利打开

这是第三篇日志,记录如何为软件增加停止任务功能。

二、问题描述

软件初步开发完成后,我打包成exe文件提供给小伙伴试用,有小伙伴问我,软件怎么没有停止任务的功能?如果点击开始按钮后,又不想爬取了,想换个关键字再爬,但没有停止功能,就只能等着软件完成当前的所有任务或者关闭软件,这样不合理,建议我增加一个停止功能。

这个需求是合情合理的,但奈何我的能力有限,对爬虫知识学得不够深入,关于怎么停止当前正在执行的任务还真不会。但小伙伴提出的改进意见,那么我就要努力去完善,这样也是对自己能力的提升。

三、借助DeepSeek完善软件

在前一篇日志里,我记录了通过向DeepSeek提问,获得了自动下载对应版本msedgedriver的方法,让我的爬虫软件可以在小伙伴的电脑上正常运行。这次我决定继续向DeepSeek提问,获得实现停止爬虫任务的方法。

我一共向DeepSeek提了3个问题,最终获得了我想要的答案。

第1问:“我使用python语言开发爬虫软件,用到了threading.Thread和concurrent.futures,使爬虫在多线程下运行。我现在遇到了一个问题,当开始执行爬虫任务后,我该如何终止爬虫任务?

DeepSeek思考了22秒给了我答案,答案里有两个方法。这两个方法都是通过捕获 KeyboardInterrupt(Ctrl+C)或注册信号处理器,来停止所有线程。我在pycharm上尝试运行了这两个方法中的代码,发现都需要通过按下Ctrl+F2来停止爬虫任务,而我是希望在GUI上添加一个停止按钮,点击按钮后,停止爬虫任务,显然DeepSeek提供的答案并没有达到我的目的。

第1问的答案不符合我的需求,应该是我的问题描述还不够详细,我继续提问。

第2问:“上面给出的答案需要按Ctrl+C才能终止多线程爬虫任务,而我要开发的是带GUI的爬虫软件,该软件使用到了wxpython,我希望在GUI中设置一个停止按钮,当用户点击这个按钮后,能够停止多线程爬虫任务。

这一次,DeepSeek给了我一个基本符合要求的答案,不过DeepSeek提供的Python代码直接运行会报错,需要对错误的地方做一下修改。在pycharm上实现了点击停止按钮后中断爬虫任务的目的。

(DeepSeek的解答)

(DeepSeek提供的代码运行会报错,需要修改)

(在pycharm上运行代码,可以实现停止爬虫任务)

不过我在对上述代码进行深入研究,并尝试按照我的编程风格和实际需求进行修改后发现了其中的不足。由此我提出了第3问。

第3问:“上面的答案有效,不过有一个不足,就是最终实现代码中的CrawlerTask方法里模拟爬取操作代码做得很简单,且又是在while循环中,导致如果不点击中断会无限循环下去。而实际上,爬虫任务基本是有限次数的,任务完成后会通过break退出while循环。我向知道的是在爬虫任务完成后通过break退出while循环后,如何将self.start_btn设置为Enable,将self.stop_btn设置为Disable

我的这一问虽然把需求都提出来了,但是语句不够精炼,并且还要错别字。DeepSeek的回答真是让人惊艳,它用更精炼的文字,将我的问题准确的描述了出来。

好的,我现在需要解决用户的问题。用户之前已经得到了如何在GUI中通过停止按钮终止多线程爬虫的解决方案,但现在他指出了代码中的一个不足:CrawlerTask中的模拟爬取操作过于简单,且使用了无限循环,而实际爬虫任务应该是有限次数的,完成后自动停止。用户想知道当任务自然完成时,如何正确更新按钮状态(启用开始按钮,禁用停止按钮)。

DeepSeek给出了详细的分析和具体的实现步骤,真的很赞。

(第2问的代码需要修改的地方)

(DeepSeek优化后的代码,依然有bug,需要纠错后才能运行)

(关于优化代码的关键改进说明)

(两种典型场景验证)

我将DeepSeek提供的优化后的代码赋值到pycharm中,修改掉其中的bug,运行代码,让爬虫任务运行完成,完成后软件会自动将开始按钮启用,将停止按钮禁用。

另外,DeepSeek还给出了扩展建议,我对这部分内容也进行了吸收融合。

四、功能实现

我对DeepSeek给出的答案进行了研究和融合,最终得到了具有以下功能的示例:

1.软件的GUI使用wxpython(我基本使用wxpython来制作GUI);

2.使用 threading.Event() 作为全局停止标志,所有爬虫线程定期检查 stop_event.is_set()

3.使用wx.CallAfter实现跨线程安全更新 GUI

4.软件使用的线程池ThreadPoolExecutor,并且界面中添加了滑动条控制线程池的 max_workers 参数实现动态线程数调整,默认值为3,可以在1-6之间调整; 

5.示例模拟爬取6个url,每个url生成一个任务占用一个线程,每个任务随机爬取数量不同的页数,通过异常重试机制让每一页的数据均有3次的重试机会;

6.为每个future添加回调函数,触发状态检查;

7.在CheckThreadsStatus方法中,当所有future完成时,更新按钮状态;

8.支持任务进度显示,添加计数器统计已完成任务;

9.使用Queue线程安全队列收集结果,并能在任务结束后,查看结果。

10.按钮状态逻辑:

        初始状态下:"开始爬取"按钮启用,“停止爬取”和“查看结果”按钮停用;

        执行爬虫任务状态下:"停止爬取"按钮启用,“开始爬取”和“查看结果”按钮停用;

        任务完成或停止状态下:"开始爬取"和“查看结果”按钮按钮启用,“停止爬取”停用;

有了这个模版,就可以参照它对我的爬虫软件进行改进,从而实现增加停止爬虫任务的功能了。

(软件初始状态)

(正常执行完所有爬虫任务)

(强制停止爬虫任务)

(查看爬取的结果)

五、代码展示

最后放上实现上述功能的示例代码供参考,可以直接运行。

import wx
import threading
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
import time
import randomclass CrawlerGUI(wx.Frame):def __init__(self):super().__init__(parent=None, title="多线程爬虫工具", size=wx.Size(480, 560))self.stop_event = threading.Event()  # 全局停止标志self.executor = None  # 线程池self.futures = []  # 记录所有 Future 对象self.max_workers = 3  # 同时执行线程数self.result_queue = Queue()  # 线程安全队列,用于传递爬取的结果self.completed_tasks = 0  # 任务完成数统计# 初始化 UIself.InitUI()self.Centre()self.Show()def InitUI(self):panel = wx.Panel(self)vbox = wx.BoxSizer(wx.VERTICAL)# 控制按钮区域self.start_btn = wx.Button(panel, label="开始爬取")self.stop_btn = wx.Button(panel, label="停止爬取")self.thread_slider = wx.Slider(panel, value=3, minValue=1, maxValue=6, style=wx.SL_HORIZONTAL | wx.SL_LABELS)self.view_btn = wx.Button(panel, label="查看结果")self.log_text = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY)self.stop_btn.Enable(False)  # 初始状态为不可点击self.view_btn.Enable(False)  # 初始状态为不可点击# 按钮事件绑定self.start_btn.Bind(wx.EVT_BUTTON, self.OnStart)self.stop_btn.Bind(wx.EVT_BUTTON, self.OnStop)self.thread_slider.Bind(wx.EVT_SLIDER, self.OnThreadChange)self.view_btn.Bind(wx.EVT_BUTTON, self.OnView)self.Bind(wx.EVT_CLOSE, self.on_close)  # 绑定关闭事件处理器# 布局hbox = wx.BoxSizer(wx.HORIZONTAL)hbox.Add(self.start_btn, 0, wx.ALL, 5)hbox.Add(self.stop_btn, 0, wx.ALL, 5)hbox.Add(self.view_btn, 0, wx.ALL, 5)hbox.Add(self.thread_slider, 0, wx.ALL, 5)vbox.Add(hbox, 0, wx.EXPAND)vbox.Add(self.log_text, 1, wx.EXPAND | wx.ALL, 5)panel.SetSizer(vbox)def OnStart(self, event):"""开始爬取按钮事件"""event.Skip()self.start_btn.Disable()self.stop_btn.Enable()self.view_btn.Disable()self.log_text.SetLabel("")  # 清空self.log_text.AppendText("爬虫已启动...\n")self.completed_tasks = 0  # 任务完成数重置# 重置停止标志self.stop_event.clear()# 提交任务(示例URL列表)urls = [f"https://example.com/{i}" for i in range(1, 7)]  # 示例6个URL# 初始化线程池(默认使用3个线程)self.executor = ThreadPoolExecutor(max_workers=self.max_workers)# 提交任务并绑定回调self.futures = []for url in urls:future = self.executor.submit(self.CrawlerTask, url)future.add_done_callback(self.OnTaskDone)  # 关键:添加完成回调self.futures.append(future)def OnTaskDone(self, future):"""单个任务完成时的回调"""wx.CallAfter(self.CheckThreadsStatus)  # 安全触发状态检查self.completed_tasks += 1wx.CallAfter(self.UpdateProgress)def CrawlerTask(self, url):"""处理单个URL的爬取任务"""if self.stop_event.is_set():returntry:# 模拟有限次数的爬取操作(实际替换为真实抓取逻辑)pages = random.randint(4, 10)  # 生成随机数,让每次抓取的页数不同for page in range(1, pages):  # 假设每个URL需要抓取多个页面(页数小于pages)if self.stop_event.is_set():breakretries = 3while retries > 0 and not self.stop_event.is_set():try:# 模拟抓取过程time.sleep(0.5)# 在CrawlerTask中保存结果data = f'{url}  page{page}  data'  # 模拟爬取到的数据self.result_queue.put(data)msg = f"抓取 {url} 第{page}页完成\n"wx.CallAfter(self.log_text.AppendText, msg)# 此处添加实际抓取代码:# response = requests.get(url, timeout=5)# ...breakexcept Exception:retries -= 1time.sleep(1)# 任务自然完成时提示if not self.stop_event.is_set():wx.CallAfter(self.log_text.AppendText, f"{url} 任务完成!\n")except Exception as e:wx.CallAfter(self.log_text.AppendText, f"错误: {str(e)}\n")def CheckThreadsStatus(self):"""检查线程状态并更新UI"""if all(future.done() for future in self.futures):self.start_btn.Enable()self.stop_btn.Disable()self.view_btn.Enable()self.log_text.AppendText("所有任务已完成!\n")self.executor.shutdown()  # 关闭线程池def UpdateProgress(self):"""更新任务完成进度"""progress = f"完成进度: {self.completed_tasks}/{len(self.futures)}"self.log_text.AppendText(progress + "\n")def OnStop(self, event):"""停止爬取按钮事件"""event.Skip()self.stop_btn.Disable()self.log_text.AppendText("正在停止爬虫...\n")self.stop_event.set()# 取消未开始的任务for future in self.futures:future.cancel()# 强制关闭线程池(如果使用Python 3.9+)if self.executor:self.executor.shutdown(wait=False)wx.CallAfter(self.CheckThreadsStatus)def OnThreadChange(self, event):"""变更线程数"""event.Skip()self.max_workers = self.thread_slider.GetValue()def OnView(self, event):"""查看结果"""event.Skip()wx.CallAfter(self.ProcessResults)def ProcessResults(self):"""处理结果"""self.log_text.SetLabel("")  # 清空while not self.result_queue.empty():data = self.result_queue.get()# 更新GUI或保存到文件self.log_text.AppendText(f"{data}\n")if __name__ == "__main__":app = wx.App()CrawlerGUI()app.MainLoop()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/69285.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LM Studio 部署本地大语言模型

一、下载安装 1.搜索:lm studio LM Studio - Discover, download, and run local LLMs 2.下载 3.安装 4.更改成中文 二、下载模型(软件内下载) 1.选择使用代理,否则无法下载 2.更改模型下载目录 默认下载位置 C:\Users\用户名\.lmstudio\models 3.搜…

基于知乎平台的“开源AI智能名片2 + 1链动模式S2B2C商城小程序”引流策略研究

摘要:本文聚焦于如何借助知乎平台的高权重及优质用户特性,对“开源AI智能名片2 1链动模式S2B2C商城小程序”进行有效引流。通过深入分析知乎平台的用户特点、引流规则,并结合具体的引流方法,旨在为相关项目在知乎平台实现高效用户…

Oracle CDB自动处理表空间不足脚本

之前我曾经发过一个自动处理表空间的脚本,可以通过定时任务自动处理表空间不足的问题;但是之前那个脚本没有涵盖CDB模式下的PDB,这里将脚本做了一下更新,可以处理CDB模式下多PDB的表空间问题。 传统模式的脚本请参考这个链接 Or…

在 Navicat 17 中扩展 PostgreSQL 数据类型 | 创建自定义域

定义域 以适当的格式存储数据可以确保数据完整性,防止错误,优化性能,并通过实施验证规则和支持高效数据管理来维护系统间的一致性。基于这些原因,顶级关系数据库(如PostgreSQL)提供了多种数据类型。此外&a…

Postgresql的三种备份方式_postgresql备份

这种方式可以在数据库正在使用的时候进行完整一致的备份,并不阻塞其它用户对数据库的访问。它会产生一个脚本文件,里面包含备份开始时,已创建的各种数据库对象的SQL语句和每个表中的数据。可以使用数据库提供的工具pg_dumpall和pg_dump来进行…

[Collection与数据结构] B树与B+树

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…

【基于SprintBoot+Mybatis+Mysql】电脑商城项目之修改密码和个人资料

🧸安清h:个人主页 🎥个人专栏:【Spring篇】【计算机网络】【Mybatis篇】 🚦作者简介:一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。 目录 🎃1.修改密码 -持久…

蓝桥杯小白打卡第二天

789. 数的范围 题目描述 给定一个按照升序排列的长度为 n n n 的整数数组,以及 q q q 个查询。 对于每个查询,返回一个元素 k k k 的起始位置和终止位置(位置从 0 0 0 开始计数)。 如果数组中不存在该元素,则返…

【Elasticsearch】terms聚合误差问题

Elasticsearch中的聚合查询在某些情况下确实可能存在误差,尤其是在处理分布式数据和大量唯一值时。这种误差主要来源于以下几个方面: 1.分片数据的局部性 Elasticsearch的索引通常被分成多个分片,每个分片独立地计算聚合结果。由于数据在分…

电脑可以自己换显卡吗?怎么操作

电脑是否可以自己换显卡主要取决于电脑的类型(台式机或笔记本)以及电脑的硬件配置。以下是对这一问题的详细解答及操作步骤: 一、判断电脑是否支持更换显卡 台式机:大多数台式电脑都支持更换显卡。只要主板上有PCIe插槽&#xff…

element-plus+vue3前端如何根据name进行搜索查到符合条件的数据

界面如图&#xff0c;下面的区域是接口给的所有的&#xff0c;希望前端根据输入的内容自己去匹配。 我是使用的element-plusvue3ts的写法。 <el-input v-model"filters.region" placeholder"输入区域搜索" keyup"filterRegion(filters.region)&q…

从离散傅里叶变换(DFT)到快速傅里叶变换(FFT)

摘要 离散傅里叶变换&#xff08;DFT&#xff09;是数字信号处理领域中分析信号频域特性的重要工具&#xff0c;但直接计算DFT的复杂度较高&#xff0c;限制了其在大规模数据处理中的应用。快速傅里叶变换&#xff08;FFT&#xff09;的出现显著降低了计算复杂度&#xff0c;极…

【3分钟极速部署】在本地快速部署deepseek

第一步&#xff0c;找到网站&#xff0c;下载&#xff1a; 首先找到Ollama &#xff0c; 根据自己的电脑下载对应的版本 。 我个人用的是Windows 我就先尝试用Windows版本了 &#xff0c;文件不是很大&#xff0c;下载也比较的快 第二部就是安装了 &#xff1a; 安装完成后提示…

Zookeeper入门部署(单点与集群)

本篇文章基于docker方式部署zookeeper集群&#xff0c;请先安装docker 目录 1. docker初期准备 2.启动zookeeper 2.1 单点部署 2.2 集群部署 3. Linux脚本实现快速切换启动关闭 1. docker初期准备 拉取zookeeper镜像 docker pull zookeeper:3.5.6 如果拉取时间过长&#xf…

QMK启用摇杆和鼠标按键功能

虽然选择了触摸屏&#xff0c;我仍选择为机械键盘嵌入摇杆模块&#xff0c;这本质上是对"操作连续性"的执着。   值得深思的是&#xff0c;本次开发过程中借助DeepSeek的代码生成与逻辑推理&#xff0c;其展现的能力已然颠覆传统编程范式&#xff0c;需求描述可自动…

Linux里的容器被OOM killed的两种情况

生产上遇到过几次容器实例被OOM的现象&#xff0c;总结一下LInux OOM的两种触发条件。我的虚拟机是ubuntu 24.0.4版本&#xff0c;分配4G内存&#xff0c;在我的虚拟机上复现这两种case。 一 宿主机物理内存不够 当linux上所有应用程序的内存需求加起来超出了物理内存&#x…

Windows本地部署DeepSeek-R1大模型并使用web界面远程交互

文章目录 前言1. 安装Ollama2. 安装DeepSeek-r1模型3. 安装图形化界面3.1 Windows系统安装Docker3.2 Docker部署Open WebUI3.3 添加Deepseek模型 4. 安装内网穿透工具5. 配置固定公网地址 前言 最近爆火的国产AI大模型Deepseek详细大家都不陌生&#xff0c;不过除了在手机上安…

低代码开发与传统开发:未来的技术路线选择

在科技飞速发展的当下&#xff0c;软件开发技术日新月异&#xff0c;低代码开发与传统开发作为两种重要的开发模式&#xff0c;正站在未来技术路线选择的十字路口&#xff0c;引发了众多企业和开发者的关注。它们各自有着独特的优势和适用场景&#xff0c;究竟该如何抉择&#…

二、0-1搭建springboot+vue3前后端分离-登录页面

项目仓库地址&#xff1a;zgw-admin: 从0-1搭建一个springbootvue3的项目&#xff0c;这是源码 本次主要是为了&#xff1a; a.写登录页面 b.element plus组件是否能正常使用 c.页面调用ts是否正常&#xff0c;无参和有参的函数 首页的图片&#xff1a; 页面效果 1、引入…

Spring Task之Cron表达式

&#x1f31f; Spring Task高能预警&#xff1a;你以为的Cron表达式可能都是错的&#xff01;【附实战避坑指南】 开篇暴击&#xff1a;为什么你的定时任务总在凌晨3点翻车&#xff1f; “明明设置了0 0 2 * * ?&#xff0c;为什么任务每天凌晨3点执行&#xff1f;” —— 来…