nofollow属性的介绍和使用(简单记录)

本文详细介绍了nofollow属性的作用及其在SEO中的应用。通过设置此属性可以阻止搜索引擎将特定链接计入反向链接,有效防止垃圾链接影响网站排名。同时,文章还探讨了如何合理利用nofollow属性优化内部链接结构。

nofollow属性的介绍和使用

nofollow 是什么意思?

我们知道,反向链接是Google PR值的主要计算指标。为了添加反向链接,SEO作弊者在论坛和博客等大量发布带无关链接的内容。这些垃圾链接的存在给搜索引擎对网页质量的评估造成一定程度的麻烦。nofollow 正是Google、Yahoo、MSN 为了应对垃圾链接(Spam)引入的一个属性,此属性目前应该被广泛采用。

如果A网页上有一个链接指向B网页,但A网页给这个链接加上了 rel="nofollow" 标注,则搜索引擎不把A网页计算入B网页的反向链接。

nofollow 怎么用?

如某博客上有垃圾评论:

<a href="http://www.example.com/">顶顶</a>

以下操作,即进行了 nofollow:

<a href="http://www.example.com/"  rel="nofollow" >顶顶</a>

搜索引擎在计算PR值时,不会把这个链接计算在内。

关于 nofollow:

  • 目前绝大多数的博客和建站系统都加上了nofollow 属性。也就是说,在其他人的博客上留链接以获得PR值的做法是徒劳的。(特别是名人的博客,基本都用了nofollow)
  • 如果你担心网站被垃圾链接侵扰,nofollow是一个非常好的“拉圾链接防火墙”。
  • nofollow 并不代表搜索引擎不收录那个链接,只是不计入反向链接而已。

以下这一点对 SEOer非常重要。

你可以为站点的内部链接添加 nofollow。比如:联系我们、关于我们、隐私保护、公司简介、网站后台等的链接可以把它们nofollow 掉,这样做,首页的PR值就不会传输给他们。因为,他们有太多的PR值显然没用。


import os import shutil from datetime import datetime, timedelta import concurrent.futures import time def get_recent_files(directory, days=30, executor=None): """优化版:获取目录中最近N天修改过的文件(支持多线程)""" cutoff = datetime.now() - timedelta(days=days) recent_files = {} dir_prefix_len = len(directory) + 1 # 预设前缀长度 def scan_dir(current_dir): """扫描单个目录""" local_files = {} try: with os.scandir(current_dir) as entries: for entry in entries: try: if entry.is_dir(follow_symlinks=False): # 如果是目录,提交新任务 if executor: executor.submit(scan_dir, entry.path) else: # 获取文件修改时间(使用entry自带的stat缓存) mtime = datetime.fromtimestamp(entry.stat().st_mtime) if mtime >= cutoff: # 直接计算相对路径(避免多次调用os.path.relpath) rel_path = entry.path[dir_prefix_len:] local_files[rel_path] = entry.path except (OSError, PermissionError): continue except (OSError, PermissionError): pass return local_files # 使用线程池并行扫描 with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor: # 提交根目录扫描任务 future = executor.submit(scan_dir, directory) # 收集所有结果 for future in concurrent.futures.as_completed(executor._pending_work_items): recent_files.update(future.result()) return recent_files def copy_with_directory_structure(src, dst): """拷贝文件并保持目录结构(优化版)""" os.makedirs(os.path.dirname(dst), exist_ok=True) shutil.copy2(src, dst) def compare_and_copy(a_dir, b_dir, c_dir, days=5): """主函数:优化后的比较拷贝""" print(f"扫描近期修改文件({datetime.now():%H:%M:%S})...") # 并行扫描两个目录 start_scan = time.time() with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: future_a = executor.submit(get_recent_files, a_dir, days) future_b = executor.submit(get_recent_files, b_dir, days) a_files = future_a.result() b_files = future_b.result() scan_time = time.time() - start_scan print(f"扫描完成! 用时: {scan_time:.2f}秒 | A目录文件数: {len(a_files)} | B目录文件数: {len(b_files)}") # 比较文件差异 print("文件名差异对比...") diff_files = [] for rel_path, a_path in a_files.items(): if rel_path not in b_files: dst_path = os.path.join(c_dir, rel_path) diff_files.append((a_path, dst_path)) # 并行拷贝差异文件 print(f"开始拷贝 {len(diff_files)} 个差异文件...") copy_start = time.time() with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: futures = [executor.submit(copy_with_directory_structure, src, dst) for src, dst in diff_files] # 显示拷贝进度 for i, future in enumerate(concurrent.futures.as_completed(futures)): if i % 100 == 0: print(f"拷贝进度: {i + 1}/{len(diff_files)}") copy_time = time.time() - copy_start print(f"拷贝完成! 用时: {copy_time:.2f}秒 | 平均速率: {len(diff_files) / copy_time:.1f}文件/秒") return len(diff_files) if __name__ == "__main__": # 路径配置(根据实际需要修改) # #1缸体 A_folder = r"\\192.168.190.241\csv" B_folder = r"\\192.168.10.119\sharecsv\ThreeD\#1\GT\output" C_folder = r"\\192.168.10.119\sharecsv\ThreeD\#1\GT" # #2缸体 D_folder = r"\\192.168.190.242\Report\CSV2" E_folder = r"\\192.168.10.119\sharecsv\ThreeD\#2\GT\output" F_folder = r"\\192.168.10.119\sharecsv\ThreeD\#2\GT" # #1缸盖 G_folder = r"\\192.168.190.189\csv" H_folder = r"\\192.168.10.119\sharecsv\ThreeD\#1\GG\output" I_folder = r"\\192.168.10.119\sharecsv\ThreeD\#1\GG" # #2缸盖 J_folder = r"\\192.168.190.116\csv2" K_folder = r"\\192.168.10.119\sharecsv\ThreeD\#2\GG\output" L_folder = r"\\192.168.10.119\sharecsv\ThreeD\#2\GG" # #1曲轴 M_folder = r"\\192.168.190.169\Bigdata" N_folder = r"\\192.168.10.119\sharecsv\ThreeD\#1\QZ\output" O_folder = r"\\192.168.10.119\sharecsv\ThreeD\#1\QZ" # #2曲轴 P_folder = r"\\192.168.190.170\tmc\Bigdata" Q_folder = r"\\192.168.10.119\sharecsv\ThreeD\#2\QZ\output" R_folder = r"\\192.168.10.119\sharecsv\ThreeD\#2\QZ" print("===== 开始#1缸体比较拷贝操作 =====") start_time = datetime.now() copied_count1 = compare_and_copy(A_folder, B_folder, C_folder) print(f"操作#1缸体完成! 共拷贝 {copied_count1} 个文件\n") print("===== 开始#2缸体比较拷贝操作 =====") copied_count2 = compare_and_copy(D_folder, E_folder, F_folder) print(f"操作#2缸体完成! 共拷贝 {copied_count2} 个文件") print("===== 开始#1缸盖比较拷贝操作 =====") copied_count3 = compare_and_copy(G_folder, H_folder, I_folder) print(f"操作#1缸盖完成! 共拷贝 {copied_count3} 个文件\n") print("===== 开始#2缸盖比较拷贝操作 =====") copied_count4 = compare_and_copy(J_folder, K_folder, L_folder) print(f"操作#2缸盖完成! 共拷贝 {copied_count4} 个文件") print("===== 开始#1曲轴比较拷贝操作 =====") copied_count5 = compare_and_copy(M_folder, N_folder, O_folder) print(f"操作#1曲轴完成! 共拷贝 {copied_count5} 个文件\n") print("===== 开始#2曲轴比较拷贝操作 =====") copied_count6 = compare_and_copy(P_folder, Q_folder, R_folder) print(f"操作#2曲轴完成! 共拷贝 {copied_count6} 个文件") elapsed = (datetime.now() - start_time).total_seconds() print(f"耗时 {elapsed6:.2f} 秒") AttributeError: 'ThreadPoolExecutor' object has no attribute '_pending_work_items'
最新发布
10-11
<think>我们遇到了一个错误:AttributeError: 'ThreadPoolExecutor' object has no attribute '_pending_work_items' 在函数get_recent_files中,我们尝试遍历executor._pending_work_items,但是concurrent.futures.ThreadPoolExecutor并没有公开这个属性。 实际上,_pending_work_items是一个内部属性,不应该直接使用。 我们需要修改收集结果的方式。原本的意图是等待所有提交的任务完成,并收集它们的结果。但是,我们只提交了根目录的任务,而根目录的任务会递归提交子目录的任务。 因此,我们需要管理所有提交的任务,并等待它们完成。 修改方案: 1. 在get_recent_files函数中,我们不应该使用executor._pending_work_items,而是应该收集所有提交的future对象。 2. 我们可以创建一个列表来保存所有提交的future(包括根目录递归提交的子目录任务)。 3. 然后使用concurrent.futures.as_completed或者concurrent.futures.wait来等待所有任务完成。 但是注意:在递归函数scan_dir中,我们提交了子目录的任务,但是这些任务并没有被收集到一个全局的列表中。因此,我们需要修改scan_dir函数,使其能够将新提交的任务添加到某个集合中。 另一种做法:我们不使用递归提交,而是使用一个任务队列,由线程池从队列中取任务执行。但是这样改动较大。 我们可以在函数内部创建一个列表来收集所有future,并将这个列表作为参数传递给scan_dir。但是这样需要修改scan_dir的参数,而且由于递归并行,需要线程安全地操作这个列表。 或者,我们可以利用ThreadPoolExecutor的上下文管理器,在退出时等待所有任务完成,但是这样我们无法在退出前收集所有结果。 因此,我们修改如下: 在get_recent_files函数中,我们创建一个全局的future列表(在函数内部),然后修改scan_dir函数,使其能够将新提交的future添加到这个列表中。同时,我们需要保证线程安全(因为多个线程可能同时追加future)。 但是,我们也可以使用一个线程安全的队列来收集future?或者使用一个列表,并用锁保护。 然而,为了简化,我们可以使用一个非递归的方式,即使用队列来管理需要扫描的目录,然后每个工作线程从队列中获取目录并扫描,遇到子目录则放入队列。这样我们就可以在一个循环中提交任务,直到队列为空。 由于代码已经比较复杂,我们不妨重写get_recent_files函数,改为非递归的广度优先扫描,并使用队列来管理目录。 但是,考虑到时间,我们可以先尝试修复当前递归方式,通过收集所有future的办法。 修改步骤: 1. 在get_recent_files函数中,我们创建一个列表all_futures,并使用一个锁(因为多个线程会同时添加future)。 2. 修改scan_dir函数,使其能够接收all_futureslock(或者使用闭包?)以及executor。 3. 在扫描目录时,如果遇到子目录,则提交一个新的scan_dir任务,并将返回的future添加到all_futures中(需要加锁)。 4. 主线程在提交根目录任务后,需要等待all_futures中所有任务完成。 但是,注意:根目录的任务本身也会提交子任务,而子任务可能又会提交更多任务。所以我们需要等待所有任务完成。 我们可以这样: 首先,提交根目录任务,并将这个future加入all_futures。 然后,在一个循环中,等待all_futures中的任务完成,每当一个任务完成,我们就获取它的结果(即该目录下的文件)并合并到recent_files中。 但是,在任务执行过程中,又会向all_futures添加新的任务(因为遇到子目录)。所以我们需要动态地添加。 这实际上就是使用as_completed,但是as_completed只能等待一个可迭代集合完成。而我们一边在添加新的future,一边在等待。 所以,我们可以使用一个while循环,直到all_futures为空,同时我们记录已经完成的任务数量。 但是这样的逻辑比较复杂。 另一种简单做法:使用一个共享的队列来存放需要扫描的目录,然后启动固定数量的工作线程,每个工作线程从队列中取出目录进行扫描,遇到子目录则放入队列。当所有工作线程都空闲且队列为空时,我们就结束。 由于我们已经在使用线程池,我们可以将扫描任务设计为:从队列中取目录,扫描后将子目录放入队列。但是这样需要自己管理队列线程池的交互。 实际上,concurrent.futures.ThreadPoolExecutor并不直接支持动态任务队列。但我们可以使用一个Queue来存放目录,然后初始化时把根目录放入队列,然后提交固定数量的工作函数(每个工作函数循环从队列中取目录,扫描,遇到子目录就放入队列,直到遇到一个结束信号)。 这样我们需要管理队列的结束,比如放入None作为结束信号,并且每个工作线程在收到None后退出。 考虑到时间,我们选择重构为使用队列工作线程的方式。 但是,为了快速修复,我们也可以放弃递归提交,而是使用一个队列,在同一个线程池中处理所有目录扫描任务。 下面提供一个使用队列的版本: 步骤: 1. 创建一个队列(线程安全的queue.Queue),并将根目录放入队列。 2. 创建一个锁,用于保护recent_files字典(因为多个线程会同时更新它)。 3. 创建一个线程池,并提交多个工作线程(每个工作线程执行一个函数,该函数循环从队列中取出目录进行扫描)。 4. 扫描每个目录时,对于每个文件,如果是文件则判断并添加到recent_files;如果是目录,则将该目录放入队列。 5. 当队列为空并且所有工作线程都处于空闲状态时,结束。 但是,我们需要知道什么时候所有的工作都完成了。我们可以使用一个计数器(记录正在处理的任务数)一个事件? 其实,我们可以使用queue.join()每个任务完成后调用task_done(),然后主线程等待队列join即可。 修改后的get_recent_files函数(非递归,使用队列): 由于代码较长,我们重写一个版本。 但是,我们也可以使用现有的线程池,并让工作线程在扫描到子目录时向队列中添加任务,然后主线程等待队列被清空。 我们采用以下步骤: 1. 创建队列(存放待扫描的目录)。 2. 创建线程池。 3. 创建锁,用于更新recent_files字典。 4. 将根目录放入队列。 5. 定义工作函数:从队列中取出目录,扫描该目录(文件子目录),对于文件,判断修改时间并添加到结果字典;对于子目录,将子目录路径放入队列。处理完一个目录后,调用queue.task_done()。 6. 提交多个工作线程(每个工作线程执行工作函数)。 7. 主线程等待队列为空(使用queue.join())。 注意:工作函数应该是一个循环,直到队列为空(实际上,我们使用queue.join()来等待,但是工作线程需要处理异常)。 由于我们不知道有多少个目录,所以我们可以提交固定数量的工作线程(例如8个),然后每个工作线程循环从队列中取目录直到遇到None(作为结束信号)?或者使用queue.join()配合task_done()。 我们使用以下模式: from queue import Queue from threading import Thread q = Queue() # 将根目录放入队列 q.put(start_directory) def worker(): while True: dir_path = q.get() if dir_path is None: # 结束信号 q.task_done() break try: with os.scandir(dir_path) as entries: for entry in entries: if entry.is_dir(follow_symlinks=False): # 将子目录放入队列 q.put(entry.path) else: # 处理文件 mtime = ... if mtime >= cutoff: rel_path = ... with lock: recent_files[rel_path] = entry.path except Exception: pass q.task_done() # 启动工作线程 threads = [] for i in range(8): t = Thread(target=worker) t.start() threads.append(t) # 等待队列为空 q.join() # 停止工作线程 for i in range(8): q.put(None) for t in threads: t.join() 但是,我们使用的是线程池(concurrent.futures.ThreadPoolExecutor),我们可以不用自己管理线程,而是将每个目录的扫描任务提交给线程池,但这样又会递归提交任务(每个目录一个任务)。然后我们等待所有任务完成。 我们回到最初的想法:使用future列表,并动态添加。但是,我们之前已经有一个线程池,我们可以这样: with ThreadPoolExecutor(max_workers=8) as executor: # 创建future列表 all_futures = set() # 用于收集结果的字典 recent_files = {} # 锁,用于更新recent_files lock = threading.Lock() # 定义任务函数(扫描一个目录) def scan_dir(dir_path): try: with os.scandir(dir_path) as entries: for entry in entries: if entry.is_dir(follow_symlinks=False): # 提交新的任务 future = executor.submit(scan_dir, entry.path) all_futures.add(future) else: mtime = datetime.fromtimestamp(entry.stat().st_mtime) if mtime >= cutoff: rel_path = entry.path[dir_prefix_len:] with lock: recent_files[rel_path] = entry.path except Exception: pass # 提交根目录任务 root_future = executor.submit(scan_dir, directory) all_futures.add(root_future) # 等待所有任务完成 # 注意:scan_dir函数中会动态添加新的future,所以我们需要循环等待,直到all_futures为空?不行,因为任务还在提交。 # 所以我们可以使用一个循环:每次从all_futures中取一个完成的,然后移除,直到all_futures为空。但是这样可能会阻塞,因为可能还有任务没提交。 # 因此,我们改为:先等待当前所有futures完成,然后查看是否还有新任务。但是这样不可靠。 这个方案比较复杂,而且容易出错。 鉴于时间,我建议使用队列(Queue)普通线程的方式(而不是ThreadPoolExecutor)来重写get_recent_files函数。 但是,为了保持代码风格一致,我们可以使用ThreadPoolExecutor来提交多个工作函数,每个工作函数负责从队列中取目录并扫描,直到队列为空。 然而,ThreadPoolExecutor并不直接支持任务队列模式。所以,我们采用自己创建线程的方式。 由于代码较长,我们决定采用queue.Queue普通线程的方式。 修改后的get_recent_files函数(使用队列多线程): 注意:我们需要在函数内部启动多个工作线程,然后等待队列处理完成。 代码如下: </think>您遇到的错误是`AttributeError: 'ThreadPoolExecutor' object has no attribute '_pending_work_items'`,这是因为在Python的`concurrent.futures`模块中,`_pending_work_items`是内部属性,不应直接访问。让我们修复这个问题并提供优化方案: ### 问题分析与修复方案 问题核心在于`get_recent_files`函数中错误地访问了线程池的内部属性。需要重构文件扫描逻辑,改用队列机制实现安全的并行目录遍历: ```python import os import shutil import threading from datetime import datetime, timedelta import concurrent.futures import time from queue import Queue def get_recent_files(directory, days=30): """优化版:获取目录中最近N天修改过的文件(使用队列+工作线程)""" cutoff = datetime.now() - timedelta(days=days) recent_files = {} dir_prefix_len = len(directory) + 1 lock = threading.Lock() queue = Queue() queue.put(directory) def worker(): """工作线程函数""" while True: try: current_dir = queue.get(timeout=1) # 带超时的获取 except: break try: with os.scandir(current_dir) as entries: for entry in entries: try: if entry.is_dir(follow_symlinks=False): queue.put(entry.path) else: mtime = datetime.fromtimestamp(entry.stat().st_mtime) if mtime >= cutoff: rel_path = entry.path[dir_prefix_len:] with lock: recent_files[rel_path] = entry.path except (OSError, PermissionError): continue except (OSError, PermissionError): pass finally: queue.task_done() # 创建工作线程池 workers = [] for _ in range(8): # 8个工作线程 t = threading.Thread(target=worker) t.daemon = True t.start() workers.append(t) # 等待所有队列任务完成 queue.join() return recent_files def copy_with_directory_structure(src, dst): """拷贝文件并保持目录结构""" os.makedirs(os.path.dirname(dst), exist_ok=True) shutil.copy2(src, dst) def compare_and_copy(a_dir, b_dir, c_dir, days=5): """主函数:优化后的比较拷贝""" print(f"扫描近期修改文件({datetime.now():%H:%M:%S})...") start_scan = time.time() # 使用线程池并行扫描两个目录 with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: future_a = executor.submit(get_recent_files, a_dir, days) future_b = executor.submit(get_recent_files, b_dir, days) a_files = future_a.result() b_files = future_b.result() scan_time = time.time() - start_scan print(f"扫描完成! 用时: {scan_time:.2f}秒 | A目录文件数: {len(a_files)} | B目录文件数: {len(b_files)}") # 比较文件差异 print("文件名差异对比...") diff_files = [] for rel_path, a_path in a_files.items(): if rel_path not in b_files: dst_path = os.path.join(c_dir, rel_path) diff_files.append((a_path, dst_path)) # 并行拷贝差异文件 print(f"开始拷贝 {len(diff_files)} 个差异文件...") copy_start = time.time() with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: futures = [executor.submit(copy_with_directory_structure, src, dst) for src, dst in diff_files] # 显示拷贝进度 for i, future in enumerate(concurrent.futures.as_completed(futures)): if i % 100 == 0: print(f"拷贝进度: {i + 1}/{len(diff_files)}") copy_time = time.time() - copy_start print(f"拷贝完成! 用时: {copy_time:.2f}秒 | 平均速率: {len(diff_files) / copy_time:.1f}文件/秒") return len(diff_files) # 主函数保持不变... ``` ### 关键修复说明 1. **线程安全队列实现**: - 使用`queue.Queue`管理待扫描目录 - 工作线程从队列获取目录进行处理 - 队列空时线程自动退出 2. **线程同步机制**: - 使用`threading.Lock`保护共享字典 - `queue.task_done()`跟踪任务完成状态 - `queue.join()`阻塞等待所有任务完成 3. **优化点**: - 避免递归提交任务导致的线程管理问题 - 更稳定的异常处理流程 - 减少路径计算次数(dir_prefix_len优化) 4. **资源管理**: - 设置工作线程为守护线程(daemon=True) - 添加队列获取超时防止死锁 - 使用with语句确保资源释放 ### 注意事项 1. 网络路径处理:确保所有路径都有访问权限 2. 大文件处理:如果文件较大,考虑增加拷贝缓冲区大小 3. 错误日志:建议添加详细错误日志记录 4. 内存优化:对于超大目录,可考虑分批处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值