1. 多线程 (单线程、多线程)
# 线程、 进程
# 线程是执行单位 进程 -> 公司 线程 -> 员工
# 进程是资源单位 (每一个进程里面至少有一个线程)
# 单线程
def func():
for i in range(1000):
print('func', i)
# 启动一个进程会默认有一个主线程
if __name__ == '__main__':
func()
for i in range(1000):
print('main', i)
# 多线程 写法一
from threading import Thread
def func():
for i in range(1000):
print('func', i)
# 启动一个进程会默认有一个主线程
if __name__ == '__main__':
t = Thread(target=func) # 给线程安排任务
t.start() # 告诉线程可以开始工作了,具体执行时间是由CPU决定的
for i in range(1000):
print('main', i)
from threading import Thread
# 多线程 写法二
class MyThread(Thread):
def run(self): # 固定的 当线程执行时,被执行的就是 run()
for i in range(1000):
print('子线程', i)
if __name__ == '__main__':
t = MyThread()
# t.run() # 方法的调用 -> 单线程
t.start() # 开启线程
for i in range(1000):
print('主线程', i)
# 多线程 传递参数
from threading import Thread
def func(name):
for i in range(1000):
print(name, i)
if __name__ == '__main__':
# args 是元组 必须有 ,
t1 = Thread(target=func, args=('周杰伦',))
t1.start() # 开启线程
t2 = Thread(target=func, args=('王力宏',))
t2.start() # 开启线程
2. 进程
# 多进程
from multiprocessing import Process
def func():
for i in range(1000):
print('子进程', i)
if __name__ == '__main__':
p = Process(target=func)
p.start()
for i in range(1000):
print('主进程', i)
3. 线程池 和 进程池
# 线程池: 一次性开辟开辟一些线程, 用户直接给线程池提交任务 线程任务的调度交给线程池来完成
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def fn(name):
for i in range(100):
print(name, i)
if __name__ == '__main__':
# 创建多个 进程池 只需要将 ThreadPoolExecutor(50) 换成 ProcessPoolExecutor(50) 即可
# 创建线程池
with ThreadPoolExecutor(50) as t:
for i in range(100):
t.submit(fn, name=f'线程{i}')
# 等待线程池中任务全部执行完毕,才会继续执行(守护)
print('线程池执行完毕!!!')
4. 线程池 和 进程池实战
# 1. 如何提取单个页面的数据
# 2. 使用线程池,多个页面同时抓取
import requests
from lxml import etree
import csv
from concurrent.futures import ThreadPoolExecutor
f = open('data.csv', mode='w', encoding='utf-8')
csvwrite = csv.writer(f)
def down_one_page(url):
# 获取页面源代码
resp = requests.get(url)
resp.encoding = 'utf-8'
# print(resp.text)
html = etree.HTML(resp.text)
# 这里 table 是列表(我们需要列表里第一个数据所以需要加 [0])
table = html.xpath('/html/body/div[1]/div[2]/div[1]/div/div[2]/table[1]')[0]
trs = table.xpath('./tbody/tr')[1:] # 方法一
# trs = table.xpath('./tbody/tr[position()>1]') # 方法二
# print(len(trs)) # 数据的长度
# 拿数据
for tr in trs:
txt = tr.xpath('./td/p//text()')
# 对数据做一些简单的处理(数据有多的符号才处理 否则不处理哈)
# txt = (item.replace('//', '').replace('\\', '') for item in txt)
# 把数据存放在文件中
csvwrite.writerow(txt)
# print(txt)
print(url, '提取完毕')
if __name__ == '__main__':
# for i in range(1, 200): # 单线程 效率及其底下 将 200 修改为 14369页 数据呢???
# down_one_page(f'http://zhongdapeng.com/shucaijiage/{i}.html')
# 创建线程池
with ThreadPoolExecutor(50) as t:
for i in range(1, 200):
# 把下载任务提交给线程池
t.submit(down_one_page, f'http://zhongdapeng.com/shucaijiage/1141.html')
print('全部下载完成!!!')
5. 协程的概念
import time
def func():
print('我爱黎明')
time.sleep(3) # 程序处于堵塞状态,CPU不为我工作
print('我真的爱黎明')
if __name__ == '__main__':
func()
# input() # 程序处于堵塞状态
# requests.get(bilibile) 在网络请求返回数据之前,程序也是处于堵塞状态的
# 一般情况下, 当程序处于 IO操作时,线程都会处于阻塞状态
# IO操作: 输入或输出的时候
# 协程: 当程序遇到 IO 操作的时候,可以选择性的切换到其它任务上
# 在微观上是一个任务一个任务的进行切换,切换条件一般就是IO操作
# 在宏观上,我们能看到的就是多个任务一起执行
# 上方所讲的一切,都是在单线程的情况下
6. 协程程序
# Python 编写协程程序
import asyncio
import time
# 多任务异步
async def func1():
print('你好啊,你叫小张!')
# time.sleep(3) # 当程序出现同步操作的时候,异步就中断了
await asyncio.sleep(3) # 异步操作的代码
print('你好啊,你叫小张!')
async def func2():
print('你好啊,你叫小明!')
# time.sleep(2)
await asyncio.sleep(2) # 异步操作的代码
print('你好啊,你叫小明!')
async def func3():
print('你好啊,你叫小红!')
# time.sleep(4)
await asyncio.sleep(4) # 异步操作的代码
print('你好啊,你叫小红!')
async def main():
# 写法一
# f1 = func1()
# await f1 # await 放在协程对象的前面(也必须放在async函数的里面)
# 写法二(推荐)
task = [
# 使用asyncio.create_task()创建task对象
asyncio.create_task(func1()),
asyncio.create_task(func2()),
asyncio.create_task(func3())
]
await asyncio.wait(task)
if __name__ == '__main__':
t1 = time.time()
# 一次性启动多个任务(协程)
asyncio.run(main())
t2 = time.time()
print(t2 - t1, '时间差') # 执行任务花了多少时间
7. 协程爬虫领域的应用模板
import asyncio
import time
# 在爬虫领域的应用
async def download(url):
print("准备开始下载")
await asyncio.sleep(2) # 网络请求 requests.get()
print("下载完成")
async def main():
urls = [
"http://www.baidu.com",
"http://www.bilibili.com",
"http://www.163.com"
]
# 准备异步协程对象列表
tasks = []
for url in urls:
d = asyncio.create_task(download(url))
tasks.append(d)
# tasks = [asyncio.create_task(download(url)) for url in urls] # 这么干也行哦~
# 一次性把所有任务都执行
await asyncio.wait(tasks)
if __name__ == '__main__':
t1 = time.time()
asyncio.run(main())
t2 = time.time()
print(t2 - t1, '时间差') # 执行任务花了多少时间
8. aiohttp模块的使用
# requests.get() 同步的操作 -> 异步操作(aiohttp模块)
# pip install aiohttp
import asyncio
import aiohttp
import requests
urls = [
"https://www.toopic.cn/public/uploads/small/1658043292312165804329268.png",
"https://www.toopic.cn/public/uploads/image/20200411/20200411153721_42156.jpg",
"https://www.toopic.cn/public/uploads/small/1638860271747163886027116.jpg"
]
async def aiodownload(url):
# 起名字
name = url.rsplit('/', 1)[1]
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
# 发送请求
with open(name, mode='wb') as f:
f.write(await resp.content.read()) # 读取任务是异步的,需要用 await 挂起
print(name, '搞定')
# 保存图片内容
# 保存到本地
async def main():
tasks = []
for url in urls:
# 创建一个 协程对象
d = asyncio.create_task(aiodownload(url))
tasks.append(d)
# 添加到 异步任务中
await asyncio.wait(tasks)
if __name__ == '__main__':
# 解决报错
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(main())
# 所有章节的内容 (名称、cid)
# https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"4306063500"}
# 章节的具体内容
# https://dushu.baidu.com/api/pc/getChapterContent?data={"book_id":"4306063500","cid":"4306063500|1569782244","need_bookinfo":1}
# 异步文件操作
# pip install aiofiles
import requests
import asyncio
import aiohttp
import aiofiles
import json
"""
1. 同步操作: 访问getCatalog 获取所有(章节、cid)
2. 异步操作: 访问getChapterContent 下载所有的内容
"""
async def aiodownload(cid, b_id, title):
data = {
"book_id": b_id,
"cid": f"{b_id}|{cid}",
"need_bookinfo": 1
}
# 转换为json字符串
data = json.dumps(data)
# print(data, 'json字符串')
url = f"https://dushu.baidu.com/api/pc/getChapterContent?data={data}"
# 发送异步请求(获取到小说的内容)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
dic = await resp.json()
# dic['data']['novel']['content'] # 小说的内容
# print(title, type(title), '标题')
# 异步写入文件
async with aiofiles.open('西游记小说/' + title + '.docx', mode='w', encoding='utf-8') as f:
await f.write(dic['data']['novel']['content'])
print(f'{title} 已下载完成')
async def getCatalog(url):
resp = requests.get(url)
# print(resp.json())
dic = resp.json()
tasks = []
for item in dic['data']['novel']['items']: # item 是对应章节名称和cid的
title = item['title']
cid = item['cid']
# print(cid, title, '名称,cid')
# 准备异步任务 每一个 cid 都是一个异步任务
# 创建 协程对象
d = asyncio.create_task(aiodownload(cid, b_id, title))
tasks.append(d)
await asyncio.wait(tasks)
if __name__ == '__main__':
b_id = "4306063500"
url = 'https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"'+ b_id + '"}'
# 解决报错
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(getCatalog(url))
print('over_all', '全部下载已完成')