我正在使用线程和队列模块在Python中编写一个简单的爬虫程序。我获取一个页面,检查链接并将它们放入队列,当某个线程完成处理页面时,它从队列中抓取下一个。我正在为我已经访问过的页面使用一个数组来过滤我添加到队列中的链接,但如果有多个线程并且它们在不同页面上获得相同的链接,则会将重复的链接放入队列。那么如何才能找出某个url是否已经在队列中以避免再次将其放在那里?
答案 0 :(得分:15)
如果您不关心处理项目的顺序,我会尝试在内部使用Queue
的{{1}}子类:
set
正如Paul McGuire指出的那样,这将允许在从“待处理”集中删除并且尚未添加到“已处理”集合之后添加重复项目。要解决此问题,您可以将这两个集存储在class SetQueue(Queue):
def _init(self, maxsize):
self.maxsize = maxsize
self.queue = set()
def _put(self, item):
self.queue.add(item)
def _get(self):
return self.queue.pop()
实例中,但由于您使用较大的集来检查项目是否已被处理,因此您也可以返回到Queue
正确地订购请求。
queue
与单独使用集合相反,这样做的优点是class SetQueue(Queue):
def _init(self, maxsize):
Queue._init(self, maxsize)
self.all_items = set()
def _put(self, item):
if item not in self.all_items:
Queue._put(self, item)
self.all_items.add(item)
的方法是线程安全的,因此您不需要额外的锁定来检查另一个集合。
答案 1 :(得分:4)
put方法也需要被覆盖,否则连接调用将永远阻止 https://github.com/python/cpython/blob/master/Lib/queue.py#L147
class UniqueQueue(Queue):
def put(self, item, block=True, timeout=None):
if item not in self.queue: # fix join bug
Queue.put(self, item, block, timeout)
def _init(self, maxsize):
self.queue = set()
def _put(self, item):
self.queue.add(item)
def _get(self):
return self.queue.pop()
答案 2 :(得分:3)
以下是对LukášLalinský后者solution的改进。
重要的区别是,put
会被覆盖,以确保unfinished_tasks
准确无误且join
按预期工作。
from queue import Queue
class UniqueQueue(Queue):
def _init(self, maxsize):
self.all_items = set()
Queue._init(self, maxsize)
def put(self, item, block=True, timeout=None):
if item not in self.all_items:
self.all_items.add(item)
Queue.put(self, item, block, timeout)
答案 3 :(得分:1)
我解决这个问题的方式(实际上我是在Scala而不是Python中完成的)是同时使用Set和Queue,只是添加了队列(和set)的链接(如果它们尚未存在于集合中)。 / p>
set和queue都封装在一个线程中,只向消费者线程公开一个类似队列的接口。
编辑:其他人建议使用SQLite,这也是我正在考虑的事情,如果访问过的网址需要增大。 (目前每次抓取只有几百页,因此很容易适合内存。)但是数据库也可以封装在集合本身中,因此消费者线程无需了解它。
答案 4 :(得分:1)
SQLite使用起来非常简单,非常适合......只是一个建议。
答案 5 :(得分:1)
使用:
url in q.queue
如果url
在队列中
答案 6 :(得分:1)
为什么只使用数组(理想情况下,字典会更好)来过滤你已经访问过的内容?在排队后立即将数据添加到数组/字典中,只有在它们尚未存在于数组/字典中时才将它们添加到队列中。然后你有3个简单的单独的东西:
答案 7 :(得分:1)
这是SetQueue
import Queue
class SetQueue(Queue.Queue):
def _init(self, maxsize):
Queue.Queue._init(self, maxsize)
self.all_items = set()
def _put(self, item):
if item not in self.all_items:
Queue.Queue._put(self, item)
self.all_items.add(item)
def _get(self):
item = Queue.Queue._get(self)
self.all_items.remove(item)
return item
答案 8 :(得分:0)
而不是“已访问的页面数组”使“已添加到队列中的页面数组”
答案 9 :(得分:0)
可悲的是,我没有评价最佳LukášLalinský的回答。
为LukášLalinský的SetQueue的第二个变种添加对SetQueue.task_done()
和SetQueue.join()
的支持,在if:
def _put(self, item):
if item not in self.all_items:
Queue._put(self, item);
self.all_items.add(item);
else:
self.unfinished_tasks -= 1;
经过测试并使用Python 3.4。
答案 10 :(得分:0)
我同意@Ben James。试着同时使用deque和set。
这里是代码:
class SetUniqueQueue(Queue):
def _init(self, maxsize):
self.queue = deque()
self.setqueue = set()
def _put(self, item):
if item not in self.setqueue:
self.setqueue.add(item)
self.queue.append(item)
def _get(self):
return self.queue.popleft()
答案 11 :(得分:-3)
此外,您可以尝试使用字典而不是集合。集合上的操作在它们很大时往往变得相当慢,而字典查找很好而且很快。
我的2c。