在上一篇爬虫案例中(python爬取网页图片)我们使用了最基础的文件读写来保存爬取的文件,但是其效率不尽人意,网速慢的时候可能得三四秒才能下载完一张图片,如果获取的图片总量以千以万计量那么这个速度是完全不可行的,因此接下来我们将引入python中的新机制-----多线程
在进行多线程之前,我们先来体验一下单线程
import time
def test():
time.sleep(20)
if __name__ == '__main__':
s = time.time()
test()
e = time.time()
print(e - s)
想都不用想,结果肯定是20啦
接下来正戏开始
首先导入thread包调用Thread模块
from threading import Thread
接着我们将休眠的20秒拆成10个2秒同时执行
按照我们平时的写法可能需要将time.sleep(2)
循环遍历十次
但是利用Thread模块我们可以创建线程让程序同时执行任务
首先遍历十次循环
for i in range(10):
然后依次启动线程,括号内的参数target是你要执行的函数
for i in range(10):
t = Thread(target=test)# 注意test函数不要加括号!
然后启动线程
t.start()
完整代码:
import time
from threading import Thread
def test():
time.sleep(2)
if __name__ == '__main__':
threads = []
s = time.time()
for i in range(10):
t = Thread(target=test)
t.start()
threads.append(t)
# 等待所有线程完成
for t in threads:
t.join()
e = time.time()
print(e - s)
差距一下就显现了
了解完多线程的基本用法,接下来就引入了线程池的概念,线程池会预先创建线程集合然后从中选择一个空闲线程来处理任务,大白话就是你尽管写你的业务代码,线程的创建和销毁会由线程池帮你安排,从而大大提高线程的重用性和执行效率
想要使用线程池得先导入新的模块ThreadPoolExecutor
from concurrent.futures import ThreadPoolExecutor
代码书写的格式也比刚刚的更简单,只需要将你准备执行多线程运算的代码用 with ThreadPoolExecutor(num)as t:
包起来,num是你要开启线程的数量,然后submit中第一个参数是你要执行的函数,后面的参数是你需要传入的元素
with ThreadPoolExecutor(10)as t:
for i in range(10):
t.submit(test, i)
推荐:开启的线程数量 = 自己电脑CPU的核心数
不知道自己CPU多少核的可以直接打开设备管理器查看
接着继续运行我们的代码
import time
from concurrent.futures import ThreadPoolExecutor
def test(i):
time.sleep(2)
print(f"线程{i}睡了两秒")
if __name__ == '__main__':
threads = []
s = time.time()
with ThreadPoolExecutor(10)as t:
for i in range(10):
t.submit(test, i)
e = time.time()
print(e - s)
没有问题,继续下一个任务
这里我们直接把上一篇爬虫爬取网站图片的代码搬过来
import requests
from lxml import etree
url = 'https://loryx.wiki/%E6%B5%8F%E8%A7%88/%E7%89%8C%E5%BA%93'
data = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'
}
res = requests.get(url=url, data=data)
res.encoding = 'utf-8'
et = etree.HTML(res.text)
# print(res.text)
src = et.xpath("//td[@class='col15 leftalign']/a/@href")
name = et.xpath("//td[@class='col0 leftalign']/text()")
for i, index in enumerate(name):
name[i] = index.strip()
for i in range(len(src)):
with open(f"img/{name[i]}.png", 'wb') as f:
f.write(requests.get(src[i]).content)
如果你试着跑一下这个代码的话,你可能出去吃个饭回来发现程序还在运行中
现在开始对该代码进行改写
代码到这里我们已经获取了全部的src列表和name列表准备遍历了
所以先将该部分拆分到函数main内
def main():
res = requests.get(url=url)
res.encoding = 'utf-8'
et = etree.HTML(res.text)
src = et.xpath("//td[@class='col15 leftalign']/a/@href")
name = et.xpath("//td[@class='col0 leftalign']/text()")
for i, index in enumerate(name):
name[i] = index.strip()
for i in range(len(src)):
剩余的写入文件的代码也拆分到函数download内
这里文件名和传入数据我们也直接修改为形参
修改前:
def download():
with open(f"img/{name[i]}.png", 'wb') as f:
f.write(requests.get(src[i]).content)
修改后:
def download(name, src):
with open(name, 'wb') as f:
f.write(requests.get(src).content)
在main函数内导入ThreadPoolExecutor
模块,并将准备遍历的部分进行包裹
with ThreadPoolExecutor(64) as t:
for i in range(len(src)):
接下来还需要两个步骤
with ThreadPoolExecutor(64) as t:
for i in range(len(src)):
file_name = name[i]
t.submit(download, file_name, src[i])
代码到这里基本就完成了,直接启动main()
函数
下载速度飞快,一瞬间60多张图片就下好了
在python爬虫的学习中,即使是多线程也并不是我们最终的手段,在高级点的项目中,我们可能会用到其他的类似于Scrapy的爬虫框架
Scrapy使用Twisted框架作为其底层网络引擎,利用异步IO技术来实现高效的网络请求和数据处理,并且其内置了大量的组件和模块包,还支持数据库处理
这些在日后笔者也会慢慢更新。最后附上今天的完整代码:
from concurrent.futures import ThreadPoolExecutor
import requests
from lxml import etree
url = 'https://loryx.wiki/%E6%B5%8F%E8%A7%88/%E7%89%8C%E5%BA%93'
def download(name, src):
with open(name, 'wb') as f:
f.write(requests.get(src).content)
def main():
res = requests.get(url=url)
res.encoding = 'utf-8'
et = etree.HTML(res.text)
src = et.xpath("//td[@class='col15 leftalign']/a/@href")
name = et.xpath("//td[@class='col0 leftalign']/text()")
for i, index in enumerate(name):
name[i] = index.strip()
with ThreadPoolExecutor(64) as t:
for i in range(len(src)):
file_name = f"img/{name[i]}.png"
t.submit(download, file_name, src[i])