Python线程的创建、执行和管理以及注意事项

2023-04-24 15:47:38 浏览数 (2)

亿牛云代理亿牛云代理

Python提供了多种方法来创建、执行和管理线程,并且需要注意线程安全性和性能方面的问题。其中使用threading模块创建线程,并获取其执行的函数返回值的方法有:

  1. 使用concurrent.futures模块:提供了高级API,可以将返回值和异常从工作线程传递到主线程。但可能比使用threading模块更耗费资源。
  2. 使用multiprocessing.pool模块:提供了类似的接口,可以使用进程或线程池,并使用apply_async方法异步地执行函数并获取结果。但需要序列化和传递数据,而且不能共享内存。
  3. 使用可变对象作为参数传递给线程的构造器,并让线程将其结果存储在该对象的指定位置。但可能会导致竞争条件。
  4. 使用Thread的子类:重写run和join方法,使得join方法可以返回目标函数的返回值。但需要访问一些私有的数据结构。

在选择方法时,需要考虑具体需求和场景。以下是需要注意的一些方面:

  1. concurrent.futures模块可以简化线程的创建和管理,但可能比使用threading模块更耗费资源。
  2. multiprocessing.pool模块可以利用多核处理器并行执行函数,但需要序列化和传递数据,而且不能共享内存。
  3. 使用可变对象作为参数传递给线程可能会导致竞争条件,即多个线程同时修改同一个对象,造成数据不一致或错误。
  4. 使用Thread的子类来返回目标函数的返回值可能会破坏Thread的原有设计,而且需要访问一些私有的数据结构。
  5. Python的线程受到全局解释器锁(GIL)的限制,即在任何时刻只有一个线程能够执行Python字节码,因此对于计算密集型的任务,线程并不能提高性能。
  6. Python的线程在执行I/O操作或其他阻塞调用时会释放GIL,因此对于I/O密集型的任务,线程可以提高性能。
  7. Python的线程需要注意线程安全性,即避免多个线程同时访问或修改共享的资源,否则可能会造成数据损坏或不一致。
  8. Python提供了一些工具来保证线程安全性,例如锁(Lock)、信号量(Semaphore)、定时器(Timer)和屏障(Barrier)等。

例如用”汽车”和“冰淇淋”作为关键词对B站进行搜索,将返回的视频标题进行采集整理并写入数据库,同时计算数据总量,以此进行热点事件分析,代码如下:

代码语言:python代码运行次数:0复制
# 导入所需的模块

import requests

import re

import sqlite3

import threading



# 定义一个函数,根据关键词和页码获取B站搜索结果页面的HTML内容

def get_html(keyword, page):

    # 构造请求头和参数

    headers = {

        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"

    }

    params = {

        "keyword": keyword,

        "page": page,

        "order": "totalrank"

    }



    #设置亿牛云爬虫代理加强版 代理IP的服务器地址、端口、用户名和密码

    proxyHost = "www.16yun.cn"

    proxyPort = "31111"



    # 代理验证信息

    proxyUser = "16YUN"

    proxyPass = "16IP"



    proxyMeta = "http://%(user)s:%(pass)s@%(host)s:%(port)s" % {

        "host" : proxyHost,

        "port" : proxyPort,

        "user" : proxyUser,

        "pass" : proxyPass,

    }



    # 设置 http和https访问都是用HTTP代理

    proxies = {

        "http"  : proxyMeta,

        "https" : proxyMeta,

    }

    

    # 发送GET请求,获取响应内容,使用代理IP和用户名密码

    response = requests.get("https://search.bilibili.com/all", headers=headers, params=params, proxies=proxies)

    # 返回HTML内容

    return response.text



# 定义一个函数,从HTML内容中提取视频标题,并将其写入数据库

def extract_and_save(html):

    # 连接数据库,创建游标

    conn = sqlite3.connect("bilibili.db")

    cursor = conn.cursor()

    # 创建数据表,如果已存在则忽略

    cursor.execute("CREATE TABLE IF NOT EXISTS videos (title TEXT)")

    # 从HTML内容中提取视频标题,使用正则表达式匹配

    titles = re.findall(r"<a title="(.*?)" href=", html)

    # 遍历每个标题,将其插入数据表中

    for title in titles:

        cursor.execute("INSERT INTO videos VALUES (?)", (title,))

    # 提交事务,关闭连接

    conn.commit()

    conn.close()



# 定义一个函数,计算数据库中的数据总量,并打印结果

def count_and_print():

    # 连接数据库,创建游标

    conn = sqlite3.connect("bilibili.db")

    cursor = conn.cursor()

    # 查询数据表中的记录数

    cursor.execute("SELECT COUNT(*) FROM videos")

    count = cursor.fetchone()[0]

    # 打印结果

    print(f"共采集了{count}条数据")

    # 关闭连接

    conn.close()



# 定义一个主函数,使用线程进行快速I/O操作

def main():

    # 定义关键词和页码范围

    keyword = "汽车 冰淇淋"

    pages = range(1, 11)

    # 创建一个空列表,用于存储线程对象

    threads = []

    # 遍历每个页码,创建一个线程对象,执行get_html和extract_and_save函数,并将其添加到列表中

    for page in pages:

        thread = threading.Thread(target=lambda: extract_and_save(get_html(keyword, page)))

        thread.start()

        threads.append(thread)

    # 等待所有线程结束

    for thread in threads:

        thread.join()

    # 调用count_and_print函数,计算并打印数据总量

    count_and_print()



# 调用主函数

if __name__ == "__main__":

    main()    

总体来说,这段代码使用了多线程技术,使用多个线程并发地访问B站的搜索结果页面,提取其中的视频标题,并将其写入数据库,将网络请求和数据库操作分别放到不同的线程中执行,从而实现了快速爬取和处理大量数据的目的。同时,该代码还使用了爬虫代理IP,提高了爬虫的稳定性和安全性。

0 人点赞