协程及爬虫中应用
协程的概念
# 进程 资源分配的最小单位
# 线程 CPU执行的最小单位
# 只要是线程里的代码 就都被CPU执行就行
# 线程是由 操作系统 调度,由操作系统负责切换的
# 协程:
# 用户级别的,由我们自己写的python代码来控制切换的,是操作系统不可见的
# 当程序遇见IO操作的时候,可以选择性的切换到其他任务上
# 在宏观上,我们看到的其实是多个任务一起在执行
# 在微观上,是一个任务一个任务的进行切换,切换条件一般就是IO操作
# 上方所说的,都是在单线程的条件下
# 在Cpython解释器下 - 协程和线程都不能利用多核,都是在一个CPU上轮流执行
# 由于多线程本身就不能利用多核
# 所以即便是开启了多个线程也只能轮流在一个CPU上执行
# 协程如果把所有任务的IO操作都规避掉,只剩下需要使用CPU的操作
# 就意味着协程就可以做到题高CPU利用率的效果
# 多线程和协程
# 线程 切换需要操作系统,开销大,操作系统不可控,给操作系统的压力大
# 操作系统对IO操作的感知更加灵敏
# 协程 切换需要python代码,开销小,用户操作可控,完全不会增加操作系统的压力
# 用户级别能够对IO操作的感知比较低
asyncio示例
基本写法(不推荐)
import time
import asyncio
async def func1():
print("1")
# time.sleep(3) # 当程序出现了同步操作的时候,异步就中断了
await asyncio.sleep(3) # 异步操作的代码
print("2")
async def func2():
print("3")
await asyncio.sleep(2)
print("4")
async def func3():
print("5")
await asyncio.sleep(4)
print("6")
if __name__ == '__main__':
f1 = func1()
f2 = func2()
f3 = func3()
tasks = [
f1,f2,f3
]
t1 = time.time()
asyncio.run(asyncio.wait(tasks))
t2 = time.time()
print(t2-t1)
推荐写法(内有过时警告!)
async def func1():
print("1")
await asyncio.sleep(3) # 异步操作的代码
print("2")
async def func2():
print("3")
await asyncio.sleep(2)
print("4")
async def func3():
print("5")
await asyncio.sleep(4)
print("6")
async def main():
# 第一种写法
# f1 = func1()
# await f1 # 一般await挂起操作放在携程对象前面
# 第二种写法(推荐)
tasks = [
func1(),
func2(),
# py3.8以后加上asyncio.create_task(),如下示例,是为了把协程对象包装成task对象
# py3.7可以自动包装,但是3.11之后会弃掉,并且3.8及之后版本不这样写会有警告
asyncio.create_task(func3())
]
await asyncio.wait(tasks)
if __name__ == '__main__':
t1 = time.time()
# 一次性启动多个任务(协程)
asyncio.run(main())
t2 = time.time()
print(t2-t1)
爬虫套用示例
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 = download(url)
tasks.append(d)
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())
aiohttp模块
使用aiohttp模块爬取图片示例
# requests.get() 同步的代码 -> 异步操作aiohttp
# pip install aiohttp
import asyncio
import aiohttp
urls = [
"https://pic.netbian.com/uploads/allimg/180908/170122-15363972822a8c.jpg",
"https://pic.netbian.com/uploads/allimg/180609/115910-1528516750cf00.jpg",
"https://pic.netbian.com/uploads/allimg/180705/111224-15307603442589.jpg"
]
async def aiodownload(url):
name = url.rsplit("/",1)[1] #
# s = aiohttp.ClientSession() <==> requests 用法一模一样,只不过换了个名
# s.get() .post <==> requests.get() .post
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
# resp.content.read() <==> resp.content
# resp.text() <==> resp.text
# resp.json() <==> resp.json()
# 请求回来了,写入文件
# 可以自己去学习一个模块替换下面创建文件,aiofiles
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 = aiodownload(url)
tasks.append(d)
await asyncio.wait(tasks) # 其实就是asyncio.run(asyncio.wait(tasks))
if __name__ == '__main__':
asyncio.run(main())
使用异步抓取一部小说
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}
data = json.dumps(data)
url = f"http://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()
# 使用aiofiles模块异步写内容
async with aiofiles.open(title, mode="w", encoding="utf-8") as f:
await f.write(dic["data"]["novel"]["content"]) # 把小说内容全部写进去
async def getCatalog(url):
resp = requests.get(url)
result_dict = resp.json()
tasks = []
for item in result_dict["data"]["novel"]["items"]:
title = "novel/" + item["title"]
cid = item["cid"]
print(title,cid)
# 准备异步任务
tasks.append(aiodownload(cid,b_id,title))
await asyncio.wait(tasks)
if __name__ == '__main__':
b_id = "4306063500"
url = 'http://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"'+ b_id +'"}'
asyncio.run(getCatalog(url))
抓取视频
抓取视频原理
# 视频网站原理
# 用户上传 -> 转码(把视频做处理,2k,1080,标清) -> 切片处理(把单个的文件进行拆分)
# 需要一个文件记录:1.视频播放顺序,2.视频存放的路径
# 一般可能存放为 M3U|M3U8 文件,M3U在经过utf-8编码之后叫M3U8
# 想要抓取一个视频:
# 1.找到M3U8文件(各种手段)
# 2.通过m3u8下载到ts文件
# 3.可以通过各种手段(不仅是编程手段)把ts文件合并为一个mp4文件
简单抓取视频
"""
这里选择91看剧,因为每个视频播放源不一样,对应的爬取方式就不一样。此处是vip播源,最简单的方式
流程:
1. 拿到播放页面源代码,尝试从video关键字处拿到m3u8
2. 从源代码中提取到m3u8的url
3. 下载m3u8文件
4. 读取m4u8文件,下载视频
5. 合并视频
"""
import requests
import re
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
}
obj = re.compile(r"url: '(?P<url>.*?)',",re.S) # 用来提取m3u8地址
# 获取m3u8文件地址
url = "https://www.91kanju.com/vod-play/56169-1-1.html"
response1 = requests.get(url,headers=headers)
m3u8_url = obj.search(response1.text).group("url")
# print(m3u8_url)
response1.close()
# 下载m3u8文件
response2 = requests.get(m3u8_url,headers=headers)
with open("哲人王后.m3u8",mode="wb") as f:
f.write(response2.content)
response2.close()
print("下载完毕")
# 解析m3u8文件
with open("哲人王后.m3u8", mode="r", encoding="utf-8") as f:
n = 0
for line in f:
line = line.strip() # 先去掉空格,空白,换行符
if line.startswith("#"):continue
# 下载视频片段
response3 = requests.get(line)
f = open(f"video/{n}.ts",mode="wb")
f.write(response3.content)
f.close()
response3.close()
n += 1
print("完成1个")
if n==2:
break
# 这样的速度肯定是不够的,只是一个简单的下载视频例子。剩下的使用异步或者多线程来完成吧。
完整版视频下载
请点击:完整版视频下载
文章密码:123!@#