首页 > 其他分享 >如何提高爬虫工作效率

如何提高爬虫工作效率

时间:2023-03-08 09:22:05浏览次数:44  
标签:__ name 爬虫 工作效率 如何 线程 print main asyncio

单进程单线程爬取目标网站太过缓慢,这个只是针对新手来说非常友好,只适合爬取小规模项目,如果遇到大型项目就不得不考虑多线程、线程池、进程池以及协程等问题。那么我们该如何提升工作效率降低成本?

学习之前首先要对线程,进程,协程做一个简单的区分吧:

进程是资源单位,每一个进程至少要有一个线程,每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。

线程是执行单位,启动每一个程序默认都会有一个主线程。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

协程是一种用户态的轻量级线程, 协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

了解了进程、线程、协程之间的区别之后,我们就可以思考如何用这些东西来提高爬虫的效率呢?

提高爬虫效率的方法

多线程

要体现多线程的特点就必须得拿单线程来做一个比较,这样才能凸显不同~

单线程运行举例:

def func():
     for i in range(5):
         print("func", i)


 if __name__ == '__main__':
     func()
     for i in range(5):
         print("main", i)

运行结果如下:

# 单线程演示案例

result:
func 0
func 1
func 2
func 3
func 4
main 0
main 1
main 2
main 3
main 4

可以注意到在单线程的情况下,程序是先打印fun 0 - 4, 再打印main 0 - 4。

下面再举一个多线程的例子:

需要实例化一个Thread类 Thread(target=func()) target接收的就是任务(/函数),通过.start()方法就可以启动多线程了。

代码提供两种方式:

# 多线程(两种方法)
# 方法一:
 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)
# two
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)

运行结果

子线程和主线程有时候会同时执行,这就是多线程吧。

线程创建之后只是代表处于能够工作的状态,并不代表立即执行,具体执行的时间需要看CPU。

感觉线程执行的顺序就是杂乱无章的。

接下来分享一下多进程:

多进程

进程的使用:Process(target=func())

先举一个例子来感受一下多进程的执行顺序:

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)

运行结果:

从结果中可以发出,所有的子进程按照顺序执行之后。就开始打印主进程0-999。进程打印的有序也表明线程是最小的执行单位。

开启多线程打印的时候,出现的数字并不是有序的。

线程池&进程池

在python中一般使用以下方法创建线程池/进程池:

with ThreadPoolExecutor(50) as t:

     t.submit(fn, name=f"线程{i}")

具体代码:

# 线程池:一次性开辟一些线程,我们用户直接给线程池提交任务,线程任务的调度交给线程池来完成
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def fn(name):
    for i in range(1000):
        print(name,i)

if __name__ == '__main__':
    # 创建线程池
    with ThreadPoolExecutor(50) as t:
        for i in range(100):
            t.submit(fn, name=f"线程{i}")
    # 等待线程池中的任务全部执行完毕,才继续执行(守护)
    print(123)

进程池的创建方法类似。

协程

协程:当程序遇见IO操作的时候,可以选择性的切换到其他任务上。

在微观上是一个任务一个任务的进行切换,切换条件一般就是IO操作。

在宏观上,我们能看到的其实就是多个任务一起在执行。

多任务异步操作(就像你自己一边洗脚一边看剧一样~,时间管理带师(bushi)。

线程阻塞的一些案例:

例子1:

time.sleep(30)    # 让当前线程处于阻塞状态,CPU是不为我工作的
# input()    程序也是处于阻塞状态
# requests.get(xxxxxx) 在网络请求返回数据之前,程序也是处于阻塞状态
# 一般情况下,当程序处于IO操作的时候,线程都会处于阻塞状态
# for example: 边洗脚边按摩
import asyncio
import time

async def func():
     print("hahha")

if __name__ == "__main__":
     g = func()  # 此时的函数是异步协程函数,此时函数执行得到的是一个协程对象
     asyncio.run(g) # 协程程序运行需要asyncio模块的支持

输出结果:

root@VM-12-2-ubuntu:~/WorkSpace# python test.py
hahha

例子2:

async def func1():
     print("hello,my name id hanmeimei")
     # time.sleep(3)  # 当程序出现了同步操作的时候,异步就中断了
     await asyncio.sleep(3)  # 异步操作的代码
     print("hello,my name id hanmeimei")


 async def func2():
     print("hello,my name id wahahha")
     # time.sleep(2)
     await asyncio.sleep(2)  # 异步操作的代码
     print("hello,my name id wahahha")


 async def func3():
     print("hello,my name id hhhhhhhc")
     # time.sleep(4)
     await asyncio.sleep(4)  # 异步操作的代码
     print("hello,my name id hhhhhhhc")


 if __name__ == "__main__":
     f1 = func1()
     f2 = func2()
     f3 = func3()
     task = [
         f1, f2, f3
     ]
     t1 = time.time()
     asyncio.run(asyncio.wait(task))
     t2 = time.time()
     print(t2 - t1)

运行结果:

注意到执行await asyncio.sleep(4)后,主程序就会调用其他函数了。成功实现了异步操作。(边洗脚边按摩bushi )

下面的代码看起来更为规范~

async def func1():
    print("hello,my name id hanmeimei")
    await asyncio.sleep(3)
    print("hello,my name id hanmeimei")


async def func2():
    print("hello,my name id wahahha")
    await asyncio.sleep(2)
    print("hello,my name id wahahha")


async def func3():
    print("hello,my name id hhhhhhhc")
    await asyncio.sleep(4)
    print("hello,my name id hhhhhhhc")


async def main():
    # 第一种写法
    # f1 = func1()
    # await f1 # 一般await挂起操作放在协程对象前面
    # 第二种写法(推荐)
    tasks = [
        func1(),   # py3.8以后加上asyncio.create_task()
        func2(),
        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) # 网络请求
    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())
# requests.get()  同步的代码 => 异步操作aiohttp

import asyncio
import aiohttp

urls = [
    "http://kr.shanghai-jiuxin.com/file/2020/1031/191468637cab2f0206f7d1d9b175ac81.jpg",
    "http://i1.shaodiyejin.com/uploads/tu/201704/9999/fd3ad7b47d.jpg",
    "http://kr.shanghai-jiuxin.com/file/2021/1022/ef72bc5f337ca82f9d36eca2372683b3.jpg"
]


async def aiodownload(url):
    name = url.rsplit("/", 1)[1]  # 从右边切,切一次,得到[1]位置的内容 fd3ad7b47d.jpg
    async with aiohttp.ClientSession() as session: # requests
        async with session.get(url) as resp: # resp = requests.get()
            # 请求回来之后,写入文件
            # 模块 aiofiles
            with open(name, mode="wb") as f: # 创建文件
                f.write(await resp.content.read())  # 读取内容是异步的,需要将await挂起, resp.text()
    print(name, "okk")
            # resp.content.read() ==> resp.text()
    # s = aiphttp.ClientSession <==> requests
    # requests.get()  .post()
    # s.get()  .post()
    # 发送请求
    # 保存图片内容平
    # 保存为文件


async def main():
    tasks = []
    for url in urls:
        tasks.append(aiodownload(url))
    await asyncio.wait(tasks)


if __name__ == '__main__':
    asyncio.run(main())

标签:__,name,爬虫,工作效率,如何,线程,print,main,asyncio
From: https://www.cnblogs.com/q-q56731526/p/17190762.html

相关文章

  • Python常见面试题011. 如何在Python中动态创建类?
    011.如何在Python中动态创建类?说在前面答案是type你印象中的type是用来查看对象的类型的li=[]type(li)#得到list对自定义的类是这样的classPerson:......
  • 如何屏蔽国外IP访问
    宝塔如何屏蔽恶意CC和ddoss攻击以及国外IP访问呢?在我们服务器资源有限的情况下,各大搜索引擎每天不断爬取我们的服务器上的站点,但是有很多国外恶意蜘蛛抓取我们的网页,这对于......
  • vue生命周期以及如何将axios挂载到vue的原型链上
    生命周期组件的生命周期是指一个组件从创建->运行->销毁的整个阶段,强调的是一个时间段生命周期函数:是由vue框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行 ......
  • 《数据库基础语法》5. 什么是函数?如何利用函数提高效率
    楔子SQL语句主要的功能就是对数据进行处理和分析。为了避免重复造轮子,提高数据处理的效率,SQL为我们提供了许多标准的功能模块:函数(Function)。SQL函数是一种具有某种功......
  • android stdio如何迁移device
    1.在C:\Users\(名字)\.android\avd文件中找到虚拟机文件复制且删除(由于我已经迁移完毕,只剩下一个.ini文件)  类似于这样的虚拟机文件。  2.将它复制或迁移到你希......
  • 变量定义:变量是做什么的,如何定义一个变量
    当我们要计算找零时 如何能够在程序运行时输入那个数字23,然后计算输出结果呢?  下列程序就能执行上述功能  当我们执行之后会出现终端。   输入的数......
  • 如何才能写好代码?
    前言看别人的代码,自己的代码,大牛的代码,网上博客的代码。仅实现功能时感觉良好,自己脑袋清楚时感觉良好。需要改动自己的代码时,apieceofshit;看别人的代码,更是无从下手。......
  • 2023爬虫学习笔记 -- m3u8视频下载
    一、目标地址https://www.XXXX.com/二、获取mu38文件1、点击XHR,刷新页面,会看到这里有两个m3u8文件2、将m3u8地址复制到浏览器,会自动下载下来,index内容如下mixed内容如下3、......
  • Kafka是如何实现动态重平衡?
    Kafka是如何实现动态重平衡?Kafka是一个分布式流处理平台,它通过动态重平衡来实现高可靠性、高可扩展性和高性能。动态重平衡:即在集群中增加或者删除broker、更改消费者组......
  • 第一个C程序:如何在DevC++中编辑、编译和运行程序
    第一步:打开DevC++程序,选择文件——新建——源代码 第二步:保证输入法在英文状态下输入代码 第三步:保存文件,保存的时候选择.c文件格式 保存完之后*号消失 ......