• Как реализовать функцию duplicate (поясните плиз)?

    @deliro
    Чукча не читатель?

    удваивать этот список "по месту" (примечание: список передаётся по ссылке)
  • Где взять подробный пошаговый туториал по решению реальной жизненной задачи с помощью Docker?

    @deliro
    Sanes, я вижу в твоём ответе недвусмысленное пренебрежение докером и намёк на то, что им пользуются только "программисты, которые ничего не понимают в администрировании.". Да, у тебя так и написано.
  • Где взять подробный пошаговый туториал по решению реальной жизненной задачи с помощью Docker?

    @deliro
    Sanes, Ну давай, "закорячь" 50+ сервисов, написанных на разных некомпилируемых или без статической линковки языках, поддерживаемых разными людьми, горизонтально масштабируя их на десятки серверов без докера или докеро-подобной контейнеризации.
  • Идти на стажировку за бесплатно или учиться дома?

    @deliro
    Rufix, то есть, ты спрашиваешь "мне получить реальный опыт или просидеть дома в надежде, что хватит силы воли"? IT — это не только про то, чтобы кнопки нажимать, важно взаимодействие. А ещё, на стажировке есть более опытные мидлы/синьоры, которых можно мучить по возникающим вопросам. И есть реальные проекты с реальными задачами, где ты получаешь опыт и получаешь его, по сравнению с сидением дома, молниеносно.
  • Требуются сейчас программисты на чистый JavaScript?

    @deliro
    dollar,

    А вот если китайский vue

    javascript - это разве только фронт?


    Ты не переобуваться вообще не умеешь, да?
  • Побег на Линукс?

    @deliro
    Nikname_non_name, Ну тогда сиди на винде. В чём проблема?
  • Чем отличается сохранение поля модели?

    @deliro
    Лучше рассказать про последствия этой разницы :) Если питон делает сложение, то это легко приводит к race condition, когда воркеров больше одного. Если СУБД, то такой проблемы нет.
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko, вот надо попробовать. Потому что мои парсеры простаивали 90+% времени. Посмотри, если очередь на парсинг будет копиться — убери в отдельную очередь.
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko, не, ждать окончания будет накладно по ОЗУ. Я не думаю, что парсеры будут заняты больше, чем очередь на скачивание. По крайней мере, вот тот пример с парсингом википедии, что я скидывал. В нём парсеры почти всё время простаивают. Можно попробовать прям из них скидывать данные куда-то в БД. Но если из-за этого начнёт копиться очередь на парсинг — надо создать ещё одну asyncio.Queue и несколько воркеров, которые будут писать.
  • Как в Django 2.2 заставить условие "if" внутри "for" работать?

    @deliro
    Uno di Palermo,
    Да? Значит, мне показалось, что ты сказал
    все равно не работает


    Прошу прощения
  • Как в Django 2.2 заставить условие "if" внутри "for" работать?

    @deliro
    Uno di Palermo, Что значит "не работает"? "Не работает" говорит бухгалтерия в совокупности с "я ничего не нажимала". А ты, вроде, программист и можешь пользоваться такими крутыми словами как дебаггер, трейсбэки и логи
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko, ну, алгоритм придумать на базе архитектуры, я думаю, сам сможешь.

    Вот, за 10 минут накидал примерный способ скачать и распарсить всю википедию. Конечно, этот код — говно, его следует нормально инкапсулировать в классы и всё такое, но я хотел донести идею:

    spoiler

    import asyncio
    from concurrent.futures import ProcessPoolExecutor
    
    import aiohttp
    from loguru import logger as loguru
    from lxml.html import fromstring
    
    
    pool = ProcessPoolExecutor()
    parser_sem = asyncio.Semaphore(pool._max_workers)
    loguru.info(f"CPU workers: {pool._max_workers}")
    host = "https://ru.wikipedia.org"
    start_from = f"{host}/wiki/Заглавная_страница"
    q_d = asyncio.Queue()
    q_p = asyncio.Queue()
    sem = asyncio.Semaphore(100)
    downloaded_urls = set()
    
    
    class O:
        downloaded = 0
        parsed = 0
        downloading = 0
        down_pending = 0
        waiting_for_download_q = 0
    
    
    o = O()
    
    
    async def log_printer(queue_d, queue_p):
        while True:
            loguru.debug(
                f"[PRINTER] to Download: {queue_d.qsize()}, to Parse: {queue_p.qsize()}"
                f" downloaded: {o.downloaded}, parsed: {o.parsed}"
                f" pending: {o.down_pending}, downloading: {o.downloading}"
                f" waiting Q: {o.waiting_for_download_q}"
                f" tasks: {len(asyncio.Task.all_tasks())}"
            )
            await asyncio.sleep(0.33)
    
    
    def lxml_parse(html):
        try:
            tree = fromstring(html)
            urls = tree.xpath("//a/@href")
            try:
                title = tree.find(".//title").text
            except AttributeError:
                title = "<UNKNOWN>"
    
            new_urls = []
            for url in urls:
                if url.startswith("/") and not url.startswith("//"):
                    new_urls.append(f"{host}{url}")
                elif url.startswith("http"):
                    new_urls.append(url)
    
            return new_urls, title
        except Exception as e:
            loguru.error(f"Parse error: {e}")
            return [], "<ERROR>"
    
    
    async def parse(html):
        loop = asyncio.get_event_loop()
        urls, title = await loop.run_in_executor(pool, lxml_parse, html)
        o.parsed += 1
        return urls, title
    
    
    async def start_parse_task(content, queue_d):
        async with parser_sem:
            urls, title = await parse(content)
            # loguru.debug(f"[PARSER]: Parse done {title}")
            o.waiting_for_download_q += 1
            for url in urls:
                if url not in downloaded_urls:
                    await queue_d.put(url)
            o.waiting_for_download_q -= 1
            # loguru.debug(f"[PARSER]: Add {len(urls)} to download queue")
    
    
    async def parser(queue_d, queue_p):
        while True:
            content = await queue_p.get()
            asyncio.create_task(start_parse_task(content, queue_d))
    
    
    async def downloader(queue_d, queue_p, session):
        while True:
            url = await queue_d.get()
            if url in downloaded_urls:
                continue
    
            o.down_pending += 1
            async with sem:
                o.down_pending -= 1
                o.downloading += 1
                try:
                    async with session.get(url) as resp:
                        o.downloading -= 1
                        downloaded_urls.add(url)
                        # loguru.debug(f"[DOWNLOADER]: got response for {url}")
                        try:
                            text = await resp.text()
                            await queue_p.put(text)
                        except UnicodeDecodeError:
                            pass
                        o.downloaded += 1
                except Exception as e:
                    loguru.error(f"Download error: {e}")
    
    
    async def main():
        await q_d.put(start_from)
        async with aiohttp.ClientSession() as session:
            ds = []
            for i in range(100):
                ds.append(asyncio.create_task(downloader(q_d, q_p, session)))
            p = asyncio.create_task(parser(q_d, q_p))
            printer = asyncio.create_task(log_printer(q_d, q_p))
            await asyncio.gather(*ds, p, printer)
    
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())

  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko,

    По первому скрипту:
    1. В каждом таске ты создаёшь соединение с редисом. Его нужно пошарить между всеми тасками, чтобы тратить время на установку соединения
    2. while True блок может уйти в бесконечный цикл. Лучше-таки делать какую-то разумную отсечку
    3. Сессию (aiohttp.ClientSession) лучше тоже таскать по всем корутинам и использовать одну на всех

    В целом, подход правильный

    По поводу "впихнуть пару строк" — можно попробовать оставить в первом скрипте, который качает данные. Я почти уверен, что одна регулярка почти не помешает IO операциям.
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko, Да, регулярки — CPU. Ну и раз уж это парсить, то можно и всё остальное распарсить сразу
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko,

    будут плодиться лишние выполнения asyncio

    Если эти страницы всё равно придётся скачать — то без разницы, когда это произойдёт. Однако, как я понял, чтобы понять, что именно надо качать, надо сначала распарсить главную страницу. То есть, парсинг, опять CPU-bound, а в ивент-луп asyncio не должно ничего попадать CPU-bound, иначе всё залагает
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko, Тогда тебе надо две очереди — на парсинг и на скачивание. asyncio выдирает из очереди на скачивание урлы, качает страницы и результаты складывает в очередь на парсинг. Пул процессов выдирает задания из очереди на парсинг, парсит их, делает какие-то действия и если нужно спарсить что-то ещё — кладёт в очередь на скачивание.

    В целом, очереди можно реализовать в виде https://docs.python.org/3/library/multiprocessing....

    Но я бы остановился на редисе, он простой как палка и есть привязки редиса и к синхронному питону, и к aio
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko, ну а как ты сам думаешь, может ли ЦП выполняться быстрее, чем на 100%?) Я бы не ставил вообще какую-то константу в пул, лучше питон сам выберет количество воркеров в пуле. Он поставит просто столько, сколько ядер у тебя и это будет максимально выгодное количество, которое будет полностью загружать ядра, но ещё не мешать друг другу (конечно же, при условии, что в воркерах не происходит IO операций)
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko,

    в условный html_pages = []

    В условную очередь, которую ты сможешь шарить между процессами. Лучше, если это будет простой Redis. Хотя через сокет можно создать очередь тоже

    Почему? Если я запущу на данный момент 10 процессов, то они отработают гораздо медленнее, чем если запущу 100 процессов.

    Потому что у тебя скачивание (IO операция) и парсинг (CPU операция) происходят в одном месте. Если же ты в пуле процессов будешь ТОЛЬКО парсить, то каждый процесс будет загружать своё ядро на 100%. таким образом, N процессов (N - количество ядер у тебя) загрузят процессор максимально возможно, не мешая друг другу
  • Почему продолжает расти потребление ОЗУ?

    @deliro
    Ivan Yakushenko, ты пришёл сюда с ошибкой, из-за которой твой код не решает твою проблему. Я тебя правильно понимаю?

    asyncio физически не может медленней работать с IO, он создан для того, чтобы эффективно решать пробелемы с затупами IO. И чтобы догнать до этого, надо сначала узнать модель конкурентного программирования.

    Парсить же страницы — это CPU bound задача, её НАДО разделять на процессы (в питоне надо именно процессы, а не потоки). asyncio тут только усугубит всё. Причём, количество процессов бессмысленно указывать больше, чем ядер у тебя в компуктере.

    А чтобы эффективно расходовать время компухтера и твоё личное, драгоценное, нужно одновременно качать страницы и парсить уже скачанные, ставя контент страниц в очередь на парсинг и уже пулом процессов парсить. Это и называется producer-consumer.