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

多线程爬虫

其核心原理是利用线程切换技术来实现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)

  2. 定义一个生产者概念,源源不断往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))

  3. 定义一个消费者概念(解析)

    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))

  4. 开启线程

    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()

  5. 整体代码

    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来并发执行所有任务,并等待它们全部完成。

相关推荐
denghai邓海4 分钟前
红黑树删除之向上调整
python·b+树
封步宇AIGC30 分钟前
量化交易系统开发-实时行情自动化交易-3.4.1.2.A股交易数据
人工智能·python·机器学习·数据挖掘
何曾参静谧30 分钟前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices34 分钟前
C++如何调用Python脚本
开发语言·c++·python
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
Jam-Young1 小时前
Python的装饰器
开发语言·python
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django
AnFany2 小时前
LeetCode【0028】找出字符串中第一个匹配项的下标
python·算法·leetcode·字符串·kmp·字符串匹配
爪哇学长2 小时前
Java API类与接口:日期类型与集合的使用
java·开发语言·python
封步宇AIGC2 小时前
量化交易系统开发-实时行情自动化交易-3.4.1.6.A股宏观经济数据
人工智能·python·机器学习·数据挖掘