2017-08-30 51 views
0

我在抓取一个网站,其中包含几十个基本网址,最终链接到我解析的数千个xml页面,并转化为Pandas数据框,最终保存到SQLite数据库。我多处理下载/解析阶段以节省时间,但脚本在一定数量的页面(不知道多少;介于100和200之间)后静静地挂起(停止收集页面或解析XML)。Web scraper在多处理时静静地挂起

使用相同的解析器,但一切顺序(没有多处理)没有给出任何问题,所以我怀疑我在做多处理错误。也许是创建了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类的太多实例?

+0

我没有看到任何地方定义'collect_xml_links'或'start_urls' ... – pstatix

+0

在哪里修复?仍然没有'def collect_xml_links'或可迭代的'start_urls'。 – pstatix

+0

猜测类实例正在积累,我在'write_info_to_sql(result,db_name,engine)'后面添加了 'del(result)'。这似乎已经解决了挂起,但我想知道是否有更好的解决方案 – iayork

回答

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) 
+0

Python GC只关注循环引用;资源通过引用计数立即清除*。此外,这些资源是在单独的流程中创建的,而不是主流程。 –

+0

我用错了词。我的意思是资源池(管道,线程,地图结果等)。快速循环创建和销毁Pools会导致资源泄漏,因为Pool内的线程会延长相关对象的生命周期。 'join'方法确保逻辑将等待所有资源被释放,但OP没有调用它。不过,推荐的方法是创建一个“Pool”并在整个软件生命周期中使用它。 – noxdafox

+0

为了什么是值得的,根据这个建议停止了挂起,并且刮板现在完成了〜5000个XML页面,正如我所看到的那样 – iayork

0

很确定这不是理想的,但它的工作。假设问题是多进程创造了太多的对象,我增加了一个明确的“德尔”的步骤是这样的:

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 = [] 

批次已全部写入后,但仍有挂起使用。