线程
线程的具体实现步骤
导包:
from threading import Thread
NO.1
这里我们定义俩方法
def func():
for i in range(1000):
print("func",i)
def funa(name):
for i in range(1000):
print(name,i)
线程的实现
if __name__ == '__main__':
t1=Thread(target=funa,args=("孙悟空",))
# 传递参数必须是元组所以加逗号
t1.start()
t2=Thread(target=funa,args=("牛牛公主",))
t2.start()
NO.2
定义类
class MyThread(Thread):
def run(self):
for i in range(1000):
print("func", i)
在主函数里面可以任意造线程实现实例
if __name__ == '__main__':
t=MyThread()
t1=MyThread()
t2=MyThread()
t.start()
t1.start()
t2.start()
#.......
线程池:
系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。
具体实现步骤
from concurrent.futures import ThreadPoolExecutor
开辟线程池的方法
with ThreadPoolExecutor(20) as t:
for i in range(100):
t.submit(func,name=f"线程{i}")
这里是开辟了20个线程调用submit来共同执行这个函数
实例:爬新发地菜价信息
如果不用线程池的话代码如下
import time
import requests
from bs4 import BeautifulSoup
import csv
url = "http://www.xinfadi.com.cn/marketanalysis/0/list/"
f = open("菜价.csv", mode="w",encoding="utf-8",newline="")
cswriter = csv.writer(f)
t1=time.time()
for i in range(1,101):
url=url+str(i)+".shtml"
resp = requests.get(url)
# 解析数据
# 1.把页面源代码交给BeautifulSoup进行处理,生成bs对象
page = BeautifulSoup(resp.text, 'html.parser') # 指定html解析器
# 2.从bs对象中查找数据:find(标签,属性=值), find_all(标签,属性=值)
# page.find("table", class_="hq_table") # class是python的关键字,加上_则不为关键字
table = page.find("table", attrs={"class": "hq_table"}) # 与上一行一致
# 拿到所有数据行
trs = table.findAll("tr")[1:]
for tr in trs:
tds = tr.find_all("td") # 拿到每一行中的所有td
name = tds[0].text # 表示拿到被标签标记的内容
low = tds[1].text
avg = tds[2].text
high = tds[3].text
gui = tds[4].text
kind = tds[5].text
date = tds[6].text
cswriter.writerow([name,low,avg,high,gui,kind,date])
print("第"+str(i)+"页下载结束")
t2=time.time()
print("用时"+str(t2-t1)+"秒")
这里是我的结果
20*100=2000
用时如下
可见效率低下
而我们使用线程池之后
#coding:utf-8
import time
import requests
from lxml import etree
import csv
from concurrent.futures import ThreadPoolExecutor
f=open("data.csv",mode="w",encoding="utf-8",newline="")
csvwriter=csv.writer(f)
def download(url):
head={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400"
}
resp=requests.get(url,headers=head)
html=etree.HTML(resp.text)
table=html.xpath("/html/body/div[2]/div[4]/div[1]/table")[0]
trs=table.xpath("./tr")[1:]
for tr in trs:
text=tr.xpath("./td/text()")
# 处理掉//
text=(item.replace("\\","").replace("|","").replace("/","") for item in text)
csvwriter.writerow(text)
print()
if __name__ == '__main__':
# 效率低下
# for i in range(1,16721):
# download(f"http://www.xinfadi.com.cn/marketanalysis/0/list/{i}.shtml")
t1=time.time()
with ThreadPoolExecutor(50) as t:
for i in range(1,101):# 199*20=3980
t.submit(download(f"http://www.xinfadi.com.cn/marketanalysis/0/list/{i}.shtml"))
print(str(i)+"下载完毕")
t2=time.time()
print("用时"+str(t2-t1)+"秒")
用时:
效率还算一般
协程
理解
协程是一种比线程更加轻量级的存在,最重要的是,协程不被操作系统内核管理,协程是完全由程序控制的。
运行效率极高,协程的切换完全由程序控制,不像线程切换需要花费操作系统的开销,线程数量越多,协程的优势就越明显。
协程不需要多线程的锁机制,因为只有一个线程,不存在变量冲突。
对于多核CPU,利用多进程+协程的方式,能充分利用CPU,获得极高的性能。
通过函数调用了解协程的优势
我们给出三个函数(都需要睡眠)
async def func1():
print("aaaaa")
await asyncio.sleep(4)
print("aaaaaover")
async def func2():
print("bbbbb")
await asyncio.sleep(3)
print("bbbbbover")
async def func3():
print("ccccc")
await asyncio.sleep(2)
print("cccccover")
main函数及调用:
async def main():
tasks=[
asyncio.create_task(func1()),
asyncio.create_task(func2()),
asyncio.create_task(func3())
]
await asyncio.wait(tasks)
if __name__ == '__main__':
t1 = time.time()
asyncio.run(main())
t2 = time.time()
print(t2-t1)
由理论指导得出所需时长应该等于最长的休眠时间+两次切换的时间,应该等于4点多秒
若按照单进程的话应该要9秒多
这里体现了多进程的优势
实例:爬取三个图片
#coding:utf-8
import aiohttp
import asyncio
#三个图片网址
urls=[
"http://kr.shanghai-jiuxin.com/file/2021/0702/662c6fbaa13160d20122c30f97de8bd8.jpg",
"http://kr.shanghai-jiuxin.com/file/2021/0702/47337f462fc56624218e69c2e070dd6d.jpg",
"http://kr.shanghai-jiuxin.com/file/2021/0702/be7e8685eb8808c1c0809227760e5016.jpg"
]
async def aiodown(url):
name=url.rsplit("/",1)[1]
# aiohttp.ClientSession()相当于requests模块
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
resp.content.read()#resp.content
with open(name,mode="wb")as f:
f.write(await resp.content.read())
print(name)
async def main():
#任务元组
tasks=[]
for url in urls:
tasks.append(aiodown(url))
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())
运行结果:
爬100章小说
整体思路是用同步代码访问主页面拿到页面json
再通过异步代码下载小说内容
同步代码:
async def getCatalog(url):
resp=requests.get(url)
dic=resp.json()
#任务元组
tasks = []
#获取每章的title和cid
for item in dic['data']['novel']['items']:
title=item['title']
cid=item['cid']
#调用异步代码方法download
tasks.append(download(cid,bid ,title))
await asyncio.wait(tasks)
异步代码:
async def download(cid,bid,title):
#字典元素
data={
"book_id":bid,
"cid": f"{bid}|{cid}",
"need_bookinfo": 1
}
#转换成json对象
data=json.dumps(data)
#子页面链接
url=f"http://dushu.baidu.com/api/pc/getChapterContent?data={data}"
#相当于requests.get()
async with aiohttp.ClientSession()as session:
async with session.get(url)as resp:
dic= await resp.json()
#这里用到了aiofiles模块用于异步操作
async with aiofiles.open(title,mode="w",encoding="utf-8")as f:
await f.write(dic['data']['novel']['content'])
main:
if __name__ == '__main__':
bid="4306063500"
url='http://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"'+bid+'"}'
asyncio.run(getCatalog(url))
爬取西游记100章小说结果: