用自己的结果喂芹菜队列

时间:2019-01-03 01:39:13

标签: python django python-3.x selenium celery

我正在为我的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使用多个工作程序?

1 个答案:

答案 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作为另一列的行进行此操作。