一、前言
在互联网时代,数据是最有价值的资源之一。而网页爬虫是获取数据的一种非常重要的工具。在这篇文章中,我们将学习如何用 Python 编写一个多线程网页爬虫,适合小白快速上手!
二、多线程进程
单线程串行:一步一步做事情
单线程程序一次只能执行一个任务。所有任务按顺序执行,下一个任务必须等待当前任务完成。这种方式实现简单,但效率较低,尤其是在处理需要等待(如 I/O 操作)的时候。
多线程并发:同时做多件事
多线程允许程序同时执行多个任务。对于 I/O 密集型任务(如文件读写、网络请求等),多线程可以大幅提高效率,因为线程在等待 I/O 完成时可以切换到其他任务。
多进程并行:每个任务单独运行在一个进程中
多进程类似于多线程,但每个任务运行在独立的进程中,拥有自己的内存空间。多进程适用于 CPU 密集型任务(如计算密集型操作),因为多个进程可以充分利用多核 CPU 的能力。
三、python多线程的使用
1、python创建多线程的方法
使用 threading.Thread
创建线程,这是最基本的方式,直接创建 Thread
对象并启动线程。
import threading # 导入线程模块,用于创建和管理线程
import time # 导入时间模块,用于添加延迟
# 定义两个线程要执行的函数
def print_hello():
for _ in range(10):
print("hello")
time.sleep(0.2) # 休眠0.2秒,模拟延迟,确保线程交替执行
def print_world():
for _ in range(10):
print("world")
time.sleep(0.1) # 休眠0.1秒,模拟延迟,确保线程交替执行
# 创建线程对象
hello = threading.Thread(target=print_hello)
world = threading.Thread(target=print_world)
# 启动线程
hello.start()
world.start()
# 等待线程执行完毕
hello.join()
world.join()
四、改写爬虫程序,变成多线程爬取
1、写一个爬虫程序,爬取博客园的网页
import threading # 导入线程模块,用于创建和管理线程
import time # 导入时间模块,用于延迟操作
import requests # 导入requests库,用于发送HTTP请求
# 创建一个包含50个URL的列表,URL是通过格式化字符串生成的
# 这些URL指向博客园的不同页面
urls = [f"https://www.cnblogs.com/#p{page}" for page in range(1, 50+1)]
# 定义一个函数,接受一个URL作为参数,爬取该URL的内容
def craw(url):
r = requests.get(url) # 发送GET请求到指定的URL
print(url, len(r.text)) # 打印URL以及响应内容的长度(即页面的字符数)
# 调用craw函数,爬取第一个URL,打印该页面的内容长度
craw(urls[0])
2、接下来我们写我们的线程函数,首先写单线程的函数
def single_thread():# 定义一个函数 single_thread,用于单线程爬取多个 URL
print("single_thread begin")
for url in urls:# 遍历 urls 列表中的每个 URL,调用 craw 函数进行爬取
craw(url)
print("single_thread end")
# 调用 single_thread 函数,开始执行单线程爬取任务
single_thread()
3、继续写我们的多线程函数
def multi_thread():# 定义一个函数 multi_thread,用于通过多线程爬取多个 URL
print("多线程 开始")
threads = []
for url in urls:# 遍历 urls 列表中的每个 URL,创建一个线程并将其添加到 threads 列表
threads.append(threading.Thread(target=craw, args=(url,)))
# 启动所有线程
for thread in threads:
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join()
print("多线程 结束")
# 调用 multi_thread 函数,开始执行多线程爬取任务
multi_thread()
4、速度对比
在单线程与多线程中,爬取相同的内容的时间肯定是不同的,此时我们就可以使用time模块来进行计时,分别统计单线程爬取的时间与多线程爬取的时间进行对比
if __name__ == '__main__': # 判断是否是直接运行当前脚本。如果是,下面的代码将执行。如果是作为模块导入,则不会执行。
start = time.time() # 记录当前时间(单线程开始前的时间),用于计算单线程执行时间
single_thread() # 调用 single_thread 函数,开始执行单线程任务
end = time.time() # 记录当前时间(单线程结束后的时间)
print(f"单线程需要时间{end - start}second") # 打印单线程执行时间,end - start 计算出程序执行所需的时间
start = time.time()
multi_thread()
end = time.time()
print(f"多线程需要时间{end - start}second")
时间对比:
经过对比,我们可以发现,使用多线程的爬虫速度比单线程快了20多倍!当然,具体性能提升的幅度也取决于任务的类型和网络条件,但可以肯定的是,掌握多线程爬虫将让你在爬取大量网页时事半功倍。
多线程不是万能的,它适用于 I/O 密集型任务,对于 CPU 密集型的计算任务,可能多进程更合适。但是在爬虫的场景下,多线程无疑是提高效率的好帮手。
五、代码编写实现生产者消费者爬虫
继续使用上节课的代码来进行,首先改写我们的craw函数,让函数返回获取到的url文本,代码如下:
import threading # 导入线程模块,用于实现并发操作
import time # 导入时间模块,便于控制程序的执行时间
import requests # 导入requests库,用于发送HTTP请求获取网页内容
from bs4 import BeautifulSoup # 导入BeautifulSoup库,用于解析HTML网页
# 生成需要爬取的多个URL地址,创建一个包含50个页面的URL列表
urls = [f"https://www.cnblogs.com/#p{page}" for page in range(1, 50+1)]
# 生产者函数:发送HTTP请求并获取网页的HTML内容
def craw(url):
r = requests.get(url) # 发送GET请求获取网页内容
return r.text # 返回网页的HTML源码
# 消费者函数:解析HTML网页,提取所需的链接信息
def parse(html):
soup = BeautifulSoup(html, "html.parser") # 使用BeautifulSoup解析HTML内容
links = soup.find_all("a", class_="post-item-title") # 查找所有class为'post-item-title'的<a>标签
# 返回包含链接和链接文字的元组列表
return [(link["href"], link.get_text()) for link in links]
# 主程序入口
if __name__ == '__main__':
# 调用craw函数获取网页内容,并将内容传给parse函数进行解析,输出每一个链接及其文字
for result in parse(craw(urls[2])):
print(result)
六、结语
今天我们通过一个简单的例子,学习了如何用 Python 编写一个多线程网页爬虫。希望你能通过这篇文章,快速入门多线程编程,让自己的爬虫项目跑得更快!如果你有任何问题或疑问,欢迎在评论区留言,我们一起讨论,互相进步!