Кормление сельдерея с собственным результатом - PullRequest
0 голосов
/ 03 января 2019

Я пишу сканер для своего приложения SPA. Поскольку это SPA, я не могу использовать wget / curl или любое другое решение, не основанное на браузере, для сканирования, потому что мне нужен браузер для запуска javascript в моем SPA.

Я закодировал это, используя питон и селен. Он начнется на главной странице, отсканирует все элементы 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.

Я знаю, что могу вызвать задачу Celery с помощью delay() и ждать результата с помощью 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 голосов
/ 03 января 2019

Краткий ответ: если вы хотите, чтобы это распространялось в целях скорости, вы должны превратить набор уже посещенных веб-сайтов в межпроцессную безопасную структуру. Вы можете сделать это, например, сохранив его как набор в 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-адреса будут содержаться в ключе redis, указанном в result_id. Вам не нужно использовать redis, вы можете так же легко сделать это со строками в базе данных, у которых result_id равен одному столбцу, а url - в другом.

...