Организация очереди без дублей, многопоток.



  • Был в долгом отпуске :)
    Кажется мозги заржавели, чувствуется.
    Захотел себе сделать парсер небольшой, но задачка оказалась не тривиальной из-за одного нюансика.

    Задача парсить схожие сайты с сервиса similarweb.
    Есть стартовый набор сайтов, с которых будет начинаться сбор схожих сайтов. А после этого, к стартовому набору добавляются и сами спаршенные схожие сайты и процесс парсинга пошел дальше. Парсим схожие сайты из схожих спаршенных ранее и так вглубь "бесконечно", пока перестанут появляться новые схожие сайты, а будут попадаться одни дубли.

    Например, идем по ссылке https://www.similarweb.com/website/youtube.com/
    Крутим вниз и видим раздел Competitors & Similar Sites там блок с 10 схожими сайтами.
    Берем эти 10 сайтов и добавляем их в очередь на парсинг. А исходный сайт youtube.com добавляем в черный список, чтобы не парсить в дальнейшем, то что уже спаршено. И пошли по кругу снова.

    Думал использую под хранение очереди из сайтов для парсинга РЕСУРС. Но не тут то было, огорчился , когда понял, что в ресурс возможно добавить дубликат и все простота и удобство ресурса улетучилась. Откуда дубли будут? Например в 20 потоков идет парсинг, и среди схожих сайтов будут дубликаты, которые будут добавлены в очередь. Т.е. дубликаты нужно фильтровать перед этапом добавление в очередь парсинга.

    Потом вспомнил про встроенную БД, проверил, но и там можно добавить запись-дубль. А возможности указать колонку, как уникальный ключ не нашел. По этому и БД отпали.

    Потом увидел , что добавился функционал "асинхронные функции", да, через них такое можно провернуть, но много городить придется. Поскольку очередь будет расширяться , то придется химичить с циклами вызова асинх. функций. Т.е. брать кусок 20 строк из очереди, отправлять их в асинхронные функции, там по этих 20 сайтах получили схожие (с кучей дублей). Вышли в основной поток, обработали эти данные, убрали дубли, удалили те, сайты, что уже ранее были спаршены сверка с blacklist. Потом опять входим в цикл берем следующий кусок данных из очереди. Но проблемка то в том, что очередь динамичная, по этому сколько раз цикл делать по вызову асинх.функции? Сделать бесконечный цикл while(True) ? И если в очереди нет данных, то выходить из цикла, завершать работу? Ну да, вроде рабочий вариант. А что тогда делать, если захочется скрипт остановить и возобновить работу завтра? Можно перед каждой итерацией цикла по вызову асинхр.функции читать файл, если так написано СТОП, значит выходить из цикла сохранять текущую очередь в файл. Но опять же - костыли.

    За глобальные переменные тоже думал, но они тут не в тему вроде как, еще больше костылей.

    Всего этого можно было бы избежать, если бы в ресурс нельзя было добавлять дубли. Как в js есть set - в которую, если добавляешь запись и она уже там есть, то новая (дубль) не добавится.

    Кто как решал бы красиво, с минимум костылей и без использования внешней БД ?



  • @out Сам недавно впервые заюзал "асинхронные функции"
    https://community.bablosoft.com/topic/15033/как-оптимальнее-организовать-асинхронную-работу-функции

    Мне кажется (может я пока не совсем догоняю, без асинхронных функций в какой то степени проще было проектировать проект), когда стоит большая задача, нужно делать 2 цикла, один while бесконечный, и один поменьше, который порциями подает задания для асинхронной функции. В твоем случае задания наврятли кончатся, поэтому можно делать бесконечный.

    Давно еще писал паука на питоне с подобным функционалом, он парсил заходил на урлы, по маске заданным списком регулярок и кусков урлов а так же по блэклисту он сравнивал попадает ли урл под условия, добавлял в список и так до бесконечности. Т.е. изначально в списке только несколько урлов, также есть список блэклист (там нежелательные урлы или же уже спарсенные сайты) и список с регулярками. Паук заходит на первый урл, проверяет парсит урлы, которые проверяет есть ли он в блэклисте, затем проверяет, подходит ли он по маске регулярок и есть ли он уже в основном текущем списке, если нет то добавляется в конец списка. Мне кажется в БАСе тоже самое, надо в списке держать список урлов, перед добавление в него нового урла чекать на присутствие в этом же списке, добавлять новый урл в конец и т.д. При новом запуске с предыдущей позиции, спарсенный список загружается в память и с сайта, на котором он был последний (как то помечать прогресс парсера чтобы не ходить по тем же урлам) и так же чекая на дубли добавлять по условиям в конец списка и т.д. Давно парсер писал, точно не помню сколько точно было списков, но все в них держал и по ним чекал каждый новый урл.



  • Вот ради интереса откопал это чудо инженерной мысли, написал для того чтобы разобраться как pyqt на питоне работает, в 2013 году. По скрину логика понятна будет:



  • @serrgo логику то я понимаю, спасибо. Но вот как это красиво сделать в БАСе с минимум ненужных действий.. чтобы не нагородить всякой хрени )
    Т.е. насколько я знаю в БАС нет подходящей структуры под очереди. А значит, придется таки что-то городить, осталось понять с использованием чего именно: списков в глобальных переменных, ресурсов или асинх. функций. Наверное, по старинке с глобальными переменным.



  • @out Напиши потом как написал


Log in to reply