我正在为我的SPA应用程序编写一个搜寻器。由于它是SPA,因此我无法使用wget / curl或任何其他基于非浏览器的解决方案进行爬网,因为我需要使用浏览器才能在SPA中运行javascript。
我使用python和selenium进行了编码。它将从首页开始,扫描所有href
元素,将它们保存在set
中,丢弃我已经访问过的元素(如visited
中的opened with selenium and collected all the href elements
),并从集合中获取下一个URL并进行访问。然后它将一遍又一遍地重复该过程,直到访问了所有链接。
代码如下:
def main():
...
# Here we will be saving all the links that we can find in the DOM of
# each visited URL
collected = set()
collected.add(crawler.start_url)
# Here we will be saving all the URLs that we have already visited
visited = set()
base_netloc = urlparse(crawler.start_url).netloc
while len(collected):
url = collected.pop()
urls = self.collect_urls(url)
urls = [x for x in urls if x not in visited and urlparse(x).netloc == base_netloc]
collected = collected.union(urls)
visited.add(url)
crawler.links = list(visited)
crawler.save()
def collect_urls(self, url):
browser = Browser()
browser.fetch(url)
urls = set()
elements = browser.get_xpath_elements("//a[@href]")
for element in elements:
link = browser.get_element_attribute(element, "href")
if link != url:
urls.add(link)
browser.stop()
return urls
我想使对collect_urls
的每次调用都成为Celery任务,因此它可以在失败时重试,并且还可以使整个过程更快(使用多个工作程序)。问题是collect_urls
是从while
内部调用的,这取决于collected
集,该集由collect_urls
的结果填充。
我知道我可以使用delay()
调用Celery任务,并使用get()
等待结果,所以我的代码将如下所示:
while len(collected):
url = collected.pop()
task = self.collect_urls.delay(url)
urls = task.get(timeout=30)
这会将我对collect_urls
的调用转换为Celery任务,并且如果出现故障,我可以重试,但是我仍然无法使用多个工作人员,因为我需要等待delay()
的结果。
我该如何重构我的代码,使其可以为collect_urls
使用多个工作程序?
答案 0 :(得分:1)
简短的回答,如果您希望出于速度目的而分发它,则必须使已访问的网站集成为跨进程安全的结构。例如,您可以通过将其作为一组存储在Redis或数据库表中来实现。完成后,您可以更新代码以执行以下操作:
# kick off initial set of tasks:
result_id = uuid.uuid4()
for x in collected:
task = self.collect_urls.delay(x, result_id)
return result_id
您可以使用该result_id定期检查访问的URL集。一旦该集合具有n
个呼叫次数相同的长度,您就认为它已经完成。
在collect_urls函数中,您实际上是这样做的:
def collect_urls(self, url, result_id):
# for example, you can use redis smember to check if the
# set at result_id contains url
if url has been visited:
return
# you can do this in redis using sadd
add url to the set of visited
# collect urls as before
...
# but instead of returning the urls, you kick off new tasks
for x in urls:
collect_urls.delay(x, result_id)
如果您使用redis,则所有收集/访问的url都将包含在result_id标识的redis密钥中。您不必使用redis,您可以轻松地对数据库中具有result_id作为一列,而将url作为另一列的行进行此操作。