Многопроцессорный парсер теряет ссылки при паринге (selenium+PhantomJS+ProcessPoolExecutor)?

Я хочу написать парер для некоторого вебсайта,
который имеет много JS кодов. Для этого я решил выб-
рать связку Selenium+PhantomJS+lxml. Работаю на Python
Парсер необходим достаточно быстрый, что бы обраба-
тывал не менее 1000 ссылок за 1 час. Для этой цели
я решил использовать многопроцессорность. (не ммного-
поточночность - из-за GIL!). Для разбиения на процессы
использовал concurrent.Future.ProcessPoolExecutor.

Проблема в следующем, я например, даю на вход 10
ссылок, на выходе обрабатываетя в лучшем случае 9
(может и 6). Это плохо! Есть еще некоторая зависимость
с увеличением количества потоков увеличивается количество
утеряных ссылок. Первое что я решил сделать, это
проследить где идет обрыв программы, где она перестает
выполняться. (assert в моем случае на сколько я понял
не пойдет, из за многопроцессорности). Тут я определил
что обрыв идет в функции browser.get(l) - не загружает
страницу. Я попробовал добавить time.sleep(x), потом
добавил wait видимый и не видимый. Тоже ничего не из-
менилось. Начал исследовать функцию get() c модуля
selenium, нашел что она перезагружать с того же модуля
функцию execute(), и там я залез в дебри, что мои
знания не позволяют разобраться, да и времени особо нет.
И в то же время, я попробвал запустить в один процесс.
То есть количество процессов = 1. И тоже одна ссылка
потерялась. Это навело на мысль, что возможно дело не
в selenium+phantomJS, а в ProcessPoolExecutor. Я заменил
этот модуль на multiproessing.Pool - и о чудо, ссылки
перестали теряться. Но вместо этого появилась другая
проблема, более 4 потоков не выполняет. Если ставишь
больше, выдает следующую ошибку:
"""
    multiprocessing.pool.RemoteTraceback: 
    Traceback (most recent call last):
    File "/usr/lib/python3.4/multiprocessing/pool.py", line 119, in worker
        result = (True, func(*args, **kwds))
    File "/usr/lib/python3.4/multiprocessing/pool.py", line 44, in mapstar
        return list(map(*args))
    File "interface.py", line 34, in hotline_to_mysql
        w = Parse_hotline().browser_manipulation(link)
    File "/home/water/work/parsing/class_parser/parsing_classes.py", line 352, in browser_manipulation
        browser.get(l)
    File "/usr/local/lib/python3.4/dist-packages/selenium/webdriver/remote/webdriver.py", line 247, in get
        self.execute(Command.GET, {'url': url})
    File "/usr/local/lib/python3.4/dist-packages/selenium/webdriver/remote/webdriver.py", line 233, in execute
        response = self.command_executor.execute(driver_command, params)
    File "/usr/local/lib/python3.4/dist-packages/selenium/webdriver/remote/remote_connection.py", line 401, in execute
        return self._request(command_info[0], url, body=data)
    File "/usr/local/lib/python3.4/dist-packages/selenium/webdriver/remote/remote_connection.py", line 471, in _request
        resp = opener.open(request, timeout=self._timeout)
    File "/usr/lib/python3.4/urllib/request.py", line 463, in open
        response = self._open(req, data)
    File "/usr/lib/python3.4/urllib/request.py", line 481, in _open
        '_open', req)
    File "/usr/lib/python3.4/urllib/request.py", line 441, in _call_chain
        result = func(*args)
    File "/usr/lib/python3.4/urllib/request.py", line 1210, in http_open
        return self.do_open(http.client.HTTPConnection, req)
    File "/usr/lib/python3.4/urllib/request.py", line 1185, in do_open
        r = h.getresponse()
    File "/usr/lib/python3.4/http/client.py", line 1171, in getresponse
        response.begin()
    File "/usr/lib/python3.4/http/client.py", line 351, in begin
        version, status, reason = self._read_status()
    File "/usr/lib/python3.4/http/client.py", line 321, in _read_status
        raise BadStatusLine(line)
    http.client.BadStatusLine: ''

    The above exception was the direct cause of the following exception:

    Traceback (most recent call last):
    File "interface.py", line 69, in <module>
        main()
    File "interface.py", line 63, in main
        executor.map(hotline_to_mysql, link_list)
    File "/usr/lib/python3.4/multiprocessing/pool.py", line 260, in map
        return self._map_async(func, iterable, mapstar, chunksize).get()
    File "/usr/lib/python3.4/multiprocessing/pool.py", line 599, in get
        raise self._value
    http.client.BadStatusLine: ''
    """
    import random
    import time
    import lxml.html as lh
    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
    from multiprocessing import Pool
    from selenium.webdriver.common.keys import Keys
    from concurrent.futures import Future, ProcessPoolExecutor, ThreadPoolExecutor
    AMOUNT_PROCESS = 5

    def parse(h)->list:
        # h - str, html of page
        lxml_ = lh.document_fromstring(h)
        name = lxml_.xpath('/html/body/div[2]/div[7]/div[6]/ul/li[1]/a/@title')
        prices_ = (price.text_content().strip().replace('\xa0', ' ')
                    for price in lxml_.xpath('//*[@id="gotoshop-price"]'))
        markets_ =(market.text_content().strip() for market in
                lxml_.find_class('cell shop-title'))
        wares = [[name[0], market, price] for (market, price)
                in zip(markets_, prices_)]
        return wares


    def browser_manipulation(l):
        #options =  []
        #options.append('--load-images=false')
        #options.append('--proxy={}:{}'.format(host, port))
        #options.append('--proxy-type=http')
        #options.append('--user-agent={}'.format(user_agent)) #тут хедеры рандомно

        dcap = dict(DesiredCapabilities.PHANTOMJS)
        #user agent takes from my config.py
        dcap["phantomjs.page.settings.userAgent"] = (random.choice(USER_AGENT))
        browser = webdriver.PhantomJS(desired_capabilities=dcap)
        #print(browser)
        #print('~~~~~~', l)
        #browser.implicitly_wait(20)
        #browser.set_page_load_timeout(80)
        #time.sleep(2)
        browser.get(l)
        time.sleep(20)
        result = parse(browser.page_source)
        #print('++++++', result[0][0])
        browser.quit()
        return result

    def main():
        #open some file with links

        with open(sys.argv[1], 'r') as f:
            link_list = [i.replace('\n', '') for i in f]
        with Pool(AMOUNT_PROCESS) as executor:
            executor.map(browser_manipulation, link_list)

    if __name__ == '__main__':
        main()

Собственно вопросы: где может быть ошибка? из-за
селениума и фантома, ProcessPoolExecutora, или я где
то код написал не правильно?
Как можно увеличить скорость парсинга что бы 1000
ссылок, за 1 час. ?
Наконец, может есть какой то другой способ паринга
динамических страниц? (само собой на питоне)
Спасибо за ответы.
  • Вопрос задан
  • 1663 просмотра
Пригласить эксперта
Ответы на вопрос 1
alekciy
@alekciy
Вёбных дел мастер
Наверное уже не очень актуально, но оставлю ремарку для истории. Потеря страниц на парсенге - ситуация штатная. Обилие JS, падения кода при работе с DOM, сетевые проблемы, все может привести к генерации исключения и фейлу получения данных. Так что при разработке стоит сразу заклываться на штатность такой ситуации и просто отлавливать неразобранные страницы и отправлять на повторный парсинг.

1000 страниц за 1 час более чем реальная задача. Сам получал скорость в 1000 за 15 минут. Достигается просто поднятием кластера. Требует много ресурсов (у меня выходило что-то около 10 узлов на каждый до 5Гб ОЗУ).
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы