day8 爬虫相关方法

day8 爬虫相关方法

1. 全局解释器锁(GIL) 全称:Global Interpretter Lock

Cpytho —> 通过C实现的python解释器,申请和释放内容的操作并非线程安全,所以需要GIL来保证只有一个线路能执行关键操作,导致无法利用CPU的多核特性。
即便如此,多线程还是可以提升程序的执行效率(对于I/O密集型任务),也可以改善用户体验。

因此,对于计算密集型任务,可以使用多进程的方式来突破GIL的限制。

from threading import Thread  


def run_fast():
    while True:
        pass


if __name__ == '__main__':
    for _ in range(5):
        Thread(target=run_fast).start()
2.守护线程

守护线程 - 不值得保留的线程

主线程如果执行结束了,守护线程即便任务没有执行完也要跟着结束掉。

import time
from threading import Thread


def output(content):
    while True:
        print(content, end='')
        time.sleep(0.01)


if __name__ == '__main__':
    Thread(target=output, args=('Ping',), daemon=True).start()
    Thread(target=output, args=('Pong',), daemon=True).start()
    time.sleep(3)  # daemon = True 守护线程 主线程一停其他线程都跟着停
练习:

定义一个类描述一个银行账户(余额=0,存钱,取钱),
创建一个银行账户对象,启动100个线程,每个线程向该账户转入1元,
转账完成后,查看银行账户的余额。注意:存钱和取钱的受理是需要耗费时间的!

当多个线程竞争一个资源的时候,如果资源本身并不是线程安全的就会出现问题。
如果代码中需要对数据进行保护(保护临界资源),可以使用锁机制

如果对象实现了上下文管理器协议,就可以用到with语法中。
上下文管理器就是两个魔术方法:
~ enter:进入上下文的时候自动调用的方法
~ exit:离开上下文的时候自动调用的方法

import time 
from threading import Thread, RLock

class Account:
    """银行账户"""
    
    def __init__(self):
        self.balance = 0
        # 重入锁 (一般情况下我们使用的锁都是重入锁)
        self.lock = RLock()
        
    def deposit(self, money):
        """存钱"""
       	# 如果多个线程同时执行到这行代码,name后面的线程对余额的更新会覆盖掉之前的更新
        # 这个现象在数据库层面也有哦可能发生,我们称之为丢失更新(第一类丢失更新和第二类丢失更新)
        #通过上下文语法自动获得锁和释放锁  with
        with self.lock:
            new_balance = self.balane + money
            time.sleep(0.01)
            self.balance = new_balance
            
     def withdraw(aslf, money):
        """取钱"""
        if money <= self.balance:
            with self.lock:
                new_balance = self.balance - money
                time.sleep(0.01)
                self.balance = new_balance
            return True
        return False
 
if __name__ == '__main__':
    accpunt = Account()   # accpunt等于类
    add_money_threads = []
    for _ in range(100):
        t = Thread(target=account.depsit, args(1, ))
        add_money_threads.append(t)
        t.start()
    for t in add_money_thredas:
		t.join()
    print(account.balance)
3.池化技术

创建和释放一个线程都会产生比较大的系统开销,所以如果程序要长时间的使用多个线程
最好的方式是通过线程池来使用线程。线程池会提前创建好若干个线程,在使用的过程中
基本上不需要创建和释放线程,程序和线程池之间是借还关系。

池化技术是一种典型的空间换时间的技术。

import time
from concurrent.futures import ThreadPoolExecuto   
from threading import RLock   # 池化

class Account:
    """银行账户"""

    def __init__(self):
        self.balance = 0
        self.lock = RLock()

    def deposit(self, money):
        """存钱"""
        # 如果多个线程同时执行到这个代码,那么后面的线程对余额的更新会覆盖掉之前的更新
        # 这个现象在数据库层面也有可能发生,我们称之为丢失更新(第一类丢失更新第二类丢失更新)
        # 通过上下文语法自动获得锁和释放锁  with
        with self.lock:
            new_balance = self.balance + money
            time.sleep(0.01)
            self.balance = new_balance

    def withdraw(self, money):
        """取钱"""
        if money <= self.balance:
            with self.lock:
                new_balance = self.balance - money
                time.sleep(0.01)
                self.balance = new_balance
            return True
        return False
    
    
if __name__ == '__main__':
    account = Account()
    with ThreadPoolExecutor(max_workers=16) as pool:   # 创建16个线程
        for _ in range(100)
        	# 如果传入的函数有多个参数,可以直接依次传入,不需要用元组包装
            pool.submit(account.deoposit, 1)  # .submit 提交
   	pool.shutdown()  # .shutdown等线程执行完关闭
    print(account.balance)
4. 迭代器

迭代器:实现了迭代器协议的对象。
迭代器协议对应两个魔术方法:
~ iter:获取迭代器对象
~ next:获取到下一个值
作业:写一个可以迭代出从2开始的若干个质数(只能被1和自身整除的正整数)的迭代器

class FibIter:
    
    def __init__(self, num):
        self.a, sela.b = 0, 1
        self.num = num
        self.counter = 0
        
    def __init__(self)# 固定写法
    	return self  # 返回它自己
    
   	def __next__(self):
        if self.counter < self.num:
            self.a self.b = self.b, self.a + self.b
            slef.counter += 1 
            return self.a
       	raise StopIteraton()
        
        
if __name__ == '__main__':
    fiter = FibIter(20)
    for valur in fiter:
        print(value)
5.生成器

生成器:迭代器语法升级简化版本

def bif(num):
	a, b = 0, 1
	for _ in range(num):
		a, b = b, a + b
		yield a 
		

if __name__ == '__main__':
	fiter = fib(20)
	print(type(fiter))
	fro value in fiter
		print(valur)
6. 协程

协程(coroutine): 相互协作的子程序(函数)

生成器经过预激活操作就可以升级为一个协程

yield —> 产出 / 让步

def calc_avg():
	total, counter = 0, 0
	avg_value = None
	while True:
	curr_num = yield avg_value   # 产出和 让出CPU
	total += curr_num
	counter += 1
	avg_value = total / counter
	
	
def main():
	gen = calc_avg()
	# 将生成器预激活两种方式 ---> 协程
	next(gen)
	# gen.seng(None)  # 生成器预激活协程
    
	for _ in rang(5)
		num = int(input())
		print(gen.send(num))
		
if __name__ == '__main__':
    main()
7. 异步函数

使用异步函数来创建协程对象

async —> asynchronous
sync —> synchronous

# 定义异步函数
import asyncio

async def say_hello():
	print('hello, world')
	

# 调用异步函数不是执行函数体而是得到一个协程对象
co = say_hello()
# 协程对象需要挂载到一个事件循环上才能运作起来
loop = asucio.get_event_loop()
loop.run_until_complete(co)
loop.close()
8.同步阻塞式调用

I/O 操作模式:

​ ~ 同步:调用一个函数,必须等函数返回之后才能往下执行

​ ~ 异步:调用一个函数,不必等函数返回之后代码才能往下执行

​ – 阻塞 —> 多路I/O 复用

​ – 非阻塞 —> 回调式I/O 最好的一种选择

python做并发(并行)编程的三种方式

​ ~ 多线程

​ ~ 多进程

​ ~ 异步编程/异步I/O —> 协作式并发(通过多个子程序相互协作,提高CPU使用效率)

同步阻塞式调用:

import time

def display(num):
	print(num)
	time.sleep(1)
	
	
def main():
	for num in range(1, 10):
		display(num)
		
if __name__ == '__main__':
    main()
9. 同步非阻塞调用

同步 —> 有序, 异步 —> 无序

异步非阻塞式调用 —> 协作式并发 —> 多个子程序相互提高CPU利用率

async def display(num): # 函数前面加asuns变成异步函数
	print(num)
	# 等待休眠结 + 放弃对CPU的占用
	await asyncio.sleep(1)
	# await 放弃对CPU的占用
	
	
def main():
	# 创建一组协程对象
	cos = [dispaly(num) for num in range(1, 10)] # 这里是列表
	# 将协程对象挂载到事件循环上
	loop = asyncio.get_event_loop()  # 调用方法 形成事件循环 固定写法
	looop.run_until_complete(asuncio.wait(cos))
	# run_until_complete 把后面的挂载到时间任务上  asyncio.wait() 不能传列表 所以把cos转换成事件对象
	loop.close()  # 关闭
	
if __name__ == '__main__':
    main()
10. 通过异步I/O爬取标签 ----> aiohttp / httpx

导入 pip install aiohttp

import re
import aiohttp
import asyncio

# 命令捕获组(具名捕获组)
pattern = re.compile(r'<title>(?P<foo>.*?)</title>')

urls = [
    'https://www.python.org/',
    'https://www.taobao.com/',
    'https://pypi.org/',
    'https://www.git-scm.com/',
    'https://www.jd.com/',
    'https://opendata.sz.gov.cn/',
    'https://www.datacamp.com/'
]

asyns def show_title(url):
	"""根据指定的URL获取网站标题"""
	async with aiohttp.ClientSession() as session:
		await asyncio.sleep(1)
		async with session.get(url, ssl=False) as resp:
			html_code = await resp.text()
			match = pattern.search(html_code)
			if match:
				print(match.group('foo'))
				

cos_list = [shou_title(url) for url in urls]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(cos_list))
loop.close()
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值