CoderCastrov logo
CoderCastrov
Докер

Парсинг 50 миллионов твитов за $5 за 5 часов

Парсинг 50 миллионов твитов за $5 за 5 часов
просмотров
5 мин чтение
#Докер

Это будет подробное руководство, в котором объясняется, как не только парсить целевые данные из Twitter, но и масштабировать проект быстро и недорого, чтобы мы могли получить миллионы твитов.

Мотивация и обзор:

Классификация положительного и отрицательного настроения твитов стала популярной в последние годы с использованием таких техник, как "мешок слов", но я хочу проверить, насколько мощными и адаптивными стали современные модели языка, попробовав понять общественное настроение по отношению к биткоину. Хотя недавние инновации в техниках передачи обучения снизили необходимость в огромных объемах данных, мне все равно нужно собрать большой корпус твитов, чтобы построить эффективную модель. Для этого я собрал 43,8 миллиона твитов, содержащих ключевые слова, такие как биткоин или #btc, всего за 5 часов, используя различные технологии, включая Docker Swarm, MongoDB, BeautifulSoup и DigitalOcean.

Написание скрипта для парсинга:

Поиск веб-страниц:

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

Используя библиотеку requests и заполнив некоторые параметры, мы легко можем загрузить HTML с Twitter страницы в наш объект ответа.

Извлечение данных с помощью BeautifulSoup:

Получив этот ответ, теперь нам нужно разобрать HTML для получения нужных данных - а именно текста и временной метки.

Каждый твит представлен с помощью тегов <li> с атрибутом data-item-type равным 'tweet'. Используя BeautifulSoup и метод find_all(), мы можем легко получить все твиты. Изучив HTML для каждого твита, мы находим классы, содержащие нужные нам данные, такие как tweet-text и _timestamp. Хотя Twitter отображает, сколько времени назад был опубликован твит (5 часов назад), мы можем увидеть точное время в миллисекундах UTC, когда был опубликован твит, что будет очень ценно при сопоставлении твитов с рынком. Обратите внимание, что, изучив немного подробнее, мы можем извлечь некоторые дополнительные метаданные, такие как количество ретвитов, избранных и комментариев.

Работа с пагинацией:

Наконец, когда мы запускаем наш первоначальный поиск, мы загружаем только 20 твитов; только когда мы прокручиваем вниз, мы видим больше. Чтобы определить, как Twitter работает с этой пагинацией, мы используем наш браузер для анализа сетевой активности. Мы видим, что когда мы прокручиваем вниз для новой веб-страницы, мы получаем следующий запрос:

**URL запроса:**https://twitter.com/i/search/timeline?f=tweets&vertical=default&q=bitcoin%2C%20crypto%2C%20btc%20since%3A2019-07-05%20until%3A2019-07-06&src=typd&include_available_features=1&include_entities=1&lang=en&**max_position**=thGAVUV0VFVBaAwKeFkLn56x8WjMC8sZvtgOwfEjUAFQAlAFUAFQAA&reset_error_state=false

Из этого мы видим, что в запросе есть поле max_position, которое мы можем найти с помощью следующей строки:

next_pointer = soup.find("div", {"class": "stream-container"})["data-min-position"]

Наш первоначальный запрос к Twitter возвращает HTML, но каждый запрос пагинации возвращает JSON, поэтому мы должны разбирать каждый тип ответа по-разному.

Чтобы увидеть полный скрипт, проверьте здесь.

Развертывание и масштабирование:

Интеграция базы данных:

Поскольку это большой объем данных, который нам нужно сохранить в базе данных, мы используем MongoDB, так как она легко взаимодействует с Python и имеет отличную библиотеку.

Мы создаем 2 коллекции. Первая называется tweets и просто хранит все собранные нами твиты следующим образом:

tweets collection

Мы вставляем твиты в нашу функцию записи после завершения парсинга страницы.

Вторая коллекция называется queriesTodo и содержит даты для выполнения запроса.

queriesTodo collection

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

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

Инициализация узла-менеджера:

Я использовал сервер DigitalOcean с предустановленным Docker, имеющий 4 ГБ ОЗУ и 2 виртуальных ЦП, стоимостью $15 в месяц. Сначала мы хотим инициализировать сервер в качестве нашего узла-менеджера с помощью следующей команды Docker:

docker swarm init --advertise-addr <ip адрес>

Затем мы создаем оверлейную сеть, которая является VPN, позволяющей рабочим узлам общаться непосредственно с узлом-менеджером. Также обратите внимание, что могут потребоваться некоторые настройки брандмауэра, которые объясняются здесь.

docker network create -d overlay --attachable backend

Теперь, когда роевик инициализирован, мы запускаем наш первый сервис, который является экземпляром MongoDB. Создайте следующий файл docker-compose и запустите

docker stack deploy mongodb -c docker-compose.yml

Поскольку мы открываем порт 27017, мы хотим убедиться, что у нас включена аутентификация. Кроме того, хорошей идеей является монтирование тома с данными с контейнера на хост, поскольку данные более устойчивы на хосте.

Запуск 100 рабочих узлов:

Сначала нам нужно создать образ Docker для написанного выше скрипта парсинга. Для этого мы создаем простой Dockerfile, размещенный в каталоге с main.py.

В том же каталоге, где находятся main.py и Dockerfile, необходимо включить файл requriements.txt с зависимостями, импортированными в наш main.py. После перехода в правильную папку мы создаем образ и загружаем его в Docker Hub.

docker build -t <username>/twitter_scrape .
docker push <username>/twitter_scrape

Теперь мы можем вернуться к узлу-менеджеру и развернуть наш сервис парсинга.

docker service create --name news --network backend --constraint 'node.role==worker' --mode global <username>/twitter_scrape

Установка mode в значение global означает, что один контейнер будет запущен на каждом рабочем узле, подключенном к кластеру.

Наконец, нам нужно создать наш скрипт автоматизации DigitalOcean, который создает наши droplet'ы и уничтожает их, когда у нас заканчиваются запросы.

Также нам нужно иметь скрипт cloud-config, который занимается включением соответствующих портов в брандмауэре, а затем присоединением к узлу-менеджеру.

После запуска скрипта автоматизации мы должны увидеть создание droplet'ов и начало вставки твитов в базу данных.

Заключительные мысли:

Как мы справились? Мы смогли получить 43,8 млн твитов за 5,8 часов при работе 100 узлов. По стоимости $0,007 в час за узел это составляет около $4,06.

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

У Twitter есть API, но чтобы получить полную историю, вам понадобится премиум-план, который начинается от $100 в месяц и дает вам только 50 000 твитов. Самый дорогой премиум-план стоит $1900 в месяц и дает вам 1,25 млн твитов.

Я с нетерпением жду следующего этапа создания языковой модели и тестирования нашей работы на рынках.

📝 Прочитай эту историю позже в Журнале.

👩‍💻 Каждое воскресенье утром получай в своем почтовом ящике самые интересные истории в области технологий за неделю. Подпишись на рассылку Noteworthy in Tech.