Всем привет. Пишу небольшого паука, чтобы обходить сайты (больше одного одновременно). Архитектура стандартная: паук берет из очереди урл, скачивает, находит ссылки, кладет в очередь. Параллельно работает несколько воркеров.

Проблема в следующем — я не понимаю, как не заддосить скачиваемый сайт. Допустим, в robots.txt написано не скачивать страницы чаще раза в пол-секунды. Как это сделать?

Я рассматривал такие варианты:
1) Держать в кэше дату-время последнего скачивания какой-то страницы с сайта, и делать в воркере паузу, пока не пройдет нужный интервал. Тут есть риск, все все воркеры будут заняты ожиданием этого интервала с одного сайта.
2) Аналогично, запоминать дату-время последнего скачивания, но не засыпать, а ставить таск обратно в очередь, если он пришел слишком рано. Тут есть риск заддосить свою очередь — все воркеры будут заниматься только извлечением из нее тасков и складыванием обратно

Подскажите пожалуйста, как это грамотно сделать?

62 Responses to Свой паук — как не заддосить скачиваемый сайт ?

  1. Oveer:

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

  2. KapCap:

    если вдруг perl то бери AnyEvent::HTTP в союзе с AnyEvent timer, или смотри аналоги в своём языке.

  3. RedSm:

    distributed query так не умеет. Туда можно положить тикет и считать. Скипнуть нельзя.

  4. RedSm:

    можешь на пальцах объяснить, что он делает? У меня python, аналогов с ходу не нашел. Если это способ написания асинхронных приложений, то я не очень понимаю, как он поможет. У меня асинхронность организуется внешней очередью.

  5. XeuAll:

    Считал, посмотрел, что таймаут не вышел, положил обратно.

  6. Zreno:

    если у тебя питон, то чем тебе не угодил scrapy как основа для паука?

  7. RedSm:

    Ну это и есть вариант 2, ддос очереди 🙂

  8. RedSm:

    скрапи заточен под извлечение конкретных данных с одного сайта. Табличку распарсить, типа. А мне тупо надо обойти несколько сайтов и скачать оттуда все. Кмк, немного другой паттерн. Но если я ошибаюсь, скажи, пойду почитаю про него внимательнее.

  9. KapCap:

    аналог AnyEvent на питоне — Twisted (event-driven networking engine).

    > можешь на пальцах объяснить, что он делает
    могу, но лучше расскажу как бы я с его помощью написал краулер.
    например можно было бы написать так: процесс один, на базе таймеров организовал бы несколько очередей обработки урлов
    * без задержек (тут можно было бы обойтись без таймера)
    * раз в секунду
    * раз в 5 секунд
    * раз в минуту
    * раз в 10 минут
    (для каждого сайта выбирается очередь с не-меньшим таймером)
    ну и внутри закачивал бы чем-то типа LWP::UserAgent или AnyEvent::HTTP страницу.
    + или отдельная очередь/несколько очередей под обработку ответов. вернее я бы, наверно, взял AnyEvent::HTTP из-за асинхронности вызова колбека и именно так реализовал обработку ответов.

  10. KapCap:

    , а как ты планируешь бороться с дублями в очереди?

  11. LspMsk:

    1) Держать в кэше дату–время последнего скачивания какой–то страницы с сайта, и делать в воркере паузу, пока не пройдет нужный интервал. Тут есть риск, все все воркеры будут заняты ожиданием этого интервала с одного сайта.

    При взятие задания ставить в кэш метку «locked» на данное задание. При взятие из кэша проверять метку и если она есть просить следующие.

  12. KapCap:

    это кажется не экономным. получается краулер 5 минут ждать страничку при том, что в очереди вполне могут находиться менее долгоиграющие объекты.

  13. Ki4no:

    1 сайт — 1 воркер

  14. RedSm:

    это эквивалент п2, ддос очереди.

  15. RedSm:

    кладу в базу все найденные урлы, и если урл уже скачан, то отбрасываю таск.

  16. RedSm:

    Тогда сайты без ограничений будут скачиваться медленнее, чем могли бы.

  17. RedSm:

    В общем, как я понял, твое предложение — организовать асинхронность на нитях вместо сторонней очереди. Вариант вполне разумный, но дорогостоящий в плане перехода и отладки. Если других вариантов не найдется — видимо придется делать так.

  18. KapCap:

    anyevent — это не threads, но для понимания это сейчас не важно

  19. LesYes:

    А мне одному это напоминает задачу об обеающих философах?

  20. Ki4no:

    ай мин 1 сайт — 1 очередь, на сайты с ограничениями — 1 воркер на очередь, без — сколько хочешь

  21. Ihcnode:

    Я что-то немогу понять суть проблемы(
    У тебя есть несколько потоков которые парсят урлы и кладут их в стек
    У тебя у каждого есть настройка ожидать сколько-то времени после загрузки.
    В чем проблема?

  22. RedSm:

    Спасибо за идею, подумаю.

  23. RedSm:

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

  24. Ihcnode:

    то есть предполагается что 10 потоков положат сайт? Или предполагается там большое число воркеров?

  25. KapCap:

    10 потоков краулера шастающего по динамическим страницам влёгкую положат 2/3 сайтов из списка (по моим субъективным ощущениям).

  26. Roten:

    А если не использовать очередь?

    Складывать урлы в упорядоченный список, сортируя по времени, когда их уже можно скачивать.

    Всем воркерам брать первый в списке урл, если время скачивания не наступило — засыпать, пока оно не наступит (всё равно делать что-либо смысла нет).

  27. Ihcnode:

    да ну нафиг, что это за сайты такие? мой краулер даже на 20 потоках не клал ни один сайт. ДДОС, имхо, полуается когда сто потоков просят один файл, а не когда 10 просят 10 файлов

  28. KapCap:

    ок, не «положат», но la на серверах заметно возрастает.

  29. Ihcnode:

    ну хуй знает если честно, если серверу внапряг отдать одновременно 10 файлов, мне кажется стоит задуматься о том что сервер-то гавно…
    А по сути проблемы, кто мешает в конце каждого цикла в каждом потоке «спать» заданое время и динамически обновлять его если требуется?

  30. KapCap:

    ну давай на примере одного из моих проектов.
    железка 2 гб памяти, 4xXeon, 10тая слака, nginx+apache1.3+mod_perl+memcached. кеширование на nginx, кеширование в memcached, отдача статики с диска. стандартный la = 0.5, если меня краулят без пауз между запросами в 10 потоков то la = 5-10. если внаглую рандомными запросами фигачат на страницу поиска (sphinx) то la может возрасти до 15.
    вот как-то так.

  31. KapCap:

    т.е. я подразумевал ситуацию не когда сервер отдаёт 10 файлов (произвольных), а когда именно бекенд должен формировать 10 ответов в сек.

  32. Ihcnode:

    ох ну что ж так… если честно, не работал с апачем в больших продакшин системах, только для небольших проектов без таких нагрузок. Мой рабочий сервер — IIS, и для него обработать 10 паралельных запросов — не проблема и там идут не 10 запросов на поиск, а вполне себе большие операции, с чтением/записью с диска, чтением/записью в БД, и прочими штуками в виде асинхронных вебсервисов и так далее.
    В тему топика — если надо синхронизировать сквозь все потоки — вводи какой-то флаг, который покажет число запущенных потоков и жди если оно превышает норму, например — 3, а ещё лучше — используй меньше потоков и не парься, тогда не придется ничего дописывать

  33. RedSm:

    Положат/не положат — это дело мутное, а Crawl-delay надо уважать

  34. RedSm:

    Где хранить список? Как организовать доступ к нему с нескольких серверов? Чем он принципиально будет отличаться от очереди?

  35. RedSm:

    Если «жди», то есть риск, что все воркеры будут ждать одного, и процесс встанет. Если «меньше потоков», то проект сможет пережевывать за единицу времени меньше сайтов, чем мог бы.

  36. Ihcnode:

    Как встанут-то? я прделагаю завести какою-то константу, скажем, три, и счетчик сейчас работающих воркеров, то есть если их три то мы ждем, если два то стартуем и инкрементим счетчик, после того как воркер отработал — декрементит счетчик и опять проверяет. На мой взгляд проблема решается опытным путем и метод выбирается соответственно.
    Если у тебя стоит ограничение на ожидание ответа например 10 секунд и при это все три потока ждут чего-то, либо качают большой файл, то возникает, соответственно вопрос, а так ли нужна эта синхронизация? Ведь файл отдается, это не дает нагрузки на сервер, при этом анализировать какие страницы динамические, а какие — статические, тоже такое себе дело, можно не угадать. Поэтому имхо если очень приперло проставлять делэй — проставляй, либо как я говорю, либо дату последнего сткарта воркера. Так ты точно поймешь надо ли тебе в текущем потоке спать или можно работать.

  37. Roten:

    Принципиально отличаться будет упорядоченностью, т. е. каждый новый элемент будет добавляться не в конец, а на место, которое соответствует порядку сортировки. Для этого можно использовать разные быстрые алгоритмы работы с упорядоченными списками. Где хранить? В сетевой БД, в специализированном хранилище, в облаке — в общем, где удобнее, там и хранить.

  38. Ssubad:

    Хранить в Redis/MongoDb например. Воркеры набегают на этот список берут первый урл со временем меньше текущего, и ставят флаг на урл, что этот урл в работе.

  39. Roten:

    Я уже джва года хочу такой паук.

  40. RedSm:

    ага, тут будет ддос базы вместо очереди. Хз, может так и получше будет. Надо померить. Спасибо за идею.

  41. RedSm:

    Извини, не понял про «место, которое соответствует порядку сортировки». Допустим, есть список урлов для скачивания и обработки:
    http://site_1.ru/url1
    http://site_1.ru/url2
    http://site_2.ru/url1
    http://site_2.ru/url2
    http://site_2.ru/url3
    http://site_2.ru/url4
    http://site_2.ru/url5

    У site_1 есть ограничение — не скачивать чаще раза в 2 секунды. У site_2 ограничений нет. Воркер нашел новый урл http://site_1.ru/url3. В какое место списка его надо вставить?

  42. Ssubad:

    а сколько у тебя воркеров будет? Редис/монго очень хорошо держат высокие нагрузки.

  43. Ihcnode:

    Расскажи, чего я не понял. Чем не катит синхронизация по времени последнего обращения к северу?

  44. Ihcnode:

    серверу конечно же, север он жеж бескрайний

  45. RedSm:

    Хз, как проект пойдет. 10-30-50-100

  46. RedSm:

    Я не говорю, что не катит. Я говорю, что будет много пустых обращений к базе » — пора? — нет, не пора. — ну а теперь пора? — нет, подожди еще».

  47. Zreno:

    эмм… не совсем понимаю что значит «скачать все» — чтобы забрать все содержимое сайта есть wget для *nix или там teleport pro для виндовса. краулер же обычно пишут именно чтобы выдрать содержимое определённого блока с определенного подмножества страниц сайта/сайтов. это что касается «скачать все».
    насчет «скрапи заточен под извлечение конкретных данных с одного сайта» — тоже ошибочное мнение, когда ты пишешь спайдер ты ему передаешь список стартовых урлов и никто не мешает передать урлы с разных сайтов, другой вопрос как обрабатывать результаты с разных сайтов — обычно проще создать по отдельному парсеру для каждого сайта, чем один универсальный.

  48. Ihcnode:

    а при чем тут база?
    динамический объект в памяти, который — ДатаВремя, а у тебя есть параметр, через какой промежуток можно стартовать. база тыт ваще никаким боком не лежит

  49. RedSm:

    а как это согласуется с тем, что воркеры могет на нескольких серверах одновременно работать?

  50. Ihcnode:

    объект синхронизации содержит какой-то идентификатор сайта или базовый адрес

  51. RedSm:

    Я немного не про это. Ты говоришь «динамический объект в памяти». Как воркеры с нескольких серверов будут обращаться к одному динамическому объекту в памяти?

  52. RedSm:

    Тогда уж Редис. Мемкэш не persistent.

  53. Ihcnode:

    Ну тут да, только зачем запускать жтот паук с разных серверов? или ты собрался распарсить интернет? На самом деле вопрос настолько же неактуален как и запись разобранных данных в одно место например. И сколько воркеров ты планируешь использовать? И зачем тогда использовать такую сложную архитектуру?

  54. RedSm:

    «Ну тут да, только зачем запускать жтот паук с разных серверов? или ты собрался распарсить интернет?»
    Я очень надеюсь, что мой проект взлетит, будет пользоваться спросом, и тогда придется запускать много воркеров. В том числе и на разных серверах.

  55. Ihcnode:

    Ну если ты планируешь настолько масштабный проект, то может стоить думать о выделении каких-то виртуальных сущностей или об инкапсуляции отдельных потоков с отдельными базами, я не знаю…

  56. SykMilk:

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

  57. Dioon:

    Проблему недопонимаю. Если не хочется пустых обращений, на предмет «готов-не готов», можно с помощью двух очередей организовать event driven модель.

    1. Очередь 1 — очередь queue урлов на обработку со свойствами. Пополняется всеми процессами грязно, без блокировок.
    2. Бизнес сервер, который постоянно следит за очередью, синхронизирует, вычищает дубли. Опеределяет циклические урлы, дубли итд. Пополняет очередь 2.
    3. Очередь 2 — очередь событий (events), которые просят: «Возьми меня» и отдаются ближайшему свободному (наименее загруженному) обработчику.
    4. Обработчики парсят и укладывают свежие данные в Очередь 1.

  58. RedSm:

    А где здесь ограничение на частоту скачивания?

  59. Oveer:

    на бизнес сервере видимо

  60. RedSm:

    «все это очень зыбко» ©

Добавить комментарий