一级域名网站建设,做电子商务的网站,网站开发antnw,注册装修公司要多少钱才能注册时光轮回#xff0c;冬去春来#xff0c;转眼时间来到了2023年4月。天空沥沥淅淅下着小雨#xff0c;逐渐拉上了幕布。此刻#xff0c;正值魔都的下班高峰#xff0c;从地铁站出来的女孩子纷纷躲到一边#xff0c;手指飞快的划过手机屏幕#xff0c;似乎在等待男朋友送来… 时光轮回冬去春来转眼时间来到了2023年4月。天空沥沥淅淅下着小雨逐渐拉上了幕布。此刻正值魔都的下班高峰从地铁站出来的女孩子纷纷躲到一边手指飞快的划过手机屏幕似乎在等待男朋友送来的雨伞亦或者是等待滴滴专车的到来其中也不乏一些勇敢的女孩子带上帽子加快步伐快速的消失在熙熙攘攘的人群中。此时一名不起眼的少年从人群中快速的走了出来似乎没有注意到天空中的雨滴迈着有力的步伐直径冲向一家小饭馆饭馆老板熟悉的上了他常吃的套餐炒米粉加素鸡和卤蛋一顿狼吞虎咽之后又快速起身转身向出租屋的方向走去穿过一段昏暗的小路他抬起头仰望天空清凉的雨滴打在脸上使他疲惫的身心得到了一丝的清爽不由得又加快了步伐随后彻底消失在茫茫夜色中。因为他今天要创作博客案例04某小说网多线程小说下载
本案例仅供学习交流使用请勿商用。如涉及版本侵权请联系我删除。
目录
一、多线程爬虫
1. 多线程爬虫
2. 队列多线程爬虫的思路
3. Queue的使用方法
4. 生产者和消费者模型
5. 生产者消费者模型代码示例
二、某小说网多线程爬虫
1. 需求分析
2. 生产者类
3. 消费者类
4. 主函数
三、总结分析 一、多线程爬虫
1. 多线程爬虫 接上一篇文章案例03某图网图片多线程下载不懂得先去看进程和线程。我们都知道线程是抢占式资源那必然就会存在下面几个问题
线程间竞争和协作问题多个线程同时访问共享资源时容易出现竞争和协作问题。例如当多个线程同时对同一个变量进行修改时可能会导致数据的不一致性有的线程抢的多有的线程抢的少不能大幅度的提高爬虫的效率的问题在进行I/O操作时致使数据缺失为了解决这些问题我们引入队列多线程爬虫。
2. 队列多线程爬虫的思路 队列queue是计算机科学中常用的数据结构之一它是一种先进先出FIFO的数据结构类似于现实生活中的排队等待服务的过程。我们使用队列进行多线程爬虫基本思路为创建两个队列一个用来放url一个用来放从url获取的结果数据最后对结果数据进行清洗得到我们想要的数据下面介绍一下使用队列爬虫的创建过程和注意要点 创建任务队列首先需要创建一个任务队列用来存储所有需要爬取的URL地址。可以使用Python内置的Queue队列也可以使用第三方库如Redis等来实现。 创建线程池创建一个包含多个线程的线程池每个线程都会从任务队列中获取一个待爬取的URL地址进行处理。 编写爬取逻辑在每个线程中编写一个爬取逻辑用于从队列中获取一个URL地址并发起请求获取网页内容并解析数据。具体的爬取逻辑可以根据实际需求进行编写例如使用requests库进行网页请求使用BeautifulSoup等库进行网页内容解析。 启动线程池启动线程池让所有线程开始处理任务队列中的URL地址。每个线程都会循环执行自己的爬取逻辑直到任务队列为空为止。 添加异常处理在爬取过程中可能会出现各种异常情况例如网络请求超时、网页内容解析错误等。为了保证程序的稳定性需要添加相应的异常处理逻辑例如重试机制、错误日志记录等。 控制并发度为了防止对网站造成过大的负载压力需要控制爬取并发度。可以通过控制线程池中的线程数、限制每个线程的请求频率等方式来实现。 数据存储爬取到的数据需要进行存储可以使用文件、数据库等方式进行存储。同时还需要考虑数据去重、数据更新等问题。 3. Queue的使用方法
方法描述queue.Queue([maxsize])创建一个FIFO队列对象。maxsize为队列的最大长度如果未指定或为负数则队列长度无限制。q.put(item[, block[, timeout]])将元素item放入队列中。如果队列已满且block为True则此方法将阻塞直到有空间可用或者超时timeout。如果block为False则此方法将引发一个Full异常。q.get([block[, timeout]])从队列中取出一个元素并将其返回。如果队列为空且block为True则此方法将阻塞直到有元素可用或者超时timeout。如果block为False则此方法将引发一个Empty异常。q.empty()检查队列是否为空。如果队列为空则返回True否则返回False。q.qsize()返回队列中元素的数量。这种方法不可靠因为在多线程情况下队列长度可能会发生变化。如果要获取准确的队列长度建议使用锁来保护队列。q.queue.clear()清空队列中的所有元素。这种方法不是线程安全的因此在多线程情况下需要使用锁来保护队列。
4. 生产者和消费者模型 生产者-消费者模型是一种并发编程模型常用于处理大量数据的情况。在爬虫中生产者-消费者模型被广泛应用于提高爬取效率和降低爬虫对目标网站的压力。 生产者-消费者模型通常由两部分组成生产者和消费者。
生产者负责从目标网站爬取数据并将这些数据存储到一个共享的数据结构中如队列或缓冲区。消费者从共享的数据结构中读取数据并进行处理例如解析数据、存储数据或者再次发送请求。生产者-消费者模型的优势在于能够有效地处理大量数据同时减少对目标网站的压力。通过将数据的爬取和处理分离开来可以减少因为网络延迟或者其他原因导致的爬取效率低下的问题。此外由于消费者和生产者可以独立运行因此可以使用多线程或者多进程来实现并发处理提高爬虫效率。需要注意的是生产者-消费者模型也可能带来一些问题例如线程同步和共享数据结构的访问冲突等。为了避免这些问题需要在实现过程中仔细考虑线程安全和并发访问控制等问题以确保程序的正确性和性能。简单画了一下生产者-消费者模型的流程图 基本的思维逻辑为
创建一个共享的任务队列。创建若干个生产者线程每个生产者线程负责生成任务并将其放入任务队列中。创建若干个消费者线程每个消费者线程从任务队列中取出任务并进行处理。生产者线程和消费者线程之间通过共享的任务队列进行通信。如果队列中没有任务了消费者线程需要等待新的任务被生产者线程添加到队列中。如果队列已满生产者线程需要等待队列中的任务被消费者线程取出以便继续向队列中添加新的任务。
5. 生产者消费者模型代码示例
import threading
import queue
import requests
from bs4 import BeautifulSoup# 生产者线程
class ProducerThread(threading.Thread):def __init__(self, url_queue):super().__init__()self.url_queue url_queuedef run(self):# 从网站中爬取所有的文章链接resp requests.get(http://example.com/articles)soup BeautifulSoup(resp.text, html.parser)for a in soup.find_all(a):link a.get(href)if link.startswith(/articles/):# 将链接添加到队列中self.url_queue.put(http://example.com link)# 消费者线程
class ConsumerThread(threading.Thread):def __init__(self, url_queue, data_queue):super().__init__()self.url_queue url_queueself.data_queue data_queuedef run(self):while True:try:# 从队列中取出一个链接url self.url_queue.get(timeout10)# 爬取链接对应的文章内容resp requests.get(url)soup BeautifulSoup(resp.text, html.parser)title soup.find(h1).textcontent soup.find(div, {class: content}).text# 将文章内容添加到数据队列中self.data_queue.put((title, content))except queue.Empty:break# 主函数
def main():# 创建队列url_queue queue.Queue()data_queue queue.Queue()# 创建生产者线程和消费者线程producer_thread ProducerThread(url_queue)consumer_thread1 ConsumerThread(url_queue, data_queue)consumer_thread2 ConsumerThread(url_queue, data_queue)# 启动线程producer_thread.start()consumer_thread1.start()consumer_thread2.start()# 等待线程结束producer_thread.join()consumer_thread1.join()consumer_thread2.join()# 处理爬取到的数据while not data_queue.empty():title, content data_queue.get()# 处理文章内容print(title, content)if __name__ __main__:main()使用生产者-消费者模型在爬虫中可以将数据的爬取和处理分离开来多线程的并发处理可以大大提高爬虫的效率减少爬虫对目标网站的访问压力从而降低被封 IP 的风险。但是还需要考虑线程同步、共享数据结构的访问冲突等问题。
二、某小说网多线程爬虫
1. 需求分析
我的书架需要登录生产者函数获取一本书的每章节url储存在队列中消费者函数解析url页面获取数据存储在队列中获取消费者队列中的数据写入本地2. 生产者类 生产者类实现session登录解析页面将url存储到队列中。
import os
import time
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import threading
import queue
import warningswarnings.filterwarnings(ignore)# 生产者类
class ProducerThread(threading.Thread):def __init__(self, url_queue):super().__init__()self.url_queue url_queueself.session requests.Session()self.headers {User-Agent: UserAgent().random}self.login()self.book_dict_list self.get_book_dict_list()def login(self):self.session.post(url, #此处为登录请求的urldata{loginName: 152****7662, password: ******},# 在此处替换自己的账号密码headersself.headers,verifyFalse,)def get_book_dict_list(self):res self.session.get(url #此处为书架的url).json()data res.get(data)return datadef get_chapter_urls(self, book_id):url furl#此处为书的章节列表urlpage_text requests.get(urlurl).content.decode()soup BeautifulSoup(page_text, lxml)dl_li soup.find(div, class_Main List).find_all(dl, class_Volume)urls []for dl in dl_li:chapter_url [url a[href] for a in dl.dd.find_all(a)]#网站urlurls.extend(chapter_url)return urlsdef run(self):for book_dict in self.book_dict_list:book_id book_dict.get(bookId)chapter_urls self.get_chapter_urls(book_id)for url in chapter_urls:self.url_queue.put(url)
3. 消费者类 消费者函数从队列中取出链接解析页面获取数据将数据存储到结果队列中
# 消费者类
class ConsumerThread(threading.Thread):def __init__(self, url_queue, data_queue):super().__init__()self.url_queue url_queueself.data_queue data_queue# self.session sessionself.headers {User-Agent: UserAgent().random}def run(self):while True:try:# 从队列中取出一个链接url self.url_queue.get(timeout10)page_text requests.get(urlurl, headersself.headers).content.decode()soup BeautifulSoup(page_text, lxml)chapter_title soup.find(div, class_readAreaBox content).h1.stringcontent_text soup.find(div, class_readAreaBox content).find(div, class_p).textchapter_text chapter_title \n content_textchapter_text chapter_text.replace(u\xa0, ).replace(u\u36d1, ).replace(u\u200b, )print(f{chapter_title} 存入队列)self.data_queue.put(({chapter_title : chapter_text}))time.sleep(0.1)except queue.Empty:breakexcept Exception as e:print(f{url} 抓取错误..., e)continue
4. 主函数 主函数中开启了200个线程获取解析页面和数据存储
# 主函数
def main():# 创建队列url_queue queue.Queue()data_queue queue.Queue()# 创建生产者线程和消费者线程producer_thread ProducerThread(url_queue)consumer_threads [ConsumerThread(url_queue, data_queue) for _ in range(200)]# 启动线程producer_thread.start()for consumer_thread in consumer_threads:consumer_thread.start()# 等待线程结束producer_thread.join()for consumer_thread in consumer_threads:consumer_thread.join()# 处理爬取到的数据book_path os.path.join(./结果数据/案例04某小说网多线程小说下载)if not os.path.exists(book_path):os.mkdir(book_path)while not data_queue.empty():chapter data_queue.get()for title in chapter:chapter_text chapter[title]chapter_path os.path.join(book_path, f{title}.txt)try:with open(chapter_path, w, encodingutf-8) as f:f.write(chapter_text)except Exception as e:print(f{title} 写入错误..., e)
三、总结分析 本章使用消费者-生产者模型进行了多线程的数据采集提升了爬虫效率使用了队列可以将生产者线程抓取的章节链接存入队列等待消费者线程进行抓取避免了生产者和消费者之间的阻塞和同步问题。 但是在执行多线程的时候线程数量数固定的没有根据具体需求来动态调整可能会导致效率不高或者浪费资源。没有使用线程池来管理线程的生命周期可能会导致线程过多、创建线程时间过长等问题。在下一章中我们通过创建线程池的方式来管理线程继续提高爬虫的效率。附全部代码
import os
import time
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import threading
import queue
import logging
import warningswarnings.filterwarnings(ignore)# 生产者类
class ProducerThread(threading.Thread):def __init__(self, url_queue):super().__init__()self.url_queue url_queueself.session requests.Session()self.headers {User-Agent: UserAgent().random}self.login()self.book_dict_list self.get_book_dict_list()def login(self):self.session.post(url,#此处为登录界面的urldata{loginName: 152****7662, password: ******},#在此处替换自己的账户密码headersself.headers,verifyFalse,)def get_book_dict_list(self):res self.session.get(url#此处为书架的url).json()data res.get(data)return datadef get_chapter_urls(self, book_id):url furl#此处为书本章节的urlpage_text requests.get(urlurl).content.decode()soup BeautifulSoup(page_text, lxml)dl_li soup.find(div, class_Main List).find_all(dl, class_Volume)urls []for dl in dl_li:chapter_url [url a[href] for a in dl.dd.find_all(a)]#此处为网站的url进行拼接urls.extend(chapter_url)return urlsdef run(self):for book_dict in self.book_dict_list:book_id book_dict.get(bookId)chapter_urls self.get_chapter_urls(book_id)for url in chapter_urls:self.url_queue.put(url)
# 消费者类
class ConsumerThread(threading.Thread):def __init__(self, url_queue, data_queue):super().__init__()self.url_queue url_queueself.data_queue data_queue# self.session sessionself.headers {User-Agent: UserAgent().random}def run(self):while True:try:# 从队列中取出一个链接url self.url_queue.get(timeout10)page_text requests.get(urlurl, headersself.headers).content.decode()soup BeautifulSoup(page_text, lxml)chapter_title soup.find(div, class_readAreaBox content).h1.stringcontent_text soup.find(div, class_readAreaBox content).find(div, class_p).textchapter_text chapter_title \n content_textchapter_text chapter_text.replace(u\xa0, ).replace(u\u36d1, ).replace(u\u200b, )print(f{chapter_title} 存入队列)self.data_queue.put(({chapter_title : chapter_text}))time.sleep(0.1)except queue.Empty:breakexcept Exception as e:print(f{url} 抓取错误..., e)continue# 主函数
def main():# 创建队列url_queue queue.Queue()data_queue queue.Queue()# 创建生产者线程和消费者线程producer_thread ProducerThread(url_queue)consumer_threads [ConsumerThread(url_queue, data_queue) for _ in range(200)]# 启动线程producer_thread.start()for consumer_thread in consumer_threads:consumer_thread.start()# 等待线程结束producer_thread.join()for consumer_thread in consumer_threads:consumer_thread.join()# 处理爬取到的数据book_path os.path.join(./结果数据/案例04某小说网多线程小说下载)if not os.path.exists(book_path):os.mkdir(book_path)while not data_queue.empty():chapter data_queue.get()for title in chapter:chapter_text chapter[title]chapter_path os.path.join(book_path, f{title}.txt)try:with open(chapter_path, w, encodingutf-8) as f:f.write(chapter_text)except Exception as e:logging.error(fDownload error: {e}) # 记录下载错误的日志print(f{title} 写入错误..., e)if __name__ __main__:main() 由于版权原因取消了代码中的url源码需要的兄弟们网盘自取提取码sgss