Статьи

Использование Twitter4j с Scala для выполнения действий пользователя

Вступление

Мой предыдущий пост показал, как использовать Twitter4j в Scala для доступа к потокам Twitter . В этом посте показано, как контролировать действия пользователя Twitter с помощью Twitter4j. Основная цель этой функциональности, возможно, заключается в создании интерфейсов для Twitter, таких как TweetDeck, но ее также можно использовать для создания ботов, которые выполняют автоматические действия в Twitter (один бот, с которым я играю, это @tshrdlu , использующий код из этого урока). и код в хранилище tshrdlu ). Этот пост будет охватывать лишь небольшую часть того, что вы можете сделать, но это некоторые из наиболее распространенных вещей, и я привожу пару простых, но интересных случаев использования. После того, как вы это сделаете, легко понять, как использовать документы API Twitter4j (и переполнение стека), чтобы сделать все остальное.

Настройка: код и авторизация

Вместо того, чтобы читатель собирал код во время прохождения учебника, я настроил код в репозитории twitter4j-tutorial . Версия, необходимая для этого урока как v0.2.0. Вы можете загрузить tarball этой версии , с которым может быть проще работать, если после написания этого руководства были дальнейшие разработки в хранилище. Оформить заказ или скачать этот код сейчас. Основной интересующий файл:

  • SRC / Основной / Scala / TwitterUser.scala

Этот учебник в основном представляет собой обзор этого файла в форме блога, с некоторыми дополнительными указателями и пояснениями здесь и там. Вам также необходимо настроить детали авторизации. Смотрите раздел «Настройка авторизации» предыдущего поста, чтобы сделать это, если вы этого еще не сделали.

ВАЖНО: Для этого урока вы должны установить разрешения для вашего приложения « Чтение и запись ».

В предыдущем уроке детали авторизации были вставлены в код. На этот раз мы будем использовать файл twitter4j.properties . Это легко: просто добавьте файл с таким именем в каталог twitter4j-tutorial со следующим содержимым, подставив при необходимости ваши данные.

1
2
3
4
oauth.consumerKey=[your consumer key here]
oauth.consumerSecret=[your consumer secret here]
oauth.accessToken=[your access token here]
oauth.accessTokenSecret=[your access token secret here]

Ограничения скорости и предупреждение

В отличие от потокового доступа к Твиттеру, выполнение действий пользователя через API подчиняется ограничениям скорости. Как только вы достигнете своего предела, Twitter сгенерирует исключение и откажется выполнять ваши запросы, пока не пройдет период времени (обычно 15 минут). Twitter делает это, чтобы ограничить количество плохих ботов, а также сохранить их вычислительные ресурсы. Для получения дополнительной информации об ограничениях скорости см. Страницу Twitter об ограничении скорости .

Я расскажу о том, как управлять лимитами ставок позже в этом посте, но я упомяну их заранее, если вы превысите их, пока возитесь с вещами на раннем этапе.

Также необходимо предостеречь: поскольку вы сможете выполнять действия автоматически, такие как отслеживание пользователей, публикация статуса и ретвит, вы можете выполнить многие из этих действий в быстрой последовательности. Это (а) израсходует ваш лимит скорости очень быстро, (б) вероятно не будет интересным поведением, и (в) может привести к приостановке вашего аккаунта. Обязательно следуйте правилам, особенно тем, которые подписаны на следующих пользователей .

Если вы собираетесь возиться с реальными публикациями, возможно, вы захотите создать учетную запись, которая не является вашей основной учетной записью Twitter, чтобы вы не раздражали своих реальных подписчиков. (Предложение: см. Параграф «Создание учетной записи» в первой части первого этапа моего курса по Applied NLP, где приведены советы по добавлению нескольких учетных записей с одним и тем же адресом Gmail.)

Основные взаимодействия: поиск, сроки, размещение

Все приведенные ниже примеры реализованы в виде объектов с основными методами, которые делают что-то, используя объект twitter4j.Twitter . Чтобы сделать так, чтобы нам не приходилось многократно вызывать TwitterFactory , мы сначала определяем черту, которая устанавливает экземпляр Twitter и готов к использованию.

1
2
3
trait TwitterInstance {
  val twitter = new TwitterFactory().getInstance
}

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

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

1
2
3
4
5
6
7
8
object QuerySearch extends TwitterInstance {
 
  def main(args: Array[String]) {
    val statuses = twitter.search(new Query(args(0))).getTweets
    statuses.foreach(status => println(status.getText + '\n'))
  }
 
}

Обратите внимание, что для этого используется объект Query , тогда как при использовании TwitterStream был необходим FilterQuery . Кроме того, чтобы это работало, у нас должен быть доступен следующий импорт:

1
import collection.JavaConversions._

Это гарантирует, что мы можем использовать java.util.List, возвращаемый методом getTweets (из twitter4j.QueryResult ), как если бы это была коллекция Scala с методом foreach (и map, filter и т. Д.). Это делается с помощью неявных преобразований, которые делают работу с библиотеками Java гораздо приятнее, чем это было бы в противном случае.

Чтобы запустить это, перейдите в каталог twitter4j-tutorial и сделайте следующее (показан пример некоторых выходных данных):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
$ ./build
> run-main bcomposes.twitter.QuerySearch scala
[info] Running bcomposes.twitter.QuerySearch scala
E' avvilente non sentirsi all'altezza di qualcosa o qualcuno, se non si possiede quella scala interiore sulla quale l'autostima pu? issarsi
 
Scala workshop will run with ECOOP, July 2nd in Montpellier, South of France. Call for papers is out. http://t.co/3WS6tHQyiF
 
#scala http://t.co/JwNrzXTwm8 Even two of them in #cologne #germany . #thumbsup
 
RT @MILLIB2DAL: @djcameo Birthday bash 30th march @ Scala nightclub 100 artists including myself make sur u reach its gonna be #Legendary
 
@kot_2010 I think it's the same case with Scala: with macros it will tend to 'outsource' things to macro libs, keeping a small lang core.
 
RT @waxzce: #scala hiring or job ? go there : http://t.co/NeEjoqwqwT
 
@esten That's not only a front-end problem. Scala devs should use scalaz.Equal and === for type safe equality. /cc @sharonw
 
<...more...>
 
[success] Total time: 1 s, completed Feb 26, 2013 1:54:44 PM

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

Для каждого пользователя доступны различные временные графики, включая домашнюю временную шкалу, временную шкалу упоминаний и временную шкалу пользователя. Они доступны как twitter4j.api.TimelineResources . Например, следующий объект показывает самые последние статусы на домашней временной шкале аутентифицирующего пользователя (которые представляют собой твиты людей, за которыми следует пользователь).

1
2
3
4
5
6
7
8
9
object GetHomeTimeline extends TwitterInstance {
 
  def main(args: Array[String]) {
    val num = if (args.length == 1) args(0).toInt else 10
    val statuses = twitter.getHomeTimeline.take(num)
    statuses.foreach(status => println(status.getText + '\n'))
  }
 
}

Количество отображаемых твитов указывается в качестве аргумента командной строки.

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

1
2
3
4
5
object UpdateStatus extends TwitterInstance {
  def main(args: Array[String]) {
    twitter.updateStatus(new StatusUpdate(args(0)))
  }
}

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

Отвечая на твиты, написанные вам

Следующий объект просматривает самые последние твиты, в которых упоминается аутентифицирующий пользователь, и отвечает «ОК». им. Он включает в себя автора оригинального твита и любых других лиц, которые были упомянуты в нем.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
object ReplyOK extends TwitterInstance {
 
  def main(args: Array[String]) {
    val num = if (args.length == 1) args(0).toInt else 10
    val userName = twitter.getScreenName
    val statuses = twitter.getMentionsTimeline.take(num)
    statuses.foreach { status => {
      val statusAuthor = status.getUser.getScreenName
      val mentionedEntities = status.getUserMentionEntities.map(_.getScreenName).toList
      val participants = (statusAuthor :: mentionedEntities).toSet - userName
      val text = participants.map(p=>'@'+p).mkString(' ') + ' OK.'
      val reply = new StatusUpdate(text).inReplyToStatusId(status.getId)
      println('Replying: ' + text)
      twitter.updateStatus(reply)
    }}
  }
 
}

Это должно быть в основном самоочевидным, но есть несколько вещей, на которые стоит обратить внимание. Во-первых, вы можете найти все сущности, которые были упомянуты (через @ -mentions) в твите через метод getUserMentionEntities класса twitter4j.Status . Код гарантирует, что автор исходного твита (который не обязательно указан в нем) включен в качестве участника для ответа, а также мы убираем аутентифицирующего пользователя. Итак, если сообщение « @tshrdlu» Что вы думаете о @tshrdlc? »Отправлено с @jasonbaldridge, ответ будет« @jasonbaldridge @tshrdlc ОК. Обратите внимание, что на экранных именах нет символа @, поэтому их необходимо добавить в текст сообщения ответа.

Во-вторых, обратите внимание, что объекты StatusUpdate можно создавать с помощью методов цепочки, которые добавляют к ним больше информации, например, setInReplyToStatusId и setLocation , которые постепенно создают объект StatusUpdate, который фактически публикуется. (Это распространенная стратегия Java, которая в основном помогает обойти тот факт, что параметры для классов не могут быть указаны по имени в Java и не имеют значений по умолчанию, как это делает Scala.)

Проверка и управление информацией об ограничении скорости

Ни один из приведенных выше кодов не делает много запросов из Twitter, поэтому было мало опасности превышения ограничений скорости. Эти ограничения представляют собой смесь времени и количества запросов: вы в основном получаете определенное количество запросов каждый час (в настоящее время 350) на одного пользователя, проходящего проверку подлинности. Из-за этих ограничений вы должны рассмотреть доступ к твитам, временным шкалам и тому подобному, используя методы потоковой передачи, когда это возможно.

Каждый ответ, который вы получаете от Twitter, возвращается как подкласс twitter4j.TwitterResponse , который не только дает вам то, что вы хотите (например, QueryResult ), но также дает вам информацию о вашем подключении к Twitter. Для получения информации об ограничении скорости вы можете использовать метод getRateLimitStatus , который затем информирует вас о количестве запросов, которые вы можете сделать, и о времени, пока ваш предел не будет сброшен.

Признак RateChecker ниже имеет функцию checkAndWait, которая, когда передается объект TwitterResponse, проверяет, превышен ли лимит скорости, и ждет, если он был достигнут. Когда скорость превышена, он узнает, сколько времени осталось до сброса ограничения скорости, и заставляет поток спать до тех пор, пока не пройдет это время (плюс 10 секунд).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
trait RateChecker {
 
  def checkAndWait(response: TwitterResponse, verbose: Boolean = false) {
    val rateLimitStatus = response.getRateLimitStatus
    if (verbose) println('RLS: ' + rateLimitStatus)
 
    if (rateLimitStatus != null && rateLimitStatus.getRemaining == 0) {
      println('*** You hit your rate limit. ***')
      val waitTime = rateLimitStatus.getSecondsUntilReset + 10
      println('Waiting ' + waitTime + ' seconds ( ' + waitTime/60.0 + ' minutes) for rate limit reset.')
      Thread.sleep(waitTime*1000)
    }
  }
 
}

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

Также обратите внимание, что вы можете напрямую запросить информацию об ограничении скорости у самого экземпляра twitter4j.Twitter, используя метод getRateLimitStatus. В отличие от результатов для того же метода на TwitterResponse, это дает карту от различных типов запросов до текущих уровней ограничения скорости для каждого из них. В реальном приложении вы хотели бы контролировать каждый из этих различных пределов на более детальном уровне, используя эту информацию.

Не все методы классов Twitter4j действительно используют API Twitter. Чтобы увидеть, работает ли данный метод, посмотрите на его Javadoc: если в его описании написано « Этот метод вызывает http://api.twitter.com/1.1/some/method.json », то он попадает в API. В противном случае это не так, и вам не нужно охранять его.

Примеры использования функции checkAndWait приведены ниже.

Создание облака слов по описаниям подписчиков

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
object DescribeFollowers extends TwitterInstance with RateChecker {
 
  def main(args: Array[String]) {
    val screenName = args(0)
    val maxUsers = if (args.length==2) args(1).toInt else 500
    val followerIds = twitter.getFollowersIDs(screenName,-1).getIDs
 
    val descriptions = followerIds.take(maxUsers).flatMap { id => {
      val user = twitter.showUser(id)
      checkAndWait(user)
      if (user.isProtected) None else Some(user.getDescription)
    }}
 
    val tword = '''(?i)[a-z#@]+'''.r.pattern
    val words = descriptions.flatMap(_.toLowerCase.split('\\s+'))
    val filtered = words.filter(_.length > 3).filter(tword.matcher(_).matches)
    val counts = filtered.groupBy(x=>x).mapValues(_.length)
    val rankedCounts = counts.toSeq.sortBy(- _._2)
 
    import java.io._
    val wordcountFile = '/tmp/follower_wordcount.txt'
    val writer = new BufferedWriter(new FileWriter(wordcountFile))
    for ((w,c) <- rankedCounts)
      writer.write(w+':'+c+'\n')
    writer.flush
    writer.close
  }
 
}

Следует учитывать, что если вы указываете это на человека с несколькими сотнями подписчиков, вы превысите ограничение скорости. Вызов getFollowersIDs является одиночным обращением , а затем каждый вызов showUser является обращением . Поскольку вызовы showUser происходят быстро, мы проверяем состояние ограничения скорости после каждого, используя checkAndWait (который доступен, потому что мы смешали в признаке RateChecker), и он ожидает сброса ограничения, как обсуждалось ранее, не давая нам превышать скорость ограничение и получение исключения из Twitter.

Число пользователей, возвращаемых getFollowersID, составляет не более 5000. Если вы запустите это для пользователя, у которого есть больше подписчиков, подписчики за пределами 5000 не будут учитываться. Если вы хотите справиться с таким пользователем, вам нужно использовать курсор, который является целым числом, указанным в качестве аргумента для getFollowersIDs, и делать несколько вызовов при увеличении этого курсора, чтобы получить больше.

Большая часть остального кода — это просто стандартные Scala-компоненты для подсчета количества слов и их вывода в файл. Обратите внимание, что предпринимаются небольшие усилия для сокращения неалфавитных символов (но с разрешением # и @) и фильтрации коротких слов.

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

jasonbaldridge_wordcloud

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

Ретвит автоматически

Надоело читать эти твиты в своей хронологии и ретвитить некоторые из них? Следующий код получает некоторые учетные записи, за которыми следует аутентифицирующий пользователь, отбирает двадцать таких пользователей, фильтрует их, чтобы получить интересные, а затем берет до 10 оставшихся и ретвитит их последние статусы (при условии, что они не отвечают на запросы). кто-нибудь другой).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
object RetweetFriends extends TwitterInstance with RateChecker {
 
  def main(args: Array[String]) {
    val friendIds = twitter.getFriendsIDs(-1).getIDs
    val friends = friendIds.take(20).map { id => {
      val user = twitter.showUser(id)
      checkAndWait(user)
      user
    }}
 
    val filtered = friends.filter(admissable)
    val ranked = filtered.map(f => (f.getFollowersCount, f)).sortBy(- _._1).map(_._2)
 
    ranked.take(10).foreach { friend => {
      val status = friend.getStatus
      if (status!=null && status.getInReplyToStatusId == -1) {
        println('\nRetweeting ' + friend.getName + ':\n' + status.getText)
        twitter.retweetStatus(status.getId)
        Thread.sleep(30000)
      }
    }}
  }
 
  def admissable(user: User) = {
    val ratio = user.getFollowersCount.toDouble/user.getFriendsCount
    user.getFriendsCount < 1000 && ratio > 0.5
  }
 
}

Метод getFriendsIDs используется для получения пользователей, за которыми следует аутентифицирующий пользователь (но которые не обязательно следуют за аутентифицирующим пользователем, несмотря на использование слова «друг»). Мы снова берем на себя ограничение скорости сбора пользователей. Мы отфильтровываем этих пользователей, ища тех, кто подписывается на менее 1000 пользователей, и тех, у кого соотношение подписчиков и друзей больше 0,5, в простой попытке отфильтровать некоторые менее интересные (или спамовые) аккаунты. Остальные пользователи затем ранжируются в соответствии с их количеством подписчиков (большинство первых). Наконец, мы берем (до) 10 из них (метод take возвращает 3 вещи, если вы запрашиваете 10, но есть только 3), посмотрим на их последнее состояние, и если оно не нулевое и не является ответом на кто-то, мы ретвит это. Между каждым из них мы ждем 30 секунд, чтобы ни один из подписчиков нашего аккаунта не получил лавину ретвитов.

Вывод

Этот пост и связанный с ним код должны предоставить достаточно информации для работы с Twitter4j, включая необходимые настройки и использование некоторых методов, чтобы начать создавать приложения с его помощью в Scala. Посмотрите третью фазу проекта моего курса Applied NLP, чтобы увидеть упражнения и код, которые позволяют проделать интересную работу для автоматизированных ботов, включая смешивание потокового доступа и доступа пользователей для получения более сложных вариантов поведения.

Ссылка: Использование Twitter4j со Scala для выполнения действий пользователя от нашего партнера по JCG Джейсона Болдриджа в блоге Bcomposes .