1

我经常需要批量下载和重命名 HTML 页面,并在不久前为它编写了这个简单的代码:

import shutil
import os
import sys
import socket

socket.setdefaulttimeout(5)   

file_read = open(my_file, "r")
lines = file_read.readlines()
for line in lines:
    try:
        import urllib.request 
        sl = line.strip().split(";")
        url = sl[0]
        newname = str(sl[1])+".html"
        urllib.request.urlretrieve(url, newname)
    except:
        pass
file_read.close()

这对于几百个网站来说效果很好,但是对于大量下载(20-50k)来说需要太长时间。加速它的最简单和最好的方法是什么?

4

1 回答 1

1

问:
“我经常不得不...加快速度
最简单最好的方法是什么?”

答:
简单(评论的方法不是)和最好
的方法是至少:(a)最小化所有开销(50k 次- 实例化成本是此类成本之一),(b)利用令人尴尬的独立性(然而, not a being a True- ) in process-flow (c)尽可能接近 just- ,latency-masked process-flow的出血边缘


Thread

[PARALLEL]

[CONCURRENT]

鉴于
简单性和性能似乎都是“最佳”的衡量标准:

任何成本,首先不能证明通过大幅提高性能来介绍自己的成本是合理的,其次,不能对性能产生额外的积极净效应(加速),都是性能反模式和不可原谅的计算机科学罪。

因此
,我无法推广使用 GIL 锁(按设计,甚至只是[CONCURRENT]阻止处理)绑定和性能令人窒息的逐步循环步进任意数量的 Python 线程一个接一个- after-another-...-re- [SERIAL]ised 大约100 [ms]-quanta 的代码解释时间链阻止了一个且只有一个这样的 Python 线程被允许运行(所有其他线程都被阻塞等待......而是一种性能反模式,不是吗?),
所以
宁愿选择基于进程的工作流并发(性能提升很多,大约 50k url-fetches,其中数百 / 数千 [ms] 延迟(协议和安全握手设置 + 远程 url-decode + 远程内容组装 + 远程内容到协议封装 + 远程到本地网络流 + 本地协议解码 + ...)。

草图流程框架:

from joblib import Parallel, delayed

MAX_WORKERs = ( n_CPU_cores - 1 )

def main( files_in ):
    """                                                     __doc__
    .INIT worker-processes, each with a split-scope of tasks
    """
    IDs = range( max( 1, MAX_WORKERs ) )
    RES_if_need = Parallel( n_jobs = MAX_WORKERs
                            )(       delayed( block_processor_FUN #-- fun CALLABLE
                                              )( my_file, #---------- fun PAR1
                                                 wPROC    #---------- fun PAR2
                                                 )
                                              for wPROC in IDs
                                     )

def block_processor_FUN( file_with_URLs = None,
                         file_from_PART = 0
                         ):
    """                                                     __doc__
    .OPEN file_with_URLs
    .READ file_from_PART, row-wise - till next part starts
                                   - ref. global MAX_WORKERs
    """
    ...

这是__main__生成足够多的工作进程的初始 Python 解释器端技巧,它开始my_file独立地爬取 URL-s 的“列表”,并且确实开始了工作流程,[CONCURENT]其中一个独立于任何其他工作流程。

通过block_processor_FUN()引用传递给工作人员的 , 确实简单地打开了文件,并开始仅获取/处理其“自己的”部分,即从( wPROC / MAX_WORKERs )( ( wPROC + 1 ) / MAX_WORKERs )它的行数。

就这么简单

如果愿意调整一些 URL 可能需要花费更长的极端情况,那么可能会改进负载平衡公平队列的形式,但代价是更复杂的设计(许多进程到进程的消息队列可用),有一个{ __main__ | main() }-side FQ/LB-feeder 并使工作进程从此类作业请求 FQ/LB-facility 检索他们的下一个任务。

更复杂和更健壮的 URL 服务持续时间分布不均匀“跨越”my_file要服务的 URL-s 的有序列表。

影响最终性能/稳健性的简单性/复杂性级别的选择是您的。

有关更多详细信息,您可能希望阅读内容和代码,以及进一步提高性能的示例或提示

于 2022-03-03T12:16:11.397 回答