多处理时,Web scraper会静默挂起

时间:2017-08-30 22:10:54

标签: python multiprocessing

我正在抓取一个包含几十个基本网址的网站,最终链接到我解析的几千个xml页面,变成一个Pandas数据帧,最后保存到SQLite数据库。我对下载/解析阶段进行了多处理以节省时间,但是脚本在一定数量的页面之后静默挂起(停止收集页面或解析XML)(不确定多少;在100到200之间)。

使用相同的解析器但按顺序执行所有操作(没有多处理)不会产生任何问题,所以我怀疑我在多处理方面做错了。也许创建Parse_url类的太多实例并堵塞内存?

以下是该流程的概述:

engine = create_engine('sqlite:///path_to_db')  # sqlalchemy

class Parse_url():
    def __init__(self, url):
        self.url = url 
    def __enter__(self):
        return self 
    def __exit__(self, exc_type, exc_value, traceback):
        return True
    def parse(self):
        # parse xml, return dataframes

def collect_xml_links(start_url):
    # collect and return a list of links to XML pages on this starting URL

def parse_urls(url):
    with Parse_url(url) as parser:
        collection_of_dfs = parser.parse()
    return collection_of_dfs

def write_info_to_sql(result, db_name, engine):
    # write info to SQLite database

start_urls = [url1, url2, url3, ... ] 
with Pool(4) as pool:
    results = pool.map(collect_xml_links, start_urls)  
for urls in results:
    url_list.extend(urls)  # This works and returns urls

for i in range(0, len(url_list), 50): # Chunks of 50 to report progress
    url_list_slice = url_list[i:i+50]
    with Pool(4) as pool:
        results = pool.map(parse_urls, url_list_slice) 
    for result in results: 
        write_info_to_sql(result, db_name, engine)

当脚本挂起时,它似乎总是在刮取相似数量的页面时这样做,但我不确定它是否完全相同。杀死脚本会产生一个无用的追踪,指向results = pool.map(parse_urls, url_list_slice)行。

我的多处理设置是否存在明显问题?我是否有可能生成过多的Parse_url类实例?

2 个答案:

答案 0 :(得分:1)

在第二个循环中,您在每次迭代时创建Pool,这不是理想的。 Python gc非常懒惰,所以你的软件会在迭代过程中积累大量资源。

multiprocessing.Pool专为重复使用而设计,因此您只需在脚本中创建一次。

with Pool(4) as pool:
    results = pool.map(collect_xml_links, start_urls)  
    for urls in results:
        url_list.extend(urls)  # This works and returns urls

    for i in range(0, len(url_list), 50): # Chunks of 50 to report progress
        url_list_slice = url_list[i:i+50]
            results = pool.map(parse_urls, url_list_slice) 
            for result in results: 
                write_info_to_sql(result, db_name, engine)

答案 1 :(得分:0)

非常确定这不是理想的,但它确实有效。假设问题是多进程创建了太多对象,我添加了一个明确的" del"像这样的步骤:

for i in range(0, len(url_list), 50): # Chunks of 50 to report progress
url_list_slice = url_list[i:i+50]
with Pool(4) as pool:
    results = pool.map(parse_urls, url_list_slice) 
for result in results: 
    write_info_to_sql(result, db_name, engine)
    del(result)  # explicitly delete the dataframes when done with them

我不确定为什么这些对象会持久存在,因为它似乎没有任何引用留给它们所以它们应该被垃圾收集。我尝试了其他一些方法来删除对它们的引用,例如

results = []

在批处理全部写完之后,但仍然有人使用它。