鱼C论坛

 找回密码
 立即注册
查看: 1282|回复: 3

[已解决]多线程提升运行速度但是不明显

[复制链接]
发表于 2023-9-24 17:02:33 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
大佬们,我在使用线程池时出现了点问题。其实函数整体分为module_1, module_2两个部分。module1是进行广度遍历并爬取,module2是进行网页提取内容并且去除内容相似度高的网页。我这里使用了多线程,但是速度特别慢。虽然可能代码有点多,但是整体主干就那么点。大家有想法的话,请给我提一下哈,谢谢!



import json
import re
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from queue import Queue
from urllib.parse import urljoin
import jieba
import requests
from bs4 import BeautifulSoup
from simhash import Simhash
from spellchecker import SpellChecker

lock = threading.Lock()
queue = Queue()


def load_stopwords(file_path):
    with open(file_path, 'r', encoding='utf-8-sig') as file:
        stop_words = [line.strip() for line in file]
    return stop_words


stop_words = load_stopwords('A:/搜索引擎系统/停用词表.txt')


def to_text_html(num, url, html):
    # with lock:
    try:
        html = html.replace("\n", "")
        with open(f'A:/搜索引擎系统/all_information/web{num}.txt', 'w', encoding='utf-8-sig') as file:
            s = str(num) + '\n' + url + '\n' + html
            file.write(s)
    except requests.exceptions.RequestException as e:
        # 其他请求异常,打印错误信息
        print(f"请求 {url} 出现错误: {e}")
        return


def to_text_KeyInfor(num, current_url, title, content, links):
    # with lock:
    data = {
        "id": num,
        "url": current_url,
        "title": title,
        "content": content,
        "links": links
    }
    with open(f'A:/搜索引擎系统/get_title_content_links/web{num}.txt', 'w', encoding='utf-8-sig') as file:
        json.dump(data, file, ensure_ascii=False)


# 获取单个网页所有超链接
def get_all_hrefs(text: str, current_url):
    all_href = []
    text.encode('utf-8-sig')
    # 使用BeautifulSoup来解析网页源码
    soup = BeautifulSoup(text, 'lxml')
    # 获取所有a标签下的所有href属性
    all_a_tags = soup.find_all('a')
    for a_tag in all_a_tags:
        href = a_tag.get('href')
        if href and not href.startswith(('#', 'javascript:')) and not href.endswith(('.pdf', '.mp4', '.apk')):
            # 将爬取到的超链接由相对路径改为绝对路径
            absolute_url = urljoin(current_url, href)
            all_href.append(absolute_url)
    # 将得到的链接返回
    return all_href


def calculate_hamming_similarity(hash1, hash2):
    similarity = 1 - bin(hash1 ^ hash2).count('1') / 64
    return similarity


def compare_hash(content):
    new_hash = Simhash(content).value

    if len(processed_hashes) == 0:
        processed_hashes.append(new_hash)
        return True

    for pro_hash in processed_hashes:
        similarity = calculate_hamming_similarity(pro_hash, new_hash)
        if similarity > 0.7:
            return False

    processed_hashes.append(new_hash)
    return True


def is_chinese(word):
    for char in word:
        if '\u4e00' <= char <= '\u9fff':  # 中文字符的Unicode范围
            return True
    return False


def remove_gibberish_words(words):
    spell = SpellChecker()
    filtered_words = []
    for word in words:
        if not is_chinese(word):  # 检查单词是否为中文
            if spell.known([word]):  # 对于非中文单词进行拼写检查
                filtered_words.append(word)
        else:
            filtered_words.append(word)
    return filtered_words


def Separate_words(content):
    # 使用正则表达式的 sub 方法将标点符号替换为空格
    content = re.sub("[^\w\s]+", "", content)
    content = list(jieba.cut(content, cut_all=False, use_paddle=True))
    content = remove_gibberish_words(content)
    content = [element for element in content if element.strip() != '']
    content = [word for word in content if word.lower() not in stop_words]

    return content


def get_title_and_content(current_url, html):
    soup = BeautifulSoup(html, 'html.parser')

    title = []
    if soup.find('title') != None:
        title = soup.find('title').get_text()

    content = soup.get_text()
    content = content.replace('\n', '')

    links = get_all_hrefs(html, current_url)
    return title, content, links


def module_2(current_url, res_text):
    num = len(result) + 1
    url = current_url
    html = res_text

    title, content, links = get_title_and_content(url, html)
    content = Separate_words(content)

    if compare_hash(content):
        to_text_KeyInfor(num, current_url, title, content, links)
        return True

    return False


def module_1():
    # 获取队列头元素
    current_url = queue.get()
    # 设置异常捕获器,避免异常终止程序
    try:
        # 发送请求,并设置超时时间为1秒
        response = requests.get(url=current_url, headers=headers, timeout=1)
        if not response.ok:
            return
        response.encoding = 'utf-8'
    except requests.exceptions.RequestException as e:
        # 请求异常,打印错误信息
        print(f"请求 {current_url} 出现错误: {e}")
        return
    # 对共享列表进行增添操作时,一定要上锁避免并发问题
    with lock:
        if len(result) >= target:
            return
        res_text = response.text

        # 查重和分词操作
        if module_2(current_url, res_text) == False:
            return

        result.append(current_url)
        # 将结果写入文件
        to_text_html(len(result), current_url, response.text)
        print(f'{len(result)}. href = {current_url}')

    # 获取一个网页中的所有超链接
    all_hrefs = get_all_hrefs(res_text, current_url)
    # 上锁,避免线程安全问题
    with lock:
        for href in all_hrefs:
            # 将https替换为http避免重复
            new_url = href.replace("https", "http")
            if new_url not in visited:
                queue.put(new_url)
                visited.add(new_url)


if __name__ == '__main__':
    # 计时
    start_time = time.time()
    start_url = 'https://baike.baidu.com/'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.82'
    }

    # 设定爬取数量
    target = 480
    # visited为浏览过的网站
    visited = set()
    # 存储已处理网页的 Simhash 值
    processed_hashes = list()
    # result接收最后的结果
    result = []

    queue.put(start_url)
    visited.add(start_url)

    # 创建线程池,提高速度
    with ThreadPoolExecutor(48) as pool:
        while len(result) < target:
            # 执行主函数
            pool.submit(module_1)
            # module_1()
            # 通过在任务执行完毕后使用time.sleep(),可以让线程在暂停的时间内进行休眠,这样其他线程就有机会执行任务,提高了整体的并发效率。
            time.sleep(0.1)
    # 将线程池关闭,并且取消未完成的请求
    pool.shutdown(cancel_futures=True)

    # while len(result) < target:
    #     module_1()

    end_time = time.time()
    print(end_time - start_time)
最佳答案
2023-9-24 17:03:09
问题分析:

根据代码,你的程序主要分为两个模块:module_1和module_2。module_1负责爬取网页并进行处理,module_2负责提取网页内容并去除相似度高的网页。

在多线程的实现中,你使用了线程池ThreadPoolExecutor来管理线程,并通过submit方法提交任务。但是你发现,多线程并没有明显提升程序的运行速度。

问题原因和解决方案:

1. IO密集型任务:根据代码,你的任务主要是请求网页并获取响应。这属于IO密集型任务,即主要消耗时间在等待网络请求的响应上。多线程并不能提供明显的性能提升,因为多线程在等待网络响应时仍然会阻塞,无法同时进行其他任务。

   解决方案:考虑使用异步编程框架,如asyncio库,结合异步的网络请求库(如aiohttp)来提高程序的性能。异步编程可以更好地利用CPU资源,在等待IO操作时可以切换到其他任务,从而提高程序的并发性能。

2. 任务调度与线程数量:你的代码中使用了48个线程进行任务的调度和执行。线程并不是越多越好,过多的线程会导致线程切换开销增加,反而降低程序的性能。

   解决方案:根据实际情况调整线程数量。可以尝试逐步增加线程数量,找到最优的线程数量。同时,可以通过合理的任务调度策略来提高程序的并发性能。

3. 其他优化措施:除了上述两点,还可以考虑以下优化措施:

   - 使用连接池:建议使用连接池来管理网络请求的连接,可以提高网络请求的效率。
   - 减少锁的使用:锁的使用会导致线程串行执行,降低并发性能。可以尝试减少锁的使用,或者使用更细粒度的锁来提高并发性能。
   - 优化算法和数据结构:对于一些耗时较长的操作,可以考虑优化算法和数据结构,减少时间复杂度,提高程序性能。

希望以上分析和解决方案对你有帮助!如果还有其他问题,请随时提问。

球一个最佳答案谢谢啦!这对我非常重要!
回答可能有误,多多谅解!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-9-24 17:02:37 | 显示全部楼层

回帖奖励 +5 鱼币

可能是因为线程池的大小不够,导致线程之间的竞争过于激烈,从而降低了程序的运行效率。可以尝试增加线程池的大小,或者根据实际情况调整线程池的大小,以达到最佳的运行效率。同时,也可以考虑使用异步编程的方式,如asyncio库,来提高程序的运行效率。此外,还可以通过使用一些高效的第三方库,如lxml和requests-html,来加速网页解析和请求的速度。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-9-24 17:02:52 | 显示全部楼层
从你提供的代码中,我们可以看到你使用了多线程来提高程序的运行速度。然而,你可能没有达到预期的速度提升,这可能是由于以下原因:

1. GIL限制:Python中的全局解释器锁(GIL)会限制同一时间只能执行一个线程的Python字节码。这意味着即使你使用了多线程,但在CPU密集型任务中,并发的线程可能不会同时执行,从而无法充分利用多核处理器的优势。

2. IO阻塞:如果你的程序主要是IO密集型任务(如网络请求、文件读写等),那么多线程可以提高并发性能。然而,如果遇到网络延迟或IO阻塞的情况,多线程并不一定能明显提升速度,因为线程仍然需要等待IO操作完成。

针对你的问题,我有几点建议:

1. 考虑使用多进程:既然你的程序主要目标是提高速度,那么可以考虑使用多进程来替代多线程。多进程可以充分利用多核处理器的优势,并且避免了GIL的限制。你可以使用Python的multiprocessing模块来实现多进程。

2. 优化网络请求:网络请求是IO密集型任务之一,你可以尝试使用异步IO(如asyncio库)或者更高效的网络请求库(如aiohttp)来提高网络请求的并发性能。

3. 检查模块2的性能:你可以检查一下module_2函数的性能,特别是其中的相似度计算和文本处理部分。如果这部分代码效率较低,可能会导致整个程序变慢。你可以尝试优化这部分代码,例如使用更高效的相似度计算算法或者减少不必要的文本处理操作。

希望以上建议对你有所帮助!如果有任何进一步的问题,请随时提问。
如果问题已经解决,请设置最佳答案
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-9-24 17:03:09 | 显示全部楼层    本楼为最佳答案   
问题分析:

根据代码,你的程序主要分为两个模块:module_1和module_2。module_1负责爬取网页并进行处理,module_2负责提取网页内容并去除相似度高的网页。

在多线程的实现中,你使用了线程池ThreadPoolExecutor来管理线程,并通过submit方法提交任务。但是你发现,多线程并没有明显提升程序的运行速度。

问题原因和解决方案:

1. IO密集型任务:根据代码,你的任务主要是请求网页并获取响应。这属于IO密集型任务,即主要消耗时间在等待网络请求的响应上。多线程并不能提供明显的性能提升,因为多线程在等待网络响应时仍然会阻塞,无法同时进行其他任务。

   解决方案:考虑使用异步编程框架,如asyncio库,结合异步的网络请求库(如aiohttp)来提高程序的性能。异步编程可以更好地利用CPU资源,在等待IO操作时可以切换到其他任务,从而提高程序的并发性能。

2. 任务调度与线程数量:你的代码中使用了48个线程进行任务的调度和执行。线程并不是越多越好,过多的线程会导致线程切换开销增加,反而降低程序的性能。

   解决方案:根据实际情况调整线程数量。可以尝试逐步增加线程数量,找到最优的线程数量。同时,可以通过合理的任务调度策略来提高程序的并发性能。

3. 其他优化措施:除了上述两点,还可以考虑以下优化措施:

   - 使用连接池:建议使用连接池来管理网络请求的连接,可以提高网络请求的效率。
   - 减少锁的使用:锁的使用会导致线程串行执行,降低并发性能。可以尝试减少锁的使用,或者使用更细粒度的锁来提高并发性能。
   - 优化算法和数据结构:对于一些耗时较长的操作,可以考虑优化算法和数据结构,减少时间复杂度,提高程序性能。

希望以上分析和解决方案对你有帮助!如果还有其他问题,请随时提问。

球一个最佳答案谢谢啦!这对我非常重要!
回答可能有误,多多谅解!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-12-24 08:21

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表