Python是一种高级编程语言,它在很多编程语言中,拥有极高的人气和运用率。Python中的多线程和线程池是其强壮的功用之一,能够让咱们更加高效地利用CPU资源,进步程序的运转速度。本篇博客将介绍Python中多线程和线程池的运用办法,并提供一些实用的事例供读者参考。

一、多线程

多线程是指在同一进程中,有多个线程一起履行不同的使命。Python中的多线程是经过threading模块来完成的。下面是一个简略的多线程示例:

import threading
def task(num):
    print('Task %d is running.' % num)
if __name__ == '__main__':
    for i in range(5):
        t = threading.Thread(target=task, args=(i,))
        t.start()

上述代码中,咱们界说了一个task函数,它承受一个参数num,用于标识使命。在主程序中,咱们创立了5个线程,每个线程都履行task函数,并传入不同的参数。经过start()办法发动线程。运转上述代码,能够看到输出成果类似于下面这样:

Task 0 is running.
Task 1 is running.
Task 2 is running.
Task 3 is running.
Task 4 is running.

由于多线程是并发履行的,因而输出成果的次序可能会有所不同。

二、线程池

线程池是一种办理多线程的机制,它能够预先创立必定数量的线程,并将使命分配给这些线程履行。Python中的线程池是经过ThreadPoolExecutor类来完成的。下面是一个简略的线程池示例:

import concurrent.futures
def task(num):
    print('Task %d is running.' % num)
if __name__ == '__main__':
    with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
        for i in range(5):
            executor.submit(task, i)

上述代码中,咱们运用了with句子创立了一个ThreadPoolExecutor对象,其中max_workers参数指定了线程池中最大的线程数量。在主程序中,咱们创立了5个使命,每个使命都经过executor.submit()办法提交给线程池履行。运转上述代码,能够看到输出成果类似于下面这样:

Task 0 is running.
Task 1 is running.
Task 2 is running.
Task 3 is running.
Task 4 is running.

由于线程池中最大的线程数量为3,因而只要3个使命能够一起履行,其他使命需求等候线程池中的线程空闲后再履行。

三、运用事例

下面是一个实践的事例,展现了怎么运用多线程和线程池来加快数据处理进程。假定咱们有一个包含1000个元素的列表,需求对每个元素进行某种运算,并将成果保存到另一个列表中。咱们能够运用单线程的办法来完成:

def process(data):
    result = []
    for item in data:
        result.append(item * 2)
    return result
if __name__ == '__main__':
    data = list(range(1000))
    result = process(data)
    print(result)

上述代码中,咱们界说了一个process函数,它承受一个列表作为参数,对列表中的每个元素进行运算,并将成果保存到另一个列表中。在主程序中,咱们创立了一个包含1000个元素的列表,并将其传递给process函数。运转上述代码,能够看到输出成果类似于下面这样:

[0, 2, 4, 6, 8, ..., 1996, 1998]

Python中的多线程和线程池能够进步爬虫的功率,本文将介绍一个爬取豆瓣电影Top250的事例,并经过多线程和线程池优化爬取进程。

  1. 单线程爬取

首要,咱们先来看一下单线程爬取的代码:

# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
def get_html(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        else:
            return None
    except Exception as e:
        print(e)
def parse_html(html):
    soup = BeautifulSoup(html, 'lxml')
    movie_list = soup.find(class_='grid_view').find_all('li')
    for movie in movie_list:
        title = movie.find(class_='title').string
        rating = movie.find(class_='rating_num').string
        print(title, rating)
def main():
    url = 'https://movie.douban.com/top250'
    html = get_html(url)
    parse_html(html)
if __name__ == '__main__':
    main()

这是一个简略的爬取豆瓣电影Top250的代码,首要经过requests库获取网页的HTML代码,然后运用BeautifulSoup库解析HTML代码,获取电影名称和评分。

但是,这种单线程爬取的办法功率较低,由于在获取HTML代码的时分需求等候呼应,而在等候呼应的进程中CPU会空闲,无法充分利用计算机的功能。

  1. 多线程爬取

接下来,咱们经过多线程的办法来优化爬取进程。首要,咱们需求导入Python中的threading库:

import threading

然后,咱们将获取HTML代码的代码放在一个函数中,并将其作为一个线程来运转:

def get_html(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        else:
            return None
    except Exception as e:
        print(e)
class GetHtmlThread(threading.Thread):
    def __init__(self, url):
        threading.Thread.__init__(self)
        self.url = url
    def run(self):
        html = get_html(self.url)
        parse_html(html)

在上面的代码中,咱们首要界说了一个GetHtmlThread类,继承自threading.Thread类,然后在类的结构函数中传入需求爬取的URL。在run办法中,咱们调用get_html函数获取HTML代码,并将其传入parse_html函数中进行解析。

接下来,咱们经过循环创立多个线程来进行爬取:

def main():
    urls = ['https://movie.douban.com/top250?start={}'.format(i) for i in range(0, 250, 25)]
    threads = []
    for url in urls:
        thread = GetHtmlThread(url)
        thread.start()
        threads.append(thread)
    for thread in threads:
        thread.join()

在上面的代码中,咱们首要界说了一个urls列表,包含了一切需求爬取的URL。然后经过循环创立多个GetHtmlThread线程,并将其加入到threads列表中。最终,经过循环调用join办法等候一切线程履行结束。

经过多线程的办法,咱们能够充分利用计算机的功能,进步爬取功率。

  1. 线程池爬取

在多线程的办法中,咱们需求手动创立和办理线程,这样会增加代码的复杂度。因而,咱们能够运用Python中的线程池来进行优化。

首要,咱们需求导入Python中的concurrent.futures库:

import concurrent.futures

然后,咱们将获取HTML代码的代码放在一个函数中,并将其作为一个使命来提交给线程池:

def get_html(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        else:
            return None
    except Exception as e:
        print(e)
def parse_html(html):
    soup = BeautifulSoup(html, 'lxml')
    movie_list = soup.find(class_='grid_view').find_all('li')
    for movie in movie_list:
        title = movie.find(class_='title').string
        rating = movie.find(class_='rating_num').string
        print(title, rating)
def main():
    urls = ['https://movie.douban.com/top250?start={}'.format(i) for i in range(0, 250, 25)]
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(get_html, url) for url in urls]
    for future in concurrent.futures.as_completed(futures):
        html = future.result()
        parse_html(html)

在上面的代码中,咱们首要界说了一个urls列表,包含了一切需求爬取的URL。然后经过with句子创立一个线程池,并设置最大线程数为5。接下来,咱们经过循环将每个URL提交给线程池,并将返回的Future对象加入到futures列表中。最终,经过concurrent.futures.as_completed函数来等候一切使命履行结束,并获取返回值进行解析。

经过线程池的办法,咱们能够更加简练地完成多线程爬取,并且能够更加灵活地操控线程的数量,避免线程过多导致系统负载过高的问题。