加载中...

协程及在爬虫中应用


协程及爬虫中应用

协程的概念

# 进程 资源分配的最小单位
# 线程 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
# 这样的速度肯定是不够的,只是一个简单的下载视频例子。剩下的使用异步或者多线程来完成吧。

完整版视频下载

请点击:完整版视频下载


文章作者: 无夜
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 无夜 !
评论
  目录