4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186

Beautiful Soup 4, сравнение html-парсеров при запуске в потоке

04.04.2018, 21:03. Показов 7323. Ответов 47
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте, спарсил страницу с помощью lxml, встроенного в Beautiful Soup - выдал 30 секунд. Не поверил, начал искать ошибки и оптимизировать. Ничего конкретного не получилось, время парсинга примерна такая же. Вспомнил, что у Beautiful Soup есть еще 2 других парсера html.parser и html5lib, в итоге их результаты:
Запуск в отдельном потоке Запуск в общей программе, без создания отдельного потока
html.parser:2 1.44
html5lib: 14 5.5
lxml: 30 1.24

И собственно вопрос, почему в потоке lxml так теряет в скорости? Хотя он считается самым быстрым
Метод запуска потока:
Python
1
2
3
4
5
6
def starting_threading():
    while q.empty()==False:
        if(threading.active_count()<=threads_count):
            t = threading.Thread(target=all_parsing, args=(q,))
            t.start()
            print("Старт потока",t)
q-очередь, threads_count=1.
Вдруг кому-то захочется спарсить эту же страницу ради спортивного интереса: https://www.mvideo.ru/smartfon... rtfony-205 . Парсить только ссылки на телефоны 1 страницы (этой). Пишите библиотеку и язык программирования для сравнения. Должен быть запрос страницы, без сохранения ее на диск, а потом ее парсинга
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
04.04.2018, 21:03
Ответы с готовыми решениями:

Beautiful Soup Python
Я пишу программу открывающую все ссылки на указанной странице и выписывает те, у которых в url есть id= и т.д. Но по какой-то причине в...

Парсинг на Python с Beautiful Soup
Напишите 2 парсера. Первый парсер разработайте для скрапинга сайта pleer.ru, где необходимо вытащить текст из колонки категорий(см. скрин...

Beautiful Soup поиск по атрибуту
Добрый вечер. Подскажите пожалуйста как с помощь bs4 получить все li c data-xxxx, xxxx-каждый раз новое

47
Эксперт Python
5438 / 3859 / 1215
Регистрация: 28.10.2013
Сообщений: 9,552
Записей в блоге: 1
06.04.2018, 17:22
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от Рыжий Лис Посмотреть сообщение
и ничего нигде не лочится.
Потому что выполняется в отдельном потоке.
Многопоточность != асинхронность.
Многопоточность основана на вытесняющей многозадачности.
Асинхронность - на кооперативной многозадачности + event loop.
В многопоточных программах потоками (если это настоящие потоки) управляет планировщик ОС.
В асинхронных программах потоков нет - они однопоточные. Однако есть корутины\сопрограммы, которые как раз выполняют роль нитей.
И управление нитями\корутинами - переключение с одной задачи на другую - задача соответствующей библиотеки (в python это asyncio).
И библиотека ожидает, что мы в своем коде будет использовать только асинхронный код - иначе все усилия - кот под хвост.

Небольшая цитата:
Переключение корутин осуществляется значительно быстрее переключения тредов, поскольку при этом не происходит переключения режима работы процессора.
-------------------------------------------------
Цитата Сообщение от danilshik Посмотреть сообщение
асинхронные запросы нужно только для частей работающих с вводом/выводам и работающих сетью
А что есть граббинг? В основном это ожидание сетевого ввода\вывода и совсем немного какие-то задачи на CPU.
Распараллеливать запросы посредством асинхронного кода более практично, чем использование многопоточности.
-----------------------------
Цитата Сообщение от danilshik Посмотреть сообщение
У вас нет какого нибудь парсера на нем
Парсера нет. Есть простой пример. Остальное нужно смотреть в доках по asyncio и aiohttp.


Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import logging
import asyncio
import aiohttp
import os
import time
import lxml.html
 
urls = [
        'http://www.python.org',
        'http://www.python.org/about/',
        'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
        'http://www.python.org/doc/',
        'http://www.python.org/download/',
        'http://www.python.org/getit/',
        'http://www.python.org/community/',
        'https://wiki.python.org/moin/',
        'http://planet.python.org/',
        'https://wiki.python.org/moin/LocalUserGroups',
        'http://www.python.org/psf/',
        'http://docs.python.org/devguide/',
        'http://www.python.org/community/awards/',
        'http://www.not_exists_url/'  # несуществующий url, чтобы получить ошибку
        ]
 
 
async def fetch(client,url):
    """
    Сопрограмма для загрузки данных по указанному url
    """
    data = dict.fromkeys(['err','title','url','status','text'])
    try:
        async with client.get(url) as r:
            # получение контента асинхронная операция, поэтому ее нужно await'ить\ожидать
            text = await r.text() 
            title = lxml.html.fromstring(text).find(".//head/title").text #cssselect('head title')[0].text
            data.update({
                'title':title,
                'status':r.status,
                'url':url,
                'text':text
                })
            return data
    
    except Exception as err:
        data.update({'err':str(err),'url':url})
        return data
   
        
    
async def main(urls):
    """
    Создает группу сопрограмм и ожидает их завершения
    """
    # создаем экземпляр клиента
    async with aiohttp.ClientSession() as client:
        # создаем корутины
        coroutines = [fetch(client,url) for url in urls]
        # ожидаем выполнения всех корутин
        completed, pending = await asyncio.wait(coroutines)
        # итерация по завершенным результатам
        for item in completed:
            data = item.result()
            
            if not data['err']:
                log.info("%s %s %s",
                    data['title'],
                    data['status'],
                    data['url']
                    )
            else:
                log.error("%s %s",
                    data['url'],
                    data['err']
                    )
                    
 
if __name__ == '__main__':
    
    log = logging.getLogger(__name__)
    format = '%(asctime)s %(levelname)s:%(message)s'
    logging.basicConfig(format=format, level=logging.INFO)
    log.info("Start")
    
    # получаем экзепляр цикла событий
    event_loop = asyncio.get_event_loop()
    
    try:
        t_start = time.time()
        # запуск цикла  обработки событий 
        event_loop.run_until_complete(main(urls))
        t_end = time.time()
        log.info("Time End: %s",t_end - t_start) # 3.042086362838745
    finally:
        # обязательно закрываем
        event_loop.close() 
    #N.B     в python 3.7  все три операции обернули одной: asyncio.run(coro)
Добавлено через 2 часа 25 минут
UPD. Другой вариант - без ожидания завершения каждого задания - обработка результатов по мере поступления.
Все то же самое, изменена только функция main, в которую теперь нужно передать именованный параметр loop с экземпляром запущенного цикла событий.
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
async def main(urls,loop=None):
    """
    Создает группу сопрограмм и получает результаты не дожидаясь окончания всех
    """
    # создаем экземпляр клиента
    
    async with aiohttp.ClientSession() as client:
        tasks = []
        for url in urls:
            task = loop.create_task(fetch(client,url))
            tasks.append(task)
        # итерация по заданиям не ожидая завершения всех
        for task in asyncio.as_completed(tasks):
            data = await task
            
            if not data['err']:
                log.info("%s %s %s",
                    data['title'],
                    data['status'],
                    data['url']
                    )
            else:
                log.error("%s %s",
                    data['url'],
                    data['err']
                    )
0
4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186
06.04.2018, 17:43  [ТС]
Если нажать на выбор город на https://www.mvideo.ru/ и искать города: то выдает лишние значения None
Кликните здесь для просмотра всего текста
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
2018-04-06 19:26:46,641 INFO:Start
2018-04-06 19:26:47,487 INFO:Выполнен запрос
2018-04-06 19:26:47,488 INFO:Получен html
2018-04-06 19:26:47,489 INFO:CityCZ_975
2018-04-06 19:26:47,489 INFO:CityCZ_1272
2018-04-06 19:26:47,489 INFO:CityCZ_7173
2018-04-06 19:26:47,489 INFO:CityCZ_2030
2018-04-06 19:26:47,489 INFO:CityCZ_1458
2018-04-06 19:26:47,489 INFO:CityCZ_2128
2018-04-06 19:26:47,489 INFO:CityCZ_1854
2018-04-06 19:26:47,489 INFO:CityCZ_974
2018-04-06 19:26:47,489 INFO:CityCZ_2246
2018-04-06 19:26:47,489 INFO:CityCZ_1250
2018-04-06 19:26:47,489 INFO:CityCZ_2446
2018-04-06 19:26:47,489 INFO:CityCZ_1780
2018-04-06 19:26:47,489 INFO:CityCZ_1638
2018-04-06 19:26:47,489 INFO:CityCZ_2534
2018-04-06 19:26:47,489 INFO:CityCZ_1216
2018-04-06 19:26:47,489 INFO:Получены города
2018-04-06 19:26:47,490 INFO:Time End: 0.84794020652771
CityCZ_2534
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_2534
CityCZ_2030
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_2030
CityCZ_1638
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_1638
CityCZ_1216
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_1216
CityCZ_1780
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_1780
CityCZ_2246
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_2246
CityCZ_7173
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_7173
CityCZ_1458
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_1458
CityCZ_2446
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_2446
CityCZ_1272
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_1272
CityCZ_1250
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_1250
CityCZ_1854
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_1854
CityCZ_2128
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_2128
CityCZ_974
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_974
CityCZ_975
https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=1?cityId=CityCZ_975
None
None
None
None
None
None
None
None
None
None
None
None
None
None
None


Добавлено через 53 секунды
применяю
Python
1
for elem in form.cssselect("input[class='input-radio']"):
Добавлено через 22 секунды
Не пойму, из-за чего происходит

Добавлено через 1 минуту
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
async def parse_city(client,url):
    try:
        async with client.get(url) as r:
            request=await r.json()
            log.info('Выполнен запрос')
            html=lxml.fromstring(request["htmlContent"])
            log.info('Получен html')
            cities=[]
            form=html.cssselect('form[id="city-radio-from"]')[0]
            # cssselect('head title')[0].text
            for elem in form.cssselect("input[class='input-radio']"):
                city = elem.get("value")
                log.info(city)
                city_temp=[]
                city_temp.append(city)
                city_temp.append("https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=" + str(1)+"?cityId="+str(city))
                cities.append(city_temp)
            log.info("Получены города")
            return cities
 
    except Exception as err:
        print(err)
 
async def main(urls):
    """
    Создет группу сопрограмм и ожидает их завершения
    """
    # создаем экземпляр клиента
    async with aiohttp.ClientSession() as client:
        # создаем корутины
        coroutines = [parse_city(client, url) for url in urls]
        completed, pending =await asyncio.wait(coroutines)
        for item in completed:
            cities=item.result()
    async with aiohttp.ClientSession() as client:
        coroutines1=[all_parsing(client, city_url) for city_url in cities]
        completed, pending=await asyncio.wait(coroutines1)
        for item in completed:
            data = item.result()
            print(data)
        #
        #     if not data['err']:
        #         log.info("%s %s %s",
        #                  data['title'],
        #                  data['status'],
        #                  data['url']
        #                  )
        #     else:
        #         log.error("%s %s",
        #                   data['url'],
        #                   data['err']
        #                   )
async def all_parsing(client, city):
    city_title=city[0] #Получение кода города
    city_url=city[1]   #Получение ссылки на 1 страницу списка телефонов данного города
    print(city_title)
    print(city_url)
if __name__ == '__main__':
 
    log = logging.getLogger(__name__)
    format = '%(asctime)s %(levelname)s:%(message)s'
    logging.basicConfig(format=format, level=logging.INFO)
    log.info("Start")
 
    # получаем экзепляр цикла событий
    event_loop = asyncio.get_event_loop()
 
    try:
        t_start = time.time()
        # запуск цикла  обработки событий
        event_loop.run_until_complete(main(url_select_city))
        t_end = time.time()
        log.info("Time End: %s", t_end - t_start)  # 3.042086362838745
    finally:
        # обязательно закрываем
        event_loop.close()
        # N.B     в python 3.7  все три операции обернули одной: asyncio.run(coro)
Добавлено через 6 минут
Хотя если проверить длину списка, то он равен 15, но ни как не 30

Добавлено через 2 минуты
И вопрос как можно ограничить количество запросов? Если я правильно понимаю, одновременно делаются запросы, количество которых равно количеству элементов в списке. А если там будет больше 50, меня же забанить могут)

Добавлено через 1 минуту
Это для ссылки https://www.mvideo.ru/sitebuil... pageUrl=/&
0
Эксперт Python
5438 / 3859 / 1215
Регистрация: 28.10.2013
Сообщений: 9,552
Записей в блоге: 1
06.04.2018, 18:39
Цитата Сообщение от danilshik Посмотреть сообщение
то выдает лишние значения None
В моем примере из функции возвращающей значения запроса возврат есть и в случае нормального ответа и в случае ошибки. У вас - нет.
И еще - не смешивайте в одной функции логику запроса и логику парсинга. Это все нужно делать в отдельных функциях помеченных как async и дожидаться их выполнения посредством await.
У меня в коде (и уже пожалел, что не сделал этого сразу) - строчка
Python
1
title = lxml.html.fromstring(text).find(".//head/title").text
должна быть в отдельной функции, поскольку это уже другая логика, отличная от логики запроса.
Такая разбивка делает код более читабельным, понятным не только вам и улучшает отладку при ошибках.
Так что ваши траблы мне пока малопонятны.
Цитата Сообщение от danilshik Посмотреть сообщение
И вопрос как можно ограничить количество запросов?
Библиотека к этому отношения не имеет. Логику ограничения должны придумать и написать вы сами. Можно ввести принудительные ограничения в виде таймаутов: для этого есть асинхронный asyncio.sleep.
0
4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186
06.04.2018, 22:57  [ТС]
Как можно поймать и ошибку raise asyncio.TimeoutError from None
concurrent.futures._base.TimeoutError
Кликните здесь для просмотра всего текста
Traceback (most recent call last):
File "D:/Учеба/Диплом/parsers/smartphone/test7.py", line 144, in <module>
event_loop.run_until_complete(main(url_s elect_city))
File "C:\ProgramData\Anaconda3\lib\asyncio\ba se_events.py", line 467, in run_until_complete
return future.result()
File "D:/Учеба/Диплом/parsers/smartphone/test7.py", line 116, in main
item= item.result()
File "D:/Учеба/Диплом/parsers/smartphone/test7.py", line 70, in parse_list_object
r=await request(client,url)
File "D:/Учеба/Диплом/parsers/smartphone/test7.py", line 16, in request
async with client.get(url) as r:
File "C:\ProgramData\Anaconda3\lib\site-packages\aiohttp\client.py", line 783, in __aenter__
self._resp = await self._coro
File "C:\ProgramData\Anaconda3\lib\site-packages\aiohttp\client.py", line 333, in _request
await resp.start(conn, read_until_eof)
File "C:\ProgramData\Anaconda3\lib\site-packages\aiohttp\client_reqrep.py", line 708, in start
self._continue = None
File "C:\ProgramData\Anaconda3\lib\site-packages\aiohttp\helpers.py", line 670, in __exit__
raise asyncio.TimeoutError from None
concurrent.futures._base.TimeoutError
2018-04-07 00:47:33,206 ERROR:Task exception was never retrieved
future: <Task finished coro=<parse_list_object() done, defined at D:/Учеба/Диплом/parsers/smartphone/test7.py:66> exception=TimeoutError()>
0
Эксперт Python
5438 / 3859 / 1215
Регистрация: 28.10.2013
Сообщений: 9,552
Записей в блоге: 1
07.04.2018, 01:06
Ошибка возникает на строке item= item.result()
Возможно ловить нужно там.
Кстати, незавершенные задания будут находиться во втором списке - pending.

Добавлено через 8 минут
Хотя, не уверен - обработка ошибок в асинхронных корутинах не совсем тривиальная задача
0
Эксперт С++
 Аватар для Avazart
8489 / 6156 / 615
Регистрация: 10.12.2010
Сообщений: 28,683
Записей в блоге: 30
07.04.2018, 11:02
Цитата Сообщение от danilshik Посмотреть сообщение
Из курса дисциплины "Операционные системы" знаю что лучше создать n-потоков, чем n-количество процессов
Да только для питона это не те самые "потоки" которые дают прирост, в питоне GIL мешает.
Поэтому я и говорю что нет смысла распараллеливать парсинг с помощью этих потоков.

Добавлено через 38 секунд
Цитата Сообщение от Garry Galler Посмотреть сообщение
Потому что выполняется в отдельном потоке.
Многопоточность != асинхронность.
Многопоточность основана на вытесняющей многозадачности.
Асинхронность - на кооперативной многозадачности + event loop.
В многопоточных программах потоками (если это настоящие потоки) управляет планировщик ОС.
В асинхронных программах потоков нет - они однопоточные. Однако есть корутины\сопрограммы, которые как раз выполняют роль нитей.
И управление нитями\корутинами - переключение с одной задачи на другую - задача соответствующей библиотеки (в python это asyncio).
И библиотека ожидает, что мы в своем коде будет использовать только асинхронный код - иначе все усилия - кот под хвост.
Модцом смешали в кучу ...
0
Эксперт Python
5438 / 3859 / 1215
Регистрация: 28.10.2013
Сообщений: 9,552
Записей в блоге: 1
07.04.2018, 13:49
Цитата Сообщение от Avazart Посмотреть сообщение
Да только для питона это не те самые "потоки" которые дают прирост, в питоне GIL мешает.
Это не так. В python - те самые потоки ОС. И они могут давать прирост производительности. Посмотрите доклады Бизли для ясности.
P.S. Если не в курсе - даже lxml отпускает GIL - достаточно глянуть исходник или прочитать в документации это:
Since version 1.1, lxml frees the GIL (Python’s global interpreter lock) internally when parsing from disk and memory, as long as you use either the default parser (which is replicated for each thread) or create a parser for each thread yourself
Многие расширения на Си точно также отпускают GIL, что дает возможность эффективно использовать распараллеливание CPU-dound задач на этих библиотеках.
А про задачи IO уже давно было сказано - они ОТПУСКАЮТ GIL, когда погружаются в состояние ожидания.

Цитата Сообщение от Avazart Посмотреть сообщение
смешали в кучу
Что именно? Это краткий ввод без попыток точных формулировок. Но ошибок там нет.
0
Эксперт С++
 Аватар для Avazart
8489 / 6156 / 615
Регистрация: 10.12.2010
Сообщений: 28,683
Записей в блоге: 30
07.04.2018, 14:02
Цитата Сообщение от Garry Galler Посмотреть сообщение
Это не так. В python - те самые потоки ОС.
Они то те же самые, но вот GIl делает их не те же самыми.
Цитата Сообщение от Garry Galler Посмотреть сообщение
они могут давать прирост производительности.
А могут и не давать, это качается вычислений.

Добавлено через 1 минуту
Цитата Сообщение от Garry Galler Посмотреть сообщение
P.S. Если не в курсе - даже lxml отпускает GIL - достаточно глянуть исходник или прочитать в документации это:
Да морока с тем что каждый раз приходится думать, гадать, читать доку просто убивает.

Какая то либо библиотека(или место в коде) не будет отпускать и это может стать узким горлышком.
0
Эксперт Python
5438 / 3859 / 1215
Регистрация: 28.10.2013
Сообщений: 9,552
Записей в блоге: 1
07.04.2018, 19:52
Цитата Сообщение от danilshik Посмотреть сообщение
И вопрос как можно ограничить количество запросов?
В первый раз я ответил, что таких средств, вроде нет в asyncio.
На самом деле есть: Semaphore, который принимает как параметр число "одновременно" выполняющихся корутин.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# если указать число заданий n=len(urls), то фактически это безлимит
# если указать n=1, то фактически это синхронное выполнение
# таким образом ограничение - это нечто среднее между этими значениями
 
# создаем семафор
SEM = asyncio.Semaphore(n)  
...
# передаем его как параметр в вызове функции запросов
...fetch(client,url,parallel_limit=SEM)...
 
# в самой функции запросов используем контексный менеджер
...
with (await parallel_limit):
    async with client.get(url) as r:
        ...
P.S. Кстати, по моим тестам асинхронная модель при сетевом I/O немного даже выигрывает по скорости у многопоточной, хотя, конечно, есть много неясностей с обработкой исключений.
И Светлов как-то не сильно эту тему освещает.
0
4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186
08.04.2018, 21:52  [ТС]
Цитата Сообщение от Garry Galler Посмотреть сообщение
В первый раз я ответил, что таких средств, вроде нет в asyncio.
На самом деле есть: Semaphore, который принимает как параметр число "одновременно" выполняющихся корутин.
PythonВыделить код

# если указать число заданий n=len(urls), то фактически это безлимит
# если указать n=1, то фактически это синхронное выполнение
# таким образом ограничение - это нечто среднее между этими значениями
# создаем семафор
SEM = asyncio.Semaphore(n) *
...
# передаем его как параметр в вызове функции запросов
...fetch(client,url,parallel_limit=SEM). ..
# в самой функции запросов используем контексный менеджер
...
with (await parallel_limit):
* * async with client.get(url) as r:
* * * * ...
Спасибо, посмотрю чуть позже

Добавлено через 1 минуту
Как думаете стоит так сделать?http://skipperkongen.dk/2016/0... d-asyncio/
Судя по опыту внутри Async находится пул, максимум 5 запросов одновременно делает? или я не так понял
0
Эксперт Python
5438 / 3859 / 1215
Регистрация: 28.10.2013
Сообщений: 9,552
Записей в блоге: 1
09.04.2018, 15:08
Цитата Сообщение от danilshik Посмотреть сообщение
стоит так сделать
Там используется run_in_executor, где дефолтный экзекутор - пул потоков. И используется такой вариант из-за синхронной requests. То есть имеет смысл, только если мы используем в асинхронном коде блокирующие долгоиграющие функции.
0
4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186
13.04.2018, 12:35  [ТС]
У меня почему то request+threedpoolexecutor в 10 потоков выполняется в 2 раза быстрее чем async с семафором 10

Добавлено через 1 минуту
request
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
log = logging.getLogger(__name__)
count_thread=10
q_list =[]
q_url=queue.Queue()
cities=[]
url_main="https://www.mvideo.ru"
 
 
def parse_list_object(q_list):
    start = time.time()
    url=q_list[0]
    city=q_list[1]
    print(url, city)
    log.info("Парсинг list: %s | %s",url,city)
    r=requests.get(url)
    html=get_html(r)
    for elem in html.xpath(".//div[@class='c-product-tile sel-product-tile-main ']"):
        error_div=elem.xpath(".//span[@class='c-product-label__text']")
        if(error_div!=[]):
            error_div=error_div[0]
            error_text=error_div.xpath("span[2]/text()")[0]
            if(error_text!="Витринный образец"):
                continue
        link_object=url_main+elem.xpath(".//h4[@class='e-h4 u-pb-0']/a")[0].get("href")+"/shopdirections?cityId="+city
        q_url.put(link_object)
        log.info(link_object)
    end = time.time()
    print(len(q_url))
    log.info("Спарсен за %s", end-start)
def parse_object(url):
    r=requests.get(url)
    log.info("Запрос")
    html=get_html(r)
    log.info("Парсинг")
    print("Парсинг")
def start_threads():
    with ThreadPoolExecutor(count_thread) as executor:
        for _ in executor.map(parse_list_object, q_list):
            pass
 
    # with Pool(count_thread) as p:
    #     p.map(parse_list_object, q_list)
 
    # pool = ThreadPool(count_thread)
    # results = pool.map(parse_list_object, q_list)
    # pool.close()
    # pool.join()
 
 
def get_html(request):
    return lxml.html.fromstring(request.text)
def parse_city():
    r = requests.get("https://www.mvideo.ru/sitebuilder/blocks/regionSelection.json.jsp?pageId=homepage&pageUrl=/&").json()
    log.info('Запрос страницы городов')
    html = lxml.html.fromstring(r["htmlContent"])
    log.info('Получение html')
    for elem in html.xpath("//input[@name='/com/mvideo/domain/RegionSelectionFormHandler.cityId']"):
        city=elem.get("value")
        cities.append(city)
        log.info(city)
 
def all_parsing():
    for city in cities:
        r = requests.get("https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=" + str(1)+"?cityId="+str(city))
        log.info("Переход на 1 страницу города:"+city)
        html =get_html(r)
        count_list=html.xpath("//a[@class='c-pagination__num c-btn c-btn_white ']/text()")[0]
        log.info("Парсинг")
        log.info(count_list)
        for i in range(1,int(count_list)+1):
            parameters=[]
            parameters.append("https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=" + str(i)+"?cityId="+city)
            parameters.append(city)
            q_list.append(parameters)
 
 
 
 
 
 
 
if __name__ == "__main__":
    start_main=time.time()
    log = logging.getLogger(__name__)
    logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s', level=logging.INFO)
    log.info('start')
    parse_city()
    all_parsing()
    start_threads()
 
    log.info('end')
    log.info("Время работы парсера: %s",time.time()-start_main)

async
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
url_select_city=["https://www.mvideo.ru/sitebuilder/blocks/regionSelection.json.jsp?pageId=homepage&pageUrl=/&"]
url_main="https://www.mvideo.ru"
sem=asyncio.Semaphore(10)
 
async def get_html(request):
    return lxml.html.fromstring(request)
 
async def request(client,url,limit):
    with(await limit):
        async with client.get(url) as r:
            print(r.status)
            return await r.text()
 
 
async def request_json(client,url):
    async with client.get(url) as r:
        return await r.json()
 
async def parse_city(client,url):
    try:
        r= await request_json(client,url)
        log.info('Выполнен запрос')
        html=await get_html(r["htmlContent"])
        log.info('Получен html')
        cities=[]
        form=html.cssselect('form[id="city-radio-from"]')[0]
        for elem in form.cssselect("input[class='input-radio']"):
            city = elem.get("value")
            log.info(city)
            city_temp=[]
            city_temp.append(city)
            city_temp.append("https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=" + str(1)+"?cityId="+str(city))
            cities.append(city_temp)
        log.info("Получены города")
        return cities
 
    except Exception as err:
        print(err)
 
async def parsing_first_list(client, city,limit):
    city_title=city[0] #Получение кода города
    city_url=city[1]   #Получение ссылки на 1 страницу списка телефонов данного города
    #log.info("Парсинг первой страницы: %s - %s",city_title,city_url)
 
    r=await request(client,city_url,limit)
    log.info("Запрос: %s",city_url)
    html = await get_html(r)
    # div_pagination=html.xpath('//div[@class="c-pagination"]')
    # #=div_pagination.cssselect("/a[class='c-pagination__num c-btn c-btn_white']")
    # print(div_pagination)
    count_list=html.xpath(".//a[@class='c-pagination__num c-btn c-btn_white ']/text()")[0]
    log.info("Количество страниц: %s",count_list)
    urls_list=[]
    for i in range(1,int(count_list)+1):
        url_temp=[]
        url_temp.append("https://www.mvideo.ru/smartfony-i-svyaz/smartfony-205/f/page=" + str(i)+"?cityId="+city_title)
        url_temp.append(city_title)
        urls_list.append(url_temp)
    log.info("Парсинг закончен:%s",city_url)
    return urls_list
 
async def parse_list_object(client, list_objects,limit):
        url=list_objects[0]
        city=list_objects[1]
        log.info("Парсинг list_objects: %s | %s",url, city)
        r=await request(client,url,limit)
        log.info("Запрос list: %s", city)
        html=await get_html(r)
        urls_object=[]
        for elem in html.xpath(".//div[@class='c-product-tile sel-product-tile-main ']"):
            error_div=elem.xpath(".//span[@class='c-product-label__text']")
            if(error_div!=[]):
                error_div=error_div[0]
                error_text=error_div.xpath("span[2]/text()")[0]
                if(error_text!="Витринный образец"):
                    continue
            link_object=url_main+elem.xpath(".//h4[@class='e-h4 u-pb-0']/a")[0].get("href")+"/shopdirections?cityId="+city
            url_temp=[]
            url_temp.append(link_object)
            url_temp.append(city)
            urls_object.append(url_temp)
            log.info("Ссылка телефона: %s - страница List: %s",link_object,url)
        log.info("Спарсен  - %s",url)
        if(len(urls_object)>0):
            print("Количество спарсенных ссылок:",len(urls_object))
            return urls_object
        else:
            return []
    # except Exception as err:
    #     print(err)
 
 
 
async def main(urls):
    """
    Создет группу сопрограмм и ожидает их завершения
    """
    # создаем экземпляр клиента
    async with aiohttp.ClientSession() as client:
        # создаем корутины
        coroutines = [parse_city(client, url) for url in urls]
        completed, pending =await asyncio.wait(coroutines)
        for item in completed:
            cities=item.result()
    async with aiohttp.ClientSession() as client:
        coroutines1=[parsing_first_list(client, city_url,limit=sem) for city_url in cities]
        completed, pending=await asyncio.wait(coroutines1)
        list_objects=[]
        for item in completed:
            list_objects += item.result()
    async with aiohttp.ClientSession() as client:
        coroutines2=[parse_list_object(client, url_object,limit=sem) for url_object in list_objects]
        completed, pending=await asyncio.wait(coroutines2)
        url_object=[]
        for item in completed:
            item= item.result()
            url_object+=item
            print(url_object)
 
if __name__ == '__main__':
 
    log = logging.getLogger(__name__)
    format = '%(asctime)s %(levelname)s:%(message)s'
    logging.basicConfig(format=format, level=logging.INFO)
    log.info("Start")
 
 
    # получаем экзепляр цикла событий
    event_loop = asyncio.get_event_loop()
 
    try:
        t_start = time.time()
        # запуск цикла  обработки событий
        event_loop.run_until_complete(main(url_select_city))
        t_end = time.time()
        log.info("Time End: %s", t_end - t_start)  # 3.042086362838745
    finally:
        # обязательно закрываем
        event_loop.close()
0
 Аватар для IRIP
514 / 146 / 28
Регистрация: 18.04.2015
Сообщений: 1,904
Записей в блоге: 16
13.04.2018, 12:38
Цитата Сообщение от danilshik Посмотреть сообщение
почему то request+threedpoolexecutor в 10 потоков выполняется в 2 раза быстрее чем async с семафором 10
а какую нагрузку на донора это дает?
На некоторых донорах стоит блок на множество запросов
0
4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186
13.04.2018, 12:42  [ТС]
async 1134 против request+threedPool 590

Добавлено через 2 минуты
Цитата Сообщение от IRIP Посмотреть сообщение
Сообщение от danilshik
почему то request+threedpoolexecutor в 10 потоков выполняется в 2 раза быстрее чем async с семафором 10
а какую нагрузку на донора это дает?
На некоторых донорах стоит блок на множество запросов
Не совсем понял, что под донором понимается?
0
 Аватар для IRIP
514 / 146 / 28
Регистрация: 18.04.2015
Сообщений: 1,904
Записей в блоге: 16
13.04.2018, 13:00
сайт с которого берут данные
от способа, которым берешь данные: интервалы между запросами, количество каналов, прокси и т.п.
многое зависит

в том числе и шанс быть заблокированным
0
4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186
13.04.2018, 13:09  [ТС]
Цитата Сообщение от IRIP Посмотреть сообщение
сайт с которого берут данные
от способа, которым берешь данные: интервалы между запросами, количество каналов, прокси и т.п.
многое зависит
в том числе и шанс быть заблокированным
Ну вот смотри в ThreedPool+request мы делаем в каждом потоке по запросу и сразу парсим результат. Так основное время - это отправка запроса и получение ответа, можно предположить, что 10 потоков дают около 10 запросов к серверу единовременно.
Также и async мы отправляем 10 запросов одновременно, и парсим по мере получения запроса. Так что можно уверять что и тут одновременно 10 запросов в серверу. Но способ с потоками в 2 раза быстрее чем, с async, хотя Garry Galler утвержает что использование async быстрее
0
║XLR8║
 Аватар для outoftime
1212 / 909 / 270
Регистрация: 25.07.2009
Сообщений: 4,360
Записей в блоге: 5
13.04.2018, 15:27
Цитата Сообщение от danilshik Посмотреть сообщение
Но способ с потоками в 2 раза быстрее чем, с async, хотя Garry Galler утвержает что использование async быстрее
IRIP, danilshik, asyncio в разы быстрее 2х поточной работы с медленными устройствами\сетью. Но нужно понимать одно, выигрыишь не с скорости получения данных, а в том есть есть возможность полностью загрузить канал данными открыв 100500 соединение, не дожидаясь пока мы получим ответ он предыдущих 100499. Вот за счет чего получается быстрее. Если отправлять 2 запроса всего - смысла город городить нету.
0
4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186
13.04.2018, 15:35  [ТС]
Цитата Сообщение от outoftime Посмотреть сообщение
Сообщение от danilshik
Но способ с потоками в 2 раза быстрее чем, с async, хотя Garry Galler утвержает что использование async быстрее
IRIP, danilshik, asyncio в разы быстрее 2х поточной работы с медленными устройствами\сетью. Но нужно понимать одно, выигрыишь не с скорости получения данных, а в том есть есть возможность полностью загрузить канал данными открыв 100500 соединение, не дожидаясь пока мы получим ответ он предыдущих 100499. Вот за счет чего получается быстрее. Если отправлять 2 запроса всего - смысла город городить нету.
Тут 10 запросов. К тому же мы не можем сделать столько запросов, либо бан получим, либо ошибку TimeError

Добавлено через 1 минуту
А возможно ли сохранить результат запроса? Идея такая: сначала получаем результаты запросов, а потом потоками обрабатываем эти страницы? По идее же быстрее должно быть
0
║XLR8║
 Аватар для outoftime
1212 / 909 / 270
Регистрация: 25.07.2009
Сообщений: 4,360
Записей в блоге: 5
13.04.2018, 15:38
Цитата Сообщение от danilshik Посмотреть сообщение
Тут 10 запросов. К тому же мы не можем сделать столько запросов, либо бан получим, либо ошибку TimeError
Тогда и смысла заморачиваться нету. Только если вычисления очень долгие, но тогда лучше multiprocessing использовать, ибо треды скорости не прибавят.

В противном случае обычное однопоточное приложение - в самый раз. Еще, я бы сделал немного не так. Сначала выкачал данные, а потом замерял время на парсинг. Смысл задержки сети в время парсинга засовывать?
0
4 / 4 / 2
Регистрация: 04.04.2015
Сообщений: 186
13.04.2018, 15:42  [ТС]
Цитата Сообщение от outoftime Посмотреть сообщение
Тогда и смысла заморачиваться нету. Только если вычисления очень долгие, но тогда лучше multiprocessing использовать, ибо треды скорости не прибавят.
Уже тестировали, ThreedPool большую выгоду дает

Добавлено через 1 минуту
Цитата Сообщение от danilshik Посмотреть сообщение
Решил сравнить все приведенные библиотеки, результаты такие:
concurent.futures - ThreadPoolExecutor: 248
Multiprocessing.Dumpy:253
Multiprocesssing - вообще не запустился, процессы создались, и потом программа завершилась. Почему, не знаю, хотя я его когда-то уже использовал. Также пришлось отказаться от очереди и заменить списком
Код
Хотя не значительно

Добавлено через 26 секунд
Можно сказать погрешность
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
13.04.2018, 15:42
Помогаю со студенческими работами здесь

В чем проблема? (Beautiful Soup)
from bs4 import BeautifulSoup import requests headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...

Парсинг Beautiful Soup 4 и Selenium.Webdriver
Здравствуйте, пишу парсер под сайт https://www.kimovil.com/ru/ и возникает проблема: товары динамически подгружаются. Сначала были...

Beautiful Soup - существует ли ограничения на длину получаемых данных?
Здравствуйте! Почему при парсинге html достается только часть данных, которые все лежат в одном теге. данные - это 1 000 000 цифр. Получаю...

Извлечь атрибуты a href и img src одновременно, используя Beautiful Soup
Знаю как извлечь и показать a href urls = soup.findAll('a') for url in urls: print(url) Но как можно найти...

Как убрать soup = BeautifulSoup (html) из кода?
Приветствую всех, только недавно начал изучать Python, дали задание: сделать парс расписания нашей группы в универе. Пользовался Beautiful...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Опции темы

Новые блоги и статьи
Асинхронный приём данных из COM-порта
Argus19 01.05.2026
Асинхронный приём данных из COM-порта Купил на aliexpress термопринтер QR701. Он оказался странным. Поключил к Arduino Nano. Был очень удивлён. Наотрез отказывается печатать русские буквы. Чтобы. . .
попытка написать игровой сервер на C++
pyirrlicht 29.04.2026
попытка написать игровой сервер на плюсах с открытым бесконечным миром. возможно получится прикрутить интерпретатор питон для кастомизации игровой логики. что есть на текущий момент:. . .
Контроль уникальности выбранного документа-основания при изменении реквизита
Maks 28.04.2026
Алгоритм из решения ниже разработан на примере нетипового документа "ЗаявкаНаРемонтСпецтехники", разработанного в КА2. Задача: уведомлять пользователя, если указанная заявка (документ-основание). . .
Благородство как наказание
Maks 24.04.2026
У хорошего человека отношения с женщинами всегда складываются трудно. А я человек хороший. Заявляю без тени смущения, потому что гордиться тут нечем. От хорошего человека ждут соответствующего. . .
Валидация и контроль данных табличной части документа перед записью
Maks 22.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа, разработанного в КА2. Задача: контроль и валидация данных табличной части документа перед записью с учетом регламента компании. . .
Отчёт о затраченных материалах за определенный период с макетом печатной формы
Maks 21.04.2026
Отчёт из решения ниже размещён в конфигурации КА2. Задача: разработка отчёта по затраченным материалам за определённый период, с возможностью вывода печатной формы отчёта с шапкой и подвалом. В. . .
Отчёт о спецтехнике находящейся в ремонте
Maks 20.04.2026
Отчёт из решения ниже размещен в конфигурации КА2. Задача: отобразить спецтехнику, которая на данный момент находится в ремонте. Есть нетиповой документ "Заявка на ремонт спецтехники" который. . .
Памятка для бота и "визитка" для читателей "Semantic Universe Layer (Слой семантической вселенной)"
Hrethgir 19.04.2026
Сгенерировано для краткого описания по случаю сборки и компиляции скелета серверного приложения. И пусть после этого скажут, что статьи сгенерированные AI - туфта и не интересно. И это не реклама -. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru