我对python很新(我主要用Java编写代码)。我有一个python脚本,它本质上是一个爬虫。它调用phantomjs,它加载页面,返回它的源,以及它在页面中找到的URL列表。
我一直在尝试使用Python 3的multiprocessing
模块来执行此操作,但我无法弄清楚如何使用工作人员也可以添加的共享队列。我不断得到不可预知的结果。
我以前的方法使用了一个全局URL列表,我从中提取了一个块并使用map_async
发送给工作人员。最后,我将收集所有返回的URL并将它们附加到全局列表中。问题是每个" chunk"需要与最慢的工人一样长。我试图对其进行修改,以便每当工作完成时,它都可以获取下一个URL。但是,我不认为我正确地做到了。这就是我到目前为止所拥有的:
def worker(url, urls):
print(multiprocessing.current_process().name + "." + str(multiprocessing.current_process().pid) + " loading " + url)
returned_urls = phantomjs(url)
print(multiprocessing.current_process().name + "." + str(multiprocessing.current_process().pid) + " returning " + str(len(returned_urls)) + " URLs")
for returned_url in returned_urls:
urls.put(returned_url, block=True)
print("There are " + str(urls.qsize()) + " URLs in total.\n")
if __name__ == '__main__':
manager = multiprocessing.Manager()
urls = manager.Queue()
urls.append(<some-url>)
pool = Pool()
while True:
url = urls.get(block=True)
pool.apply_async(worker, (url, urls))
pool.close()
pool.join()
如果有更好的方法,请告诉我。我正在抓取已知网站,最终终止条件是没有要处理的网址。但是现在看起来我将永远保持奔跑状态。我不确定是否会使用queue.empty()
,因为它确实说它不可靠。
答案 0 :(得分:0)
这就是我解决问题的方法。我最初使用this answer中发布的设计,但bj0提到它滥用了初始化函数。所以我决定使用apply_async
,以类似于我的问题中发布的代码的方式进行。
由于我的工作人员修改了队列,他们正在读取(他们添加到它)的URL,我认为我可以像这样简单地运行我的循环:
while not urls.empty():
pool.apply_async(worker, (urls.get(), urls))
我预计这会有效,因为工作人员会添加到队列中,如果所有工作人员都忙,apply_async
会等待。 这没有按照我的预期工作,并且循环提前终止。问题是,如果所有工人都很忙,apply_async
不会阻止,这一点并不明确。相反,它会排队提交的任务,这意味着urls
最终将变为空,循环将终止。当您尝试执行urls.get()
时,如果队列为空,则循环阻止的唯一时间。此时,它将等待更多项目在队列中可用。但我仍然需要找出终止循环的方法。条件是当没有工作人员返回新URL时,循环应该终止。为此,我使用一个共享的dict,如果进程没有返回任何URL,则将与process-name关联的值设置为0,否则为1。我在循环的每次迭代中检查键的总和,如果它是0,我知道我已经完成了。
基本结构最终是这样的:
def worker(url, url_queue, proc_user_urls_queue, proc_empty_urls_queue):
returned_urls = phantomjs(url) # calls phantomjs and waits for output
if len(returned_urls) > 0:
proc_empty_urls_queue.put(
[multiprocessing.current_process().name, 1]
)
else:
proc_empty_urls_queue.put(
[multiprocessing.current_process().name, 0]
)
for returned_url in returned_urls:
url_queue.put(returned_url)
def empty_url_tallier(proc_empty_urls_queue, proc_empty_urls_dict):
while 1:
# This may not be necessary. I don't know if this worker is run
# by the same process every time. If not, it is possible that
# the worker was assigned the task of fetching URLs, and returned
# some. So let's make sure that we set its entry to zero anyway.
# If this worker is run by the same process every time, then this
# stuff is not necessary.
id = multiprocessing.current_process().name
proc_empty_urls_dict[id] = 0
proc_empty_urls = proc_empty_urls_queue.get()
if proc_empty_urls == "done": # poison pill
break
proc_id = proc_empty_urls[0]
proc_empty_url = proc_empty_urls[1]
proc_empty_urls_dict[proc_id] = proc_empty_url
manager = Manager()
urls = manager.Queue()
proc_empty_urls_queue = manager.Queue()
proc_empty_urls_dict = manager.dict()
pool = Pool(33)
pool.apply_async(writer, (proc_user_urls_queue,))
pool.apply_async(empty_url_tallier, (proc_empty_urls_queue, proc_empty_urls_dict))
# Run the first apply synchronously
urls.put("<some-url>")
pool.apply(worker, (urls.get(), urls, proc_empty_urls_queue))
while sum(proc_empty_urls_dict.values()) > 0:
pool.apply_async(worker, (urls.get(), urls, proc_empty_urls_queue))
proc_empty_urls_queue.put("done") # poison pill
pool.close()
pool.join()
答案 1 :(得分:0)
以下是我可能会做的事情:
def worker(url, urls):
print(multiprocessing.current_process().name + "." + str(multiprocessing.current_process().pid) + " loading " + url)
returned_urls = phantomjs(url)
print(multiprocessing.current_process().name + "." + str(multiprocessing.current_process().pid) + " returning " + str(len(returned_urls)) + " URLs")
for returned_url in returned_urls:
urls.put(returned_url, block=True)
# signal finished processing this url
urls.put('no-url')
print("There are " + str(urls.qsize()) + " URLs in total.\n")
if __name__ == '__main__':
manager = multiprocessing.Manager()
pool = Pool()
urls = manager.Queue()
# start first url before entering loop
counter = 1
pool.apply_async(worker, (<some-url>, urls))
while counter > 0:
url = urls.get(block=True)
if url == 'no-url':
# a url has finished processing
counter -= 1
else:
# a new url needs to be processed
counter += 1
pool.apply_async(worker, (url, urls))
pool.close()
pool.join()
每当从队列中弹出一个url时,递增计数器。将其视为“当前正在处理的URL”计数器。当从队列中弹出“no-url”时,“当前正在处理的URL”已经完成,因此递减计数器。只要计数器大于0,就会有一些网址没有完成处理并返回'no-url'。
修改强>
正如我在评论中所说的那样(在这里为其他任何人阅读),在使用multiprocessing.Pool
时,而不是将其视为单个进程,最好将其视为执行的单个结构每次获取数据时的功能(在可能的情况下同时执行)。这对于数据驱动的问题非常有用,在这些问题中,您不会仅跟踪或关注正在处理的数据的单个工作进程。