python多线程案例——多线程爬取小说

devtools/2024/10/23 20:28:28/

多线程案例——多线程爬取小说

请添加图片描述

  1. 生产者——————产生URL
  2. 消费者兼生产者——下载小说
  3. 消费者——————合并小说
  4. 主函数——————函数入口

注意事项

这里我们使用了队列queue来储存URL,需要提取导入一下队列,我们在主函数中让队列实例化,指定大小使用maxsize参数

代码展示

python"># 导入库
import re
import time
import requests
import threading
import queue
import os
from lxml import etreelock = threading.Lock()end_work = False # 判断生产者是否完成工作
novel_title = ''total_numbers = 0  # 给合并板块的线程一个参考的范围,这里是为了告诉它一共有多少个文件# 生产者————产生URL
def get_url(index_url,q):global novel_title,end_work,total_numbers# 从目录页获取总页数和第一页的网址格式rt = requests.get(url=index_url)# print(rt.text)html = etree.HTML(rt.text)# 创建文件夹,放小说novel_title = html.xpath('//div[@class="infotitle"]//h1//text()')[1] # 拿到小说名称if not os.path.exists(f'/{novel_title}'): # 创建小说文件夹os.mkdir(f'/{novel_title}')result1 = html.xpath('//div[@class="tag"]/a/@href')  # 得到页数所在的标签Total_pages = re.findall('/read_(\\d+).html',result1[0])[0]   # 获取标签里面的页数信息# Total_pages = 10   # 为了展示效果,这个Total_pages暂且设为10 ,把这条屏蔽就是全部小说了total_numbers = Total_pages # 把总页数传给合并线程# print(Total_pages)for link in range(1, int(Total_pages) + 1): # 因为是左闭右开,所以右边加一url = re.sub(f'/read_.*',f'/read_{link}.html',result1[0])q.put(url)# print(url)# 完成所有URL的制作print('生产者URL完成工作')end_work = True# 生产者兼消费者————下载URL里面的小说到本地
def download_novel(q):global novel_title,end_workwhile not end_work: # 生产者URL还没生成完毕time.sleep(0.5)while True:if q.empty() and end_work:returnlock.acquire()url = q.get()lock.release()pages = re.findall('/read_(\\d+).html', url)[0] # 获取小说的章节rt = requests.get(url=url)# print(rt.text)html = etree.HTML(rt.text)result2 = html.xpath('//div[@class="content"]//text()')  # 得到小说文章result2 = "".join(result2) # 字符串拼接result2 = result2.replace('\u3000\u3000','\n') # 文本处理完毕# 写入文件title = html.xpath('//div[@class="title"]//a/text()')[0]with open(f'/{novel_title}/{pages}.text','w',encoding='utf-8') as f:f.write(str(title)+'\n' + result2 + '\n\n')print(f'{threading.current_thread().name}已下载……{title}')# 消费者——合并小说  # 其实在上面那个板块里面,把小说以追加的形式写在一个文件里面也能完成合并,这里是为了展示代码能力
def Combined_novel():global total_numbers,novel_title,end_workwhile not end_work:time.sleep(0.5)num = 1fp = open(f'/{novel_title}/{novel_title}.text','w',encoding='utf-8')  # 以追加的方式创建合并小说集,如果文件不存在就会新建一个文件fp.close()fp = open(f'/{novel_title}/{novel_title}.text','a',encoding='utf-8')while True:if int(num) > int(total_numbers):breakif os.path.exists(f"/{novel_title}/{num}.text"):with open(f"/{novel_title}/{num}.text",'r',encoding='utf-8') as f:text = f.read()fp.write(text)num += 1print(f'已合并……章节{num-1}')os.remove(f"/{novel_title}/{num-1}.text")print(f'已删除……章节{num-1}')else:print(f'未发现……章节{num}')time.sleep(1) # 等待小说下载fp.close()print('已完成全部文件合并')# 函数入口
def main():q = queue.Queue(maxsize=2000)  # 这里是创建了一个空队列,设置的大小是2000index_url ='https://www.80down.cc/novel/160651/'   # 如果想下载别的小说,只需要修改这个地方就可以了# 当然,是这个网站里面的小说 ,其他网站的小说,可以按照这个格式爬取th1 = threading.Thread(target=get_url,args=(index_url,q,))th1.start()for i in range(4):th2 = threading.Thread(target=download_novel,args=(q,),name=f'生产者{i}号')th2.start()th3 = threading.Thread(target=Combined_novel)th3.start()if __name__ == '__main__':main()

引入的库

首先介绍引入的库,还有它在代码当中发挥的作用

  1. re 这个是正则表达式,负责从字符串中提取信息,在代码中提取了小说的总页数
  2. time 这个是时间模块,在程序中是为了等待其他的线程完成工作
  3. requests 这个是网络请求模块,可以模拟浏览器向网页发送请求
  4. threading 这个是多线程模块,有了这个模块才可以开启多线程,在主函数中应用
  5. queue 这是队列模块,负责存储和出取URL
  6. os 这个是系统模块,程序中负责创建文件夹,检测文件夹是否存在
  7. from lxml import etree 这个就是xPath语法的库,不懂的看我前几篇文章
  8. lock = threading.Lock() 这个是多线程上锁的模块,主要在下载小说的线程中应用

全局变量

  1. end_work 判断生产URL是否完成工作的变量,防止下载小说的线程太快造成程序崩溃,而且这个制作URL的线程很快
  2. novel_title 这个是储存小说书名的,在下载URL时创建文件夹使用的,还有下载小说时存临时文件用,以及合并文件的路径
  3. total_numbers 告知合并小说的线程,一共有多少个文件需要合并

生产者————产生URL

和之前一样,这种比较简易的网站在设计网址的时候都是只改最后面的数字充当页数,比如/read/289414_1.html

这个下划线后面的1就是第一页,根据这个规律,可以很轻松的模拟出其他的页面链接

所以,根据小说的目录页,获取第一页小说的网址,再获取小说的总页数,这个板块对网址的请求就没用了,剩下的就是设计链接了

在这里我随手就创建了存放小说的文件夹,毕竟目录页可以很轻松的获取小说的名称啦

result1就是获取总章节的页数存到Total_pages里面,这个就是全局变量第三个的来源

后面就是把设计好的链接放到队列里面去

生产者兼消费者————下载URL里面的小说到本地

这个板块引用了两个全局变量,一个是小说的名称用来下载小说时确认路径的

一个是上面那个生产者的结束判定,毕竟要把URL先存到队列里面去才能拿出来本来是只需要让这个线程等一秒就够了,但是制作URL实在是太快了,这个判断主要作为消费者合并小说的开始标志了

我们使用一共while循环,一直循环下载小说

这里我们从队列里面拿链接的时候要锁一下,一个线程拿链接的过程其他线程不能拿,防止多个线程拿同一个链接

取完链接就是线程自己的事了,互不干扰,就能解锁了

获取一下小说的章节,便于后面保存小说时使用

再接着就是使用xPath语法获取小说的内容,这时候的内容是一个列表,使用字符串拼接函数join把列表合并

发现每句话之间都有一个\u3000\u3000的字样,我们不希望看到这个东西,使用字符串替换函数replace把他们全换成\n换行符

这时候打印一下就会发现,小说的内容已经是分行显示了且中间没有空行,很完美

最后把小说保存在本地

消费者——合并小说

这个板块可有可无,我们在上面保存文件的时候完全可以把他们保存在一个文件里面,不过由于每个线程的差异,会导致某些章节的位置错乱,把这两个板块分离还是有点好处的

这个板块就不需要使用requests库了

完全就是文件操作,读取一个个单篇的小说,再按顺序保存到一个文件中去

这里的读取使用只读r的方式

保存文件使用追加的方式a,我们使用追加的方式打开文件时,按理来说,文件不存在应该会新建一个文件,但是程序报错了

这里我使用了先以只写的方式w创建文件,再关闭文件,再使用追加的方式打开文件,就解决的这个问题

在读取小说的时候,我们要按照顺序读取,所以还要使用os库判断一下小说的章节存不存在,如果存在就读取追加,然后num+1

这里的num就是我控制小说章节顺序的依据,我在前面设计保存小说的时候就是以章节数字命名,所以无论下载时间的前后都不影响按顺序读取小说

读取完的小说没有什么价值了,就把读取后的章节删除

毕竟看小说还是喜欢一直连续的看,不喜欢一直关闭打开文件吧

主函数——函数入口

没什么好说的

只有一个队列的实例化,还有多线程的启动

这里只有板块二的工作更能证明多线程的优点

还是希望读到这里的小伙伴可以跟我交流一下哪里有疑惑,大家需要互相成长,毕竟金无足赤,人无完人

完~~


http://www.ppmy.cn/devtools/128240.html

相关文章

【C++语言】深入学习C++要修炼的内功

一、进程虚拟地址空间区域划分 我们先来分析以下代码: int gdata1 10; int gdata2 0; int gdata3;static int gdata4 11; static int gdata5 0; static int gdata6;int main() {int a 12;int b 0;int c;static int e 14;static int f 0;static int g;retu…

unity学习笔记-安装与部署

unity学习笔记-安装与部署 unity & visual studio下载unityvisual studio 创建工程项目内的布局介绍初始化项目各目录介绍1. 场景视图(Scene)2. 游戏视图(Game)3. 层次结构视图(Hierarchy)4. 检查器视图…

#每日一题#自动化 2024年10月

#每日一题#自动化 2024年10月 1、深拷贝和浅拷贝的区别是什么? 参考答案: 深拷贝是将对象本身复制给另一个对象。这意味着如果对对象的副本进行更改时不会影响原对象。在 Python 中,我们使用 deepcopy()函数进行深拷贝…

rootless模式下测试istio Ambient功能

前置需求 rootless k8s测试环境搭建:https://blog.csdn.net/longtds/article/details/142916697 istio Ambient istio安装 通过加速下载istio release包,解压并安装为ambient模式 wget https://mirror.ghproxy.com/https://github.com/istio/istio/r…

ESP32-S3学习笔记:分区表(Partition Table)的二进制分析

目录 一、参考资料 二、准备工作 三、开始分析 一、参考资料 用于研究的官方示例代码:esp-idf-v5.3\examples\storage\partition_api\partition_find参考的官方文档:ESP-IDF编程指南:分区表 二、准备工作 用VS Code打开示例代码&#xf…

Java最全面试题->Java基础面试题->JavaSE面试题->面向对象面试题

文章目录 面向对象1.面向对象和面向过程的区别2.面向对象有哪些特性3.多态的实现机制4.Java语言有哪些特点5.JDK、JRE、JVM三者的联系和区别 面向对象 下边是我自己整理的面试题,基本已经很全面了,想要的可以私信我,我会不定期去更新思维导图…

C++中的vector使用与实现

一、vector的使用 1.1 vector的定义 是一种类模板 template < class T, class Alloc allocator<T> > class vector; 其中的模板参数Alloc是在使用空间配置器&#xff08;内存池&#xff09;&#xff0c;并给了缺省值&#xff0c;暂时不深究 1.2遍历方式 1.…

12、论文阅读:SpikeYOLO:高性能低能耗目标检测网络

SpikeYOLO:高性能低能耗目标检测网络 前言解释介绍相关工作论文提出的方法网络输入SpikeYOLO架构概述网络输出宏观设计微观设计I-LIF脉冲神经元LIFI-LIF实验代码前言 脉冲神经网络(Spiking Neural Networks, SNNs)具有生物合理性和低功耗的优势,相较于人工神经网络(Artif…