提升爬虫效率:多线程,多进程,多协程

多线程爬虫

其核心原理是利用线程切换技术来实现CPU级别的并发执行。这种并发执行并不等同于多CPU的并行操作,而是在单个CPU上通过快速切换线程来模拟同时处理多个任务的效果。

爬虫使用多线程来处理网络请求,使用多线程来处理队列的URL,然后将URL返回结果放到另外一个队列只能够,其他线程再读取这个队列的数据。这种有种生产者,消费者的概念一样。

了解下queue

Python中的队列(queue)是一种先进先出(FIFO)的数据结构,可以使用标准库中的queue模块实现。以下是一个简单的示例:

import queue

# 创建一个队列
q = queue.Queue()

# 向队列中添加元素
q.put(1)
q.put(2)
q.put(3)

# 从队列中获取元素
print(q.get())  # 输出:1
print(q.get())  # 输出:2
print(q.get())  # 输出:3

多线程案例

  1. 首先定义两个队列来存放url,另一个用来放访问url后的html信息。
url_queue = queue.Queue()
html_queue = queue.Queue()
urls = [
    f"https://www.cnblogs.com/#p{page}"
    for page in range(1, 10 + 1)
]
for url in urls:
    url_queue.put(url)
  1. 定义一个生产者概念,源源不断往html_queue放数据
def craw(url):
    r = requests.get(url)
    return r.text

def do_craw(url_queue:queue.Queue, html_queue:queue.Queue):
    while True:
        url = url_queue.get()
        html = craw(url)
        html_queue.put(html)
        print(threading.current_thread().name,f"do_craw {url},size {url_queue.qsize()}")
        time.sleep(random.randint(1, 2))
  1. 定义一个消费者概念(解析)
def parser(html):
    soup = BeautifulSoup(html, 'html.parser')
    links = soup.find_all('a', 'post-item-title') # 标签,class
    return [(link['href'], link.get_text()) for link in links]

def do_parser(html_queue:queue.Queue):
    while True:
        html = html_queue.get()
        results = parser(html)
        for result in results:
            print(threading.current_thread().name, result,f"html_queue size:{html_queue.qsize()}")
        time.sleep(random.randint(1, 2))
  1. 开启线程
for idx in range(3):
    t = threading.Thread(target=do_craw, args=(url_queue,html_queue), name = f'do_craw{idx}')
    t.start()

for idx in range(2):
    t = threading.Thread(target=do_parser, args=(html_queue,), name=f'do_parser{idx}')
    t.start()
  1. 整体代码
import queue
import random
import threading
import time

import requests
from bs4 import BeautifulSoup

def craw(url):
    r = requests.get(url)
    return r.text

def do_craw(url_queue:queue.Queue, html_queue:queue.Queue):
    while True:
        url = url_queue.get()
        html = craw(url)
        html_queue.put(html)
        print(threading.current_thread().name,f"do_craw {url},size {url_queue.qsize()}")
        time.sleep(random.randint(1, 2))

def parser(html):
    soup = BeautifulSoup(html, 'html.parser')
    links = soup.find_all('a', 'post-item-title') # 标签,class
    return [(link['href'], link.get_text()) for link in links]

def do_parser(html_queue:queue.Queue):
    while True:
        html = html_queue.get()
        results = parser(html)
        for result in results:
            print(threading.current_thread().name, result,f"html_queue size:{html_queue.qsize()}")
        time.sleep(random.randint(1, 2))


if __name__ == '__main__':
    url_queue = queue.Queue()
    html_queue = queue.Queue()
    urls = [
        f"https://www.cnblogs.com/#p{page}"
        for page in range(1, 10 + 1)
    ]
    for url in urls:
        url_queue.put(url)

    for idx in range(3):
        t = threading.Thread(target=do_craw, args=(url_queue,html_queue), name = f'do_craw{idx}')
        t.start()

    for idx in range(2):
        t = threading.Thread(target=do_parser, args=(html_queue,), name=f'do_parser{idx}')
        t.start()

多进程爬虫

在计算机系统中,多个CPU同时执行不同的任务,以提高系统的处理能力和效率。在Python中,可以使用multiprocessing模块来实现多CPU并行,其实也叫多进程。

了解下Pool

Pool在Python的multiprocessing模块中是一个非常重要的类,它代表一个进程池。

进程池(Pool)是一种资源管理形式,它可以管理和控制多个进程的创建、执行和终止。通过使用进程池,可以有效地利用系统资源,提高程序运行效率。以下是对Pool类的一些详细解释:

  • 资源重用:Pool类可以创建一个进程池,其中的进程可以被重复使用。这意味着不需要为每个任务创建和销毁进程,从而减少了开销。
  • 自动管理:Pool类会自动管理进程的生命周期。当任务被提交到进程池时,它会选择一个可用的进程来执行该任务。如果没有可用的进程,任务会在队列中等待,直到有进程可用。
  • 任务队列:Pool类使用任务队列来存储待处理的任务。当进程池中有可用进程时,任务会从队列中取出并执行。
  • 并行处理:Pool类支持并行处理,即多个任务可以同时在不同的进程中执行。这有助于提高程序的执行速度,特别是在多核处理器上运行时。
  • 灵活性:Pool类提供了多种方法来提交任务,如map、imap、apply等,这些方法使得向进程池提交任务变得非常灵活和方便。
  • 上下文管理:Pool类支持作为上下文管理器使用,可以使用with语句来自动管理进程池的创建和销毁。

综上所述,理解Pool类是理解Python多进程编程的关键。通过使用Pool类,可以更高效地管理和执行多个进程,从而提高程序的性能。

1. apply

apply方法只能用于提交单个任务,并且需要指定任务的参数。如果要同时提交多个任务,可以使用map或imap方法。

from multiprocessing import Pool

def square(x):
    return x * x

if __name__ == "__main__":
    with Pool(processes=4) as pool:
        result = pool.apply(square, args=(2,))
        print("Result:", result)
2. apply_async

apply_async 允许你异步地执行一个函数。这个函数会在一个新的进程中运行,并且返回一个AsyncResult对象,你可以用这个对象来获取函数的结果。

from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    with Pool(5) as p:
        result = p.apply_async(f, (10,))
        print(result.get())
3. map

这将在进程池中的多个进程中并行执行square函数,并将结果存储在一个列表中。

from multiprocessing import Pool

def square(x):
    return x * x

if __name__ == "__main__":
    with Pool(processes=4) as pool:
        results = pool.map(square, range(10))
        print("Results:", results)
4. imap

Pool类中的imap方法与map方法类似,都是用于并行地执行函数。不过,imap方法在处理结果时稍有不同,它返回的是一个迭代器,而不是一个列表。这意味着你可以立即开始处理结果,而不需要等待所有任务完成。这对于处理大量数据或长时间运行的任务特别有用,因为它可以帮助减少内存使用并提高程序响应性。

from multiprocessing import Pool

def square(x):
    return x * x

if __name__ == "__main__":
    with Pool(processes=4) as pool:
        # imap返回一个迭代器
        results = pool.imap(square, range(10))
        # 遍历迭代器来获取结果
        for result in results:
            print("Result:", result)

多进程案例

import requests
from bs4 import BeautifulSoup
from multiprocessing import Pool
import os

def craw(url):
    r = requests.get(url)
    results = parser(r.text)  # 解析网页并提取所需信息
    for result in results:
        print("当前进程编号:", os.getpid())  # 打印当前进程编号
        print(result)


def parser(html):
    soup = BeautifulSoup(html, 'html.parser')
    links = soup.find_all('a', 'post-item-title') # 标签,class
    return [(link['href'], link.get_text()) for link in links]

if __name__ == '__main__':
    urls = [
        f"https://www.cnblogs.com/#p{page}"
        for page in range(1, 10 + 1)
    ]

    with Pool(processes=4) as pool:  # 创建一个包含4个进程的进程池
        pool.map(craw, urls)  # 使用进程池并发获取网页

多协程爬虫

多协程是指在一个线程中运行多个协程,它们通过任务的暂停和恢复来避免线程切换的开销,并且减少了锁的使用。

asyncio模块只能发tcp级别的请求,不能发http协议。

多协程案例

import asyncio
import aiohttp
from bs4 import BeautifulSoup

async def fetch(url):
    connector = aiohttp.TCPConnector(ssl=False)
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = [
        f"https://www.cnblogs.com/#p{page}"
        for page in range(1, 10 + 1)
    ]
    tasks = [fetch(url) for url in urls]
    results = await asyncio.gather(*tasks)

    for result in results:
        pages = parser(result)
        for page in pages:
            print(page)

def parser(html):
    soup = BeautifulSoup(html, 'html.parser')
    links = soup.find_all('a', 'post-item-title')  # 标签,class
    return [(link['href'], link.get_text()) for link in links]



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

首先定义了一个异步函数fetch,它使用aiohttp库异步获取给定URL的内容。然后,我们在main函数中创建了一个任务列表,其中每个任务都是对fetch函数的调用。我们使用asyncio.gather来并发执行所有任务,并等待它们全部完成。

相关推荐

  1. 提升爬虫效率线进程

    2024-02-22 14:58:02       46 阅读
  2. Queue的线爬虫和multiprocessing进程

    2024-02-22 14:58:02       44 阅读
  3. Rust 实战练习 - 5. 线进程

    2024-02-22 14:58:02       38 阅读
  4. 线进程

    2024-02-22 14:58:02       55 阅读
  5. Python---进程---线

    2024-02-22 14:58:02       63 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-22 14:58:02       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-22 14:58:02       106 阅读
  3. 在Django里面运行非项目文件

    2024-02-22 14:58:02       87 阅读
  4. Python语言-面向对象

    2024-02-22 14:58:02       96 阅读

热门阅读

  1. 近红外γ-谷氨酰转肽酶(GGT)荧光探针(NRh-G)星戈瑞

    2024-02-22 14:58:02       51 阅读
  2. 物联网和人工智能的融合

    2024-02-22 14:58:02       60 阅读
  3. android 全局异常处理封装

    2024-02-22 14:58:02       52 阅读
  4. C语言内存模型的深度剖析

    2024-02-22 14:58:02       51 阅读
  5. 自然语言转SQL的应用场景探索

    2024-02-22 14:58:02       50 阅读
  6. 剑指offer面试题17 合并俩个排序的列表

    2024-02-22 14:58:02       49 阅读
  7. sqlserver 函数

    2024-02-22 14:58:02       50 阅读
  8. 算法:带权重随机算法

    2024-02-22 14:58:02       49 阅读
  9. spring和springboot的区别,简单直接

    2024-02-22 14:58:02       52 阅读
  10. Python程序员面试准备:八股文题目与解答思路

    2024-02-22 14:58:02       48 阅读