python爬虫之JS逆向——并发爬虫

一、进程、线程以及协程的概念

1 进程

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体

跑起来的程序才叫进程

多道技术:空间复用+时间复用,于是有了进程!

进程一般由程序、数据集合和进程控制块三部分构成

进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换

在三态模型中,进程分为三个基本状态,即运行态、就绪态和阻塞态

在五态模型中,进程分为新建态、终止态、运行态、就绪态和阻塞态

2 线程

线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。

一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。

一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成,而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。

线程也有五种状态:创建、就绪、运行、阻塞和退出。

3 进程与线程的区别

以往所有代码都是一个进程里只有一个线程,该线程是主线程,主线程可以创建子线程

线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;

一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;

进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)即一些进程级的资源(如打开文件和信号),抹进程内的线程在其他进程不可见;

调度和切换:线程上下文切换比进程上下文切换要快得多。

4 协程

协程,也可称为微线程,或非抢占式的多任务子例程,一种用户态的上下文切换技术(通过一个线程实现代码块间的相互切换执行)。

这种由程序员自己写程序来管理的轻量级线程叫做 用户空间线程 具有对内核来说不可见的特性。

正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程

协程解决的是线程的切换开销和内存开销的问题

二、多线程实现------threading

1 threading简单运用

首先导入实现threading库和time库

python 复制代码
import time
import threading

其次构建两个函数用作测试

python 复制代码
def foo(t):
    print("foo开始")
    time.sleep(t)
    print("foo结束")


def bar(t):
    print("bar 开始")
    time.sleep(t)
    print("bar 结束")

串行版本:

python 复制代码
foo()
bar()

多线程并发版本:

python 复制代码
# 基于多线程的并发
t1 = threading.Thread(target=foo, args=(2, ))  # 构建线程对象
t1.start()  # 调度线程对象
t2 = threading.Thread(target=bar, args=(5, ))  # 构建线程对象
t2.start()  # 调度线程对象
# 此时有三个线程,主线程、t1和t2

# 等所有子线程结束后
t1.join()  # t1线程没有结束------阻塞
t2.join()

构建多个线程:

python 复制代码
# 创建多个线程
t_list = []
for i in range(10):
    t = threading.Thread(target=foo, args=(2,))
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()

stop = time.time()

2 互斥锁

没有锁的话"并发技术下数据的不安全性"

互斥锁就是舍弃一部分的速度,在计算数据的时候保证数据的安全性

threading.Lock()创建的lock对象

lock.acquire() 加锁

lock.release() 释放锁

python 复制代码
import threading
import time

lock = threading.Lock()  # 创建锁对象
x = 1000


def sub():
    lock.acquire()  # 加锁
    global x
    temp = x - 1
    time.sleep(0.0000001)
    x = temp
    lock.release()  # 释放锁


# 并发
t_list = []
for i in range(1000):
    t = threading.Thread(target=sub, args=())
    t.start()
    t_list.append(t)


for t in t_list:
    t.join()

print(x)

3 线程池

系统启动一个新线程成本比较高,因为它涉及与操作系统的交互,在这种情形下,使用线程池可以很好的提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑线程池。

线程池在系统启动时即创建大量空闲的线程,程序只需要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。

此外,使用线程池可以有效的控制系统中并发线程的数量,当系统中包含有大量的并发线程时,会导致系统性能极具下降,甚至导致解释器崩溃,而线程池的最大线程数参数库控制系统中并发线程的数量不超过此数。

构建线程池的基本方法:

python 复制代码
import time
# 引入线程池类
from concurrent.futures import ThreadPoolExecutor


def task(i):
    print(f"任务{i}开始")
    time.sleep(i)
    print(f"任务{i}结束")
    return i


start = time.time()
pool = ThreadPoolExecutor(3)
pool.submit(task, 1)  # 1s
pool.submit(task, 2)  # 2s
pool.submit(task, 3)  # 3s

pool.shutdown()  # 阻塞
print(f"耗时{time.time()-start}s")
print(future_list)
print([future.result() for future in future_list])

循环批量构建线程池:

python 复制代码
import time
# 引入线程池类
from concurrent.futures import ThreadPoolExecutor


def task(i):
    print(f"任务{i}开始")
    time.sleep(i)
    print(f"任务{i}结束")
    return i


start = time.time()
pool = ThreadPoolExecutor(3)
future_list = []
for i in range(1, 6):
    # 异步实现并发
    future = pool.submit(task, i)  # future对象
    future_list.append(future)
    # future.result()  # 结束后返回的结果
    # future.done()  # 返回bool值

pool.shutdown()  # 阻塞
print(f"耗时{time.time()-start}s")
print(future_list)
print([future.result() for future in future_list])

4 线程应用------斗图吧案例

单线程版本:

python 复制代码
import time
import requests
from lxml import etree


# 获取网页并解析
def get_img_urls():
    res = requests.get("https://www.doutub.com/img_lists/new/1")
    res.encoding = "utf-8"
    selector = etree.HTML(res.text)
    img_names = selector.xpath('//div[@class="cell"]/a/img/@alt')
    img_urls = selector.xpath('//div[@class="cell"]/a/img/@data-src')
    return img_names, img_urls


# 下载图片
def download_img(name, url):
    res = requests.get(url)
    with open(f"单线程斗图吧图片/{name}.jpg", "wb") as f:
        for i in res.iter_content():
            f.write(i)


start = time.time()
img_names, img_urls = get_img_urls()
for img_name, img_url in zip(img_names, img_urls):
    download_img(img_name, img_url)
    print(f"{img_name}下载完成")
print(f"整体耗时{time.time()-start}秒")

多线程版本:

python 复制代码
import time
import requests
from lxml import etree
import threading


# 获取网页并解析
def get_img_urls():
    res = requests.get("https://www.doutub.com/img_lists/new/1").text
    selector = etree.HTML(res)
    img_names = selector.xpath('//div[@class="cell"]/a/img/@alt')
    img_urls = selector.xpath('//div[@class="cell"]/a/img/@data-src')
    return img_names, img_urls


# 下载图片
def download_img(name, url):
    res = requests.get(url)
    with open(f"多线程斗图吧图片/{name}.jpg", "wb") as f:
        for i in res.iter_content():
            f.write(i)
    print(f"{name}下载完成")


start = time.time()
img_names, img_urls = get_img_urls()
t_list = []
for img_name, img_url in zip(img_names, img_urls):
    t = threading.Thread(target=download_img, args=(img_name, img_url,))
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()

print(f"整体耗时{time.time()-start}秒")

三、多进程

1 GIL锁

GIL锁是python的历史遗留问题,使得每一个python程序上都有一把锁,使得不能完全的实现并行,但是不影响并发

并行指的是在多个CPU上执行各个线程

2 多进程实现

虽然多进程开销大,但是由于python有GIL锁,如果有多个进程在不同CPU上工作也能使得速度加快

所以python的并发=多进程+协程

多进程的代码必须写在'main'中

python 复制代码
import multiprocessing
import time

# multiprocessing.Process拥有和threading.Thread()同样的API


def foo(t):
    print(f"任务{t}开始")
    time.sleep(t)
    print(f"任务{t}结束")


if __name__ == '__main__':
    start = time.time()
    # 创建多个进程
    t_list = []
    for i in range(1, 6):
        t = multiprocessing.Process(target=foo, args=(i,))
        t.start()
        t_list.append(t)

    for t in t_list:
        t.join()

    stop = time.time()
    print(f"耗时{stop - start}秒")

四、协程

1 yield

功能1:

迭代器和生成器:优化存储

可以将数据存入,需要的时候再生成,时间换取空间

函数里有yield就不是函数了,返回的是generator对象

python 复制代码
# yield:迭代器和生成器 优化存储

def get_data():
    print("start")
    yield 1  # 暂时返回,保存状态
    print("come back")
    yield 2
    print("come back2")
    yield 3


gen = get_data()
print(gen)  # <generator object get_data at 0x0000017025CDE5F0>

# gen.send(None)

ret = next(gen)
print(ret)
ret = next(gen)
print(ret)
ret = next(gen)
print(ret)

功能2:

实现协程

python 复制代码
def foo():
    print("foo start")
    yield
    print("foo stop")
    yield


def bar():
    print("bar start")
    yield
    print("bar stop")
    yield


gen_foo = foo()
gen_bar = bar()

next(gen_foo)
next(gen_bar)
next(gen_foo)
next(gen_bar)

2 asyncio模块

asyncio被用作多喝提供高性能Python异步框架的基础,包括网络和网站服务,数据库链接,分布式任务队列等等。

asyncio往往是构建IO密集型和高层级机构化网络代码的最佳选择。

基本使用:

python 复制代码
import asyncio
import time


async def task(i):
    print(f"任务{i}启动")
    await asyncio.sleep(i)
    print(f"任务{i}结束")


start = time.time()
# 创建事件循环对象
loop = asyncio.get_event_loop()
# 构建协程对象列表
tasks = [task(1), task(2), task(3)]  # 协程对象 coroutine object
# 启动运行
loop.run_until_complete(asyncio.wait(tasks))  # 阻塞等待所有协程结束
print("用时:", time.time()-start)

另一种方式:

在Windows中,asyncio.run(main())可能不能使用,只能用上面的方式...

python 复制代码
import asyncio
import time


async def foo(i):
    print(f"任务{i}启动")
    await asyncio.sleep(i)
    print(f"任务{i}结束")
    return i * i


async def main():
    # 构建任务对象列表
    tasks = [
        asyncio.create_task(foo(1)),
        asyncio.create_task(foo(2)),
        asyncio.create_task(foo(3))
    ]  # 协程对象 asyncio.Task对象

    # 收集任务方式1
    # tasks[0].add_done_callback(lambda obj: print(obj.result()))
    # await asyncio.wait(tasks)  # 阻塞
    # for task in tasks:
    #     print(task.done(), task.result())

    # 收集任务方式2
    rets = await asyncio.gather(*tasks)  # 可以传入列表
    print(rets)


start = time.time()
# 简写
asyncio.run(main())

print("用时:", time.time()-start)

3 task任务

构建过程:

对协程对象的进一步封装,包含任务的各个状态;

asyncio.Task是Future的一个子类,用于实现协作式多任务的库,且Task对象不能用户手动实例化,通过以下两个函数构建:

loop.create_task()

asyncio.ensure_future()

task任务在上面的例子中使用到...

五、aiohttp模块

aiohttp可以看作和requests对应的异步网络请求库,是基于asyncio的异步模块,可用于实现异步爬虫

优点:更快于requests的同步爬虫

安装:pip install aiohttp

基本用法:

python 复制代码
import aiohttp
import asyncio


async def main():
    async with aiohttp.ClientSession() as session:
        async with session.get("http://httpbin.org/headers") as response:
            print(await response.text())


asyncio.run(main())

六、其它

拼接视频 终端输入 ffmpeg -i xx.m3u8 -c copy yy.mp4

代码实现自动化

奉上斗图吧异步并发案例:

python 复制代码
import time
from lxml import etree
import asyncio
import aiohttp


# 获取网页并解析
async def get_img_urls():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://www.doutub.com/img_lists/new/1", ssl=False) as response:
            selector = etree.HTML(await response.text())
            img_names = selector.xpath('//div[@class="cell"]/a/img/@alt')
            img_urls = selector.xpath('//div[@class="cell"]/a/img/@data-src')
            return img_names, img_urls


# 下载图片
async def download_img(name, url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url, ssl=False) as response:
            with open(f"imgs/{name}.jpg", "wb") as f:
                f.write(await response.content.read())
            print(f"{name}下载完成!")


async def main():
    img_names, img_urls = await get_img_urls()
    tasks = [asyncio.create_task(download_img(name, url)) for name, url in zip(img_names, img_urls)]
    await asyncio.wait(tasks)


# 主逻辑
start = time.time()
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(main())
print(f"整体耗时{time.time()-start}秒")
相关推荐
好看资源平台7 分钟前
网络爬虫——综合实战项目:多平台房源信息采集与分析系统
爬虫·python
进击的六角龙28 分钟前
深入浅出:使用Python调用API实现智能天气预报
开发语言·python
檀越剑指大厂28 分钟前
【Python系列】浅析 Python 中的字典更新与应用场景
开发语言·python
湫ccc36 分钟前
Python简介以及解释器安装(保姆级教学)
开发语言·python
孤独且没人爱的纸鹤39 分钟前
【深度学习】:从人工神经网络的基础原理到循环神经网络的先进技术,跨越智能算法的关键发展阶段及其未来趋势,探索技术进步与应用挑战
人工智能·python·深度学习·机器学习·ai
羊小猪~~42 分钟前
tensorflow案例7--数据增强与测试集, 训练集, 验证集的构建
人工智能·python·深度学习·机器学习·cnn·tensorflow·neo4j
lzhlizihang44 分钟前
python如何使用spark操作hive
hive·python·spark
q0_0p1 小时前
牛客小白月赛105 (Python题解) A~E
python·牛客
极客代码1 小时前
【Python TensorFlow】进阶指南(续篇三)
开发语言·人工智能·python·深度学习·tensorflow
庞传奇1 小时前
TensorFlow 的基本概念和使用场景
人工智能·python·tensorflow