Python----Python爬虫(多线程,多进程,协程爬虫)
注意:
该代码爬取小说不久或许会失效,有时候该网站会被封禁,代码只供参考,不同小说不同网址会有差异
神印王座II皓月当空最新章节_神印王座II皓月当空全文免费阅读-笔趣阁
一、多线程爬虫
1.1、单线程爬虫的问题
爬虫通常被认为是IO密集型的程序,因为它们主要依赖于网络请求来获取数据。然而,IO处理的速度相对较慢,可能导致爬虫的整体速度受限。具体来说,单线程爬虫在处理大量网页时效率较低,主要存在以下几个问题:
速度慢:单线程爬虫只能一个接一个地请求网页,缺乏并发能力。在面对大量网页时,这种逐个请求的方式显著增加了爬取所需的时间。
资源利用率低:在等待网络响应的过程中,CPU处于闲置状态,未能充分利用系统资源。这种低效利用不仅影响了爬虫的速度,也使得系统资源浪费。
易受限于网络延迟:网络延迟是影响爬虫效率的重要因素。单线程爬虫在面对高延迟的网络时,整体爬取时间会显著延长,从而降低用户体验。
1.2、原理
多线程爬虫的核心原理是利用多线程并行处理多个网页请求,从而提高网络请求的并发性,加速数据抓取的过程。在这种架构中,爬虫将URL队列中的链接分配给多个线程进行处理,每个线程负责发送网络请求并获取响应。
具体来说,爬虫的工作流程如下:
URL队列:爬虫首先维护一个包含待爬取URL的队列。每个线程从这个队列中取出一个URL进行处理。
并行请求:多个线程同时运行,独立地发送网络请求。在等待响应的过程中,线程不会闲置,而是可以继续处理队列中的其他URL。这种并行处理显著减少了整体的爬取时间。
结果队列:每个线程在获取到网页响应后,将结果存储到一个结果队列中。这个结构确保了数据的有序存储,并允许其他线程安全地读取这些结果。
数据写入:另一些线程则负责从结果队列中读取数据,并将其写入文件或进行进一步处理。这种分工使得爬虫的各个部分能够高效协作,最大限度地提高资源利用率。
1.3、主要组成部分
1.3.1、URL队列和结果队列
URL队列:存储待爬取的URL链接,通常使用线程安全的队列(如
queue.Queue
),以便多个线程可以安全地访问和修改。结果队列:用于存放从网页中提取的结果,允许在爬取完成后统一处理或存储。
from queue import Queue
urls_queue = Queue()
out_queue = Queue()
1.3.2、类包装
使用多个线程,不停的取URL队列中的url,并进行处理:
import threading
class ThreadCrawl(threading.Thread):
def __init__(self, queue, out_queue):
threading.Thread.__init__(self)
self.queue = queue
self.out_queue = out_queue
def run(self):
while True:
item = self.queue.get()
如果队列为空,线程就会被阻塞,直到队列不为空。
处理队列中的 一条数据后,就需要通知队列已经处理完该条数据
1.3.3、函数包装
from threading import Thread
def func(args):
pass
if __name__ == '__main__':
info_html = Queue()
t1 = Thread(target=func,args=(info_html,))
1.3.4、线程池
import threading # 导入 threading 模块,用于创建和管理线程
import time # 导入 time 模块,使用 sleep 来模拟工作
import queue # 导入 queue 模块,使用线程安全的队列
class Threadingpool():
def __init__(self, max_num=10):
# 初始化线程池,最大线程数量为 max_num
self.queue = queue.Queue(max_num) # 创建一个最大大小为 max_num 的队列
for i in range(max_num):
# 将线程类的引用放入队列中
self.queue.put(threading.Thread)
def getthreading(self):
# 从队列中获取一个线程
return self.queue.get()
def addthreading(self):
# 将新的线程类引用放回队列
self.queue.put(threading.Thread)
def func(p, i):
# 每个线程执行的函数
time.sleep(1) # 通过休眠1秒来模拟工作
print(i) # 打印传递给函数的索引
p.addthreading() # 线程完成后,将线程返回到池中
if __name__ == "__main__":
p = Threadingpool() # 创建一个 Threadingpool 实例
for i in range(20):
thread = p.getthreading() # 从池中获取一个线程
t = thread(target=func, args=(p, i)) # 创建一个新线程,目标函数和参数
t.start() # 启动线程
1.4、多线程函数爬虫
import re # 导入正则表达式模块
import requests # 导入请求库,用于发送HTTP请求
from fake_useragent import UserAgent # 导入假用户代理库,用于伪装请求的用户代理
from lxml import etree # 导入lxml库,用于解析HTML
from threading import Thread # 导入线程模块,用于实现多线程
import time # 导入时间模块,用于控制延时
def sanitize_filename(title):
# 替换 Windows 文件名中的无效字符
return re.sub(r'[<>:"/\\|?*]', '', title)
def get_new_url(url):
# 设置请求头,伪装成浏览器
header = {'User-Agent': UserAgent().edge}
# 发送GET请求获取网页内容
resp = requests.get(url, header)
resp.encoding = 'gbk' # 设置网页编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
titles = [] # 存储章节标题
new_urls = [] # 存储章节链接
# 遍历指定范围内的章节
for i in range(13, 516):
if i == 368: # 跳过特定章节
continue
# 获取章节标题并打印
print(e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/text()")[0])
titles.append(e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/text()")[0]) # 添加标题到列表
# 构造章节链接并添加到列表
new_urls.append('https://www.bbiquge.cc/book_61985/' + e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/@href")[0])
return titles, new_urls # 返回标题和链接列表
def spider(title, new_url):
# 设置请求头,伪装成浏览器
header = {'User-Agent': UserAgent().edge}
# 发送GET请求获取章节内容
resp = requests.get(new_url, header)
resp.encoding = 'gbk' # 设置编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
safe_title = sanitize_filename(title) # 清理标题以作为文件名
print(title) # 打印当前正在抓取的章节标题
content = e.xpath("//div[@id='content']/text()") # 获取章节内容
# 打开文件以写入章节内容
with open(f'./神印王座/{safe_title}.txt', 'a+', encoding='utf-8') as f:
for i in content:
f.write(i.strip() + "\n") # 写入内容并清理空白
time.sleep(2) # 每次抓取后暂停2秒,防止过于频繁的请求
if __name__ == '__main__':
url = 'https://www.bbiquge.cc/book_61985/' # 目标网址
titles, new_urls = get_new_url(url) # 获取章节标题和链接
threads = [] # 存储线程列表
# 为每个章节创建一个线程进行抓取
for title, new_url in zip(titles, new_urls):
thread = Thread(target=spider, args=(title, new_url)) # 创建线程
threads.append(thread) # 添加线程到列表
thread.start() # 启动线程
# 等待所有线程完成
for thread in threads:
thread.join() # 等待每个线程结束
1.5、多线程类爬虫
import re # 导入正则表达式模块
import requests # 导入请求库,用于发送HTTP请求
from fake_useragent import UserAgent # 导入假用户代理库,用于伪装请求的用户代理
from lxml import etree # 导入lxml库,用于解析HTML
from threading import Thread # 导入线程模块
import time # 导入时间模块,用于控制延时
class NovelSpider(Thread):
def __init__(self, title, new_url):
super().__init__() # 调用父类的构造函数
self.title = title # 章节标题
self.new_url = new_url # 章节链接
self.headers = {'User-Agent': UserAgent().edge} # 请求头
@staticmethod
def sanitize_filename(title):
# 替换 Windows 文件名中的无效字符
return re.sub(r'[<>:"/\\|?*]', '', title)
def run(self):
# 发送GET请求获取章节内容
resp = requests.get(self.new_url, headers=self.headers)
resp.encoding = 'gbk' # 设置编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
safe_title = self.sanitize_filename(self.title) # 清理标题以作为文件名
print(f"抓取中: {safe_title}") # 打印当前正在抓取的章节标题
content = e.xpath("//div[@id='content']/text()") # 获取章节内容
# 打开文件以写入章节内容
with open(f'./神印王座/{safe_title}.txt', 'a+', encoding='utf-8') as f:
for i in content:
f.write(i.strip() + "\n") # 写入内容并清理空白
time.sleep(2) # 每次抓取后暂停2秒,防止过于频繁的请求
class NovelCrawler:
def __init__(self, base_url):
self.base_url = base_url # 基础网址
self.headers = {'User-Agent': UserAgent().edge} # 请求头
self.titles = [] # 存储章节标题
self.new_urls = [] # 存储章节链接
def get_new_url(self):
# 发送GET请求获取网页内容
resp = requests.get(self.base_url, headers=self.headers)
resp.encoding = 'gbk' # 设置网页编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
# 遍历指定范围内的章节
for i in range(13, 516):
if i == 368: # 跳过特定章节
continue
title = e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/text()")[0] # 获取章节标题
print(title) # 打印章节标题
self.titles.append(title) # 添加标题到列表
new_url = 'https://www.bbiquge.cc/book_61985/' + e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/@href")[0] # 构造章节链接
self.new_urls.append(new_url) # 添加链接到列表
def run(self):
self.get_new_url() # 获取章节标题和链接
threads = [] # 存储线程列表
# 为每个章节创建一个线程进行抓取
for title, new_url in zip(self.titles, self.new_urls):
spider = NovelSpider(title, new_url) # 创建爬虫实例
threads.append(spider) # 添加线程到列表
spider.start() # 启动线程
# 等待所有线程完成
for thread in threads:
thread.join() # 等待每个线程结束
if __name__ == '__main__':
url = 'https://www.bbiquge.cc/book_61985/' # 目标网址
crawler = NovelCrawler(url) # 创建爬虫控制器实例
crawler.run() # 运行爬虫
1.6、 多线程--线程池
import re # 导入正则表达式模块
import requests # 导入请求库,用于发送HTTP请求
from fake_useragent import UserAgent # 导入假用户代理库,用于伪装请求的用户代理
from lxml import etree # 导入lxml库,用于解析HTML
from concurrent.futures import ThreadPoolExecutor # 导入线程池执行器
import time # 导入时间模块,用于控制延时
def sanitize_filename(title):
# 替换 Windows 文件名中的无效字符
return re.sub(r'[<>:"/\\|?*]', '', title)
def get_new_url(url):
# 设置请求头,伪装成浏览器
header = {'User-Agent': UserAgent().edge}
# 发送GET请求获取网页内容
resp = requests.get(url, headers=header)
resp.encoding = 'gbk' # 设置网页编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
titles = [] # 存储章节标题
new_urls = [] # 存储章节链接
# 遍历指定范围内的章节
for i in range(13, 516):
if i == 368: # 跳过特定章节
continue
# 获取章节标题并打印
title = e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/text()")[0]
print(title) # 打印章节标题
titles.append(title) # 添加标题到列表
# 构造章节链接并添加到列表
new_urls.append('https://www.bbiquge.cc/book_61985/' + e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/@href")[0])
return titles, new_urls # 返回标题和链接列表
def spider(title, new_url):
# 设置请求头,伪装成浏览器
header = {'User-Agent': UserAgent().edge}
# 发送GET请求获取章节内容
resp = requests.get(new_url, headers=header)
resp.encoding = 'gbk' # 设置编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
safe_title = sanitize_filename(title) # 清理标题以作为文件名
print(f"抓取中: {safe_title}") # 打印当前正在抓取的章节标题
content = e.xpath("//div[@id='content']/text()") # 获取章节内容
# 打开文件以写入章节内容
with open(f'./神印王座/{safe_title}.txt', 'a+', encoding='utf-8') as f:
for i in content:
f.write(i.strip() + "\n") # 写入内容并清理空白
time.sleep(2) # 每次抓取后暂停2秒,防止过于频繁的请求
if __name__ == '__main__':
url = 'https://www.bbiquge.cc/book_61985/' # 目标网址
titles, new_urls = get_new_url(url) # 获取章节标题和链接
# 使用线程池进行抓取
with ThreadPoolExecutor(max_workers=10) as executor: # 设置最大工作线程数
# 为每个章节提交任务
for title, new_url in zip(titles, new_urls):
executor.submit(spider, title, new_url) # 提交任务到线程池
二、多进程爬虫
multiprocessing是python的多进程管理包,和threading.Thread 类似 multiprocessing模块
multiprocessing模块可以让程序员在给定的机器上充分的利用CPU 在multiprocessing中,通过创建Process对象生成进程,然后调用 它的start()方法
2.1、多进程函数爬虫
import re # 导入正则表达式模块
import requests # 导入请求库,用于发送HTTP请求
from fake_useragent import UserAgent # 导入假用户代理库,用于伪装请求的用户代理
from lxml import etree # 导入lxml库,用于解析HTML
from multiprocessing import Process, Manager # 导入进程和管理器模块
import time # 导入时间模块,用于控制延时
def sanitize_filename(title):
# 替换 Windows 文件名中的无效字符
return re.sub(r'[<>:"/\\|?*]', '', title)
def get_new_url(url):
# 设置请求头,伪装成浏览器
header = {'User-Agent': UserAgent().edge}
# 发送GET请求获取网页内容
resp = requests.get(url, headers=header)
resp.encoding = 'gbk' # 设置网页编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
titles = [] # 存储章节标题
new_urls = [] # 存储章节链接
# 遍历指定范围内的章节
for i in range(13, 516):
if i == 368: # 跳过特定章节
continue
# 获取章节标题并打印
title = e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/text()")[0]
print(title) # 打印章节标题
titles.append(title) # 添加标题到列表
# 构造章节链接并添加到列表
new_urls.append('https://www.bbiquge.cc/book_61985/' + e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/@href")[0])
return titles, new_urls # 返回标题和链接列表
def spider(title, new_url):
# 设置请求头,伪装成浏览器
header = {'User-Agent': UserAgent().edge}
# 发送GET请求获取章节内容
resp = requests.get(new_url, headers=header)
resp.encoding = 'gbk' # 设置编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
safe_title = sanitize_filename(title) # 清理标题以作为文件名
print(f"抓取中: {safe_title}") # 打印当前正在抓取的章节标题
content = e.xpath("//div[@id='content']/text()") # 获取章节内容
# 打开文件以写入章节内容
with open(f'./神印王座/{safe_title}.txt', 'a+', encoding='utf-8') as f:
for i in content:
f.write(i.strip() + "\n") # 写入内容并清理空白
time.sleep(2) # 每次抓取后暂停2秒,防止过于频繁的请求
if __name__ == '__main__':
url = 'https://www.bbiquge.cc/book_61985/' # 目标网址
titles, new_urls = get_new_url(url) # 获取章节标题和链接
processes = [] # 存储进程列表
# 为每个章节创建一个进程进行抓取
for title, new_url in zip(titles, new_urls):
process = Process(target=spider, args=(title, new_url)) # 创建进程
processes.append(process) # 添加进程到列表
process.start() # 启动进程
# 等待所有进程完成
for process in processes:
process.join() # 等待每个进程结束
2.2、多进程类爬虫
import re # 导入正则表达式模块
import requests # 导入请求库,用于发送HTTP请求
from fake_useragent import UserAgent # 导入假用户代理库,用于伪装请求的用户代理
from lxml import etree # 导入lxml库,用于解析HTML
from multiprocessing import Process # 导入进程模块
import time # 导入时间模块,用于控制延时
class NovelSpider(Process): # 继承自 Process
def __init__(self, title, new_url):
super().__init__() # 调用父类的构造函数
self.title = title # 章节标题
self.new_url = new_url # 章节链接
self.headers = {'User-Agent': UserAgent().edge} # 请求头
@staticmethod
def sanitize_filename(title):
# 替换 Windows 文件名中的无效字符
return re.sub(r'[<>:"/\\|?*]', '', title)
def run(self):
# 发送GET请求获取章节内容
resp = requests.get(self.new_url, headers=self.headers)
resp.encoding = 'gbk' # 设置编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
safe_title = self.sanitize_filename(self.title) # 清理标题以作为文件名
print(f"抓取中: {safe_title}") # 打印当前正在抓取的章节标题
content = e.xpath("//div[@id='content']/text()") # 获取章节内容
# 打开文件以写入章节内容
with open(f'./神印王座/{safe_title}.txt', 'a+', encoding='utf-8') as f:
for i in content:
f.write(i.strip() + "\n") # 写入内容并清理空白
time.sleep(2) # 每次抓取后暂停2秒,防止过于频繁的请求
class NovelCrawler:
def __init__(self, base_url):
self.base_url = base_url # 基础网址
self.headers = {'User-Agent': UserAgent().edge} # 请求头
self.titles = [] # 存储章节标题
self.new_urls = [] # 存储章节链接
def get_new_url(self):
# 发送GET请求获取网页内容
resp = requests.get(self.base_url, headers=self.headers)
resp.encoding = 'gbk' # 设置网页编码为'gbk'
e = etree.HTML(resp.text) # 解析网页内容为HTML树结构
# 遍历指定范围内的章节
for i in range(13, 516):
if i == 368: # 跳过特定章节
continue
title = e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/text()")[0] # 获取章节标题
print(title) # 打印章节标题
self.titles.append(title) # 添加标题到列表
new_url = 'https://www.bbiquge.cc/book_61985/' + e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/@href")[0] # 构造章节链接
self.new_urls.append(new_url) # 添加链接到列表
def run(self):
self.get_new_url() # 获取章节标题和链接
processes = [] # 存储进程列表
# 为每个章节创建一个进程进行抓取
for title, new_url in zip(self.titles, self.new_urls):
spider = NovelSpider(title, new_url) # 创建爬虫实例
processes.append(spider) # 添加进程到列表
spider.start() # 启动进程
# 等待所有进程完成
for process in processes:
process.join() # 等待每个进程结束
if __name__ == '__main__':
url = 'https://www.bbiquge.cc/book_61985/' # 目标网址
crawler = NovelCrawler(url) # 创建爬虫控制器实例
crawler.run() # 运行爬虫
三、协程爬虫
网络爬虫速度效率慢,多部分在于阻塞IO这块(网络/磁盘)。在阻塞 时,CPU的中内核是可以处理别的非IO操作。因此可以考虑使用协 程来提升爬虫效率,这种操作的技术就是协程
协程一种轻量级线程,拥有自己的寄存器上下文和栈,本质是一个进程
相对于多进程,无需线程上下文切换的开销,无需原子操作锁定及同步的开销
简单的说就是让阻塞的子程序让出CPU给可以执行的子程序
一个进程包含多个线程,一个线程可以包含多个协程
多个线程相对独立,线程的切换受系统控制。 多个协程也相对独立,但是其切换由程序自己控制
pip install aiohttp
属性或方法 | 功能 |
---|---|
aiohttp.ClientSession() | 获取客户端函数 |
session.get(url) | 发送get请求 |
seesion.post(url) | 发送post请求 |
resp.status | 获取响应状态码 |
resp.url | 获取响应url地址 |
resp.cookies | 获取响应cookie内容 |
resp.headers | 获取响应头信息 |
resp.read() | 获取响应bytes类型 |
resp.text() | 获取响应文本内容 |
import re # 导入正则表达式模块
import aiohttp # 导入异步HTTP请求库
import asyncio # 导入异步库
from fake_useragent import UserAgent # 导入假用户代理库,用于伪装请求的用户代理
from lxml import etree # 导入lxml库,用于解析HTML
async def sanitize_filename(title):
# 替换 Windows 文件名中的无效字符
return re.sub(r'[<>:"/\\|?*]', '', title)
async def fetch(session, url):
# 异步获取网页内容
async with session.get(url) as response:
return await response.text()
async def get_new_url(url):
header = {'User-Agent': UserAgent().edge}
async with aiohttp.ClientSession() as session: # 创建异步会话
resp_text = await fetch(session, url) # 获取网页内容
resp_text.encoding = 'gbk' # 设置网页编码为'gbk'
e = etree.HTML(resp_text) # 解析网页内容为HTML树结构
titles = [] # 存储章节标题
new_urls = [] # 存储章节链接
# 遍历指定范围内的章节
for i in range(13, 516):
if i == 368: # 跳过特定章节
continue
title = e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/text()")[0] # 获取章节标题
print(title) # 打印章节标题
titles.append(title) # 添加标题到列表
# 构造章节链接并添加到列表
new_urls.append('https://www.bbiquge.cc/book_61985/' + e.xpath(f"//div[@id='list']/dl/dd[{i}]/a/@href")[0])
return titles, new_urls # 返回标题和链接列表
async def spider(title, new_url):
header = {'User-Agent': UserAgent().edge}
async with aiohttp.ClientSession() as session: # 创建异步会话
resp_text = await fetch(session, new_url) # 异步获取章节内容
resp_text.encoding = 'gbk' # 设置编码为'gbk'
e = etree.HTML(resp_text) # 解析网页内容为HTML树结构
safe_title = await sanitize_filename(title) # 清理标题以作为文件名
print(f"抓取中: {safe_title}") # 打印当前正在抓取的章节标题
content = e.xpath("//div[@id='content']/text()") # 获取章节内容
# 打开文件以写入章节内容
with open(f'./神印王座/{safe_title}.txt', 'a+', encoding='utf-8') as f:
for line in content:
f.write(line.strip() + "\n") # 写入内容并清理空白
async def main():
url = 'https://www.bbiquge.cc/book_61985/' # 目标网址
titles, new_urls = await get_new_url(url) # 获取章节标题和链接
# 创建任务列表并并发执行
tasks = [spider(title, new_url) for title, new_url in zip(titles, new_urls)]
await asyncio.gather(*tasks) # 使用 gather() 并发
if __name__ == '__main__':
asyncio.run(main()) # 运行主协程函数