Advertisement
  1. Code
  2. Twitter

Construire avec l'API Twitter: Analyser vos suiveurs

Scroll to top
Read Time: 13 min
This post is part of a series called Building With the Twitter API.
Building With the Twitter API: Creating Friends to Follow
Using the Twitter API to Tweet Repetitive Content

() translation by (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Introduction

Bienvenue dans le dernier épisode de notre série API Twitter. Dans notre dernier épisode, j'ai créé Twixxr.com qui vous permettra de découvrir des femmes influentes sur Twitter pour votre compte à suivre. Aujourd'hui, je vais mettre l'accent vers l'intérieur pour regarder mes propres suiveurs.

Bien que je n'utilise pas vraiment Facebook depuis 2013, je suis restée active sur Twitter—même s'ils ont alimenté mes annonces et m'ont agacé en essayant de l'optimiser par algorithme.

Récemment, j'ai été vérifié et j'ai commencé à rassembler des abonnés à un rythme légèrement plus rapide. J'avais l'espoir que je pourrais voir plus de réponse à mes tweets. D'une manière générale, j'ai été surpris de la faible réponse qu'il y a sur Twitter pour la personne moyenne.

Building With the Twitter API reifman profile on twitter with verified checkmarkBuilding With the Twitter API reifman profile on twitter with verified checkmarkBuilding With the Twitter API reifman profile on twitter with verified checkmark

J'ai près de 1,900 suiveurs, mais rarement les gens commentent ou retweet des pièces qui me semblent importantes et d'intérêt général. Par exemple, aucune personne n'a partagé mon morceau sur la forte pointe des rapports de viol à Seattle ou le commentaire de Bill Gates à son plus scandaleusement hypocrite.

Pendant longtemps, j'ai voulu regarder de plus près mes suiveurs Twitter et répondre à certaines questions: Qui me suit exactement? Et pourquoi ne sont-ils pas plus interactifs? Est-il possible que seulement 10% de mes suiveurs soient des personnes réelles?

Twitter a eu du mal à trouver un acheteur, et peut-être que cela a quelque chose à voir avec cela.

L'API de Twitter est un bon outil pour y enquêter. Pourtant, il a une tonne de limites de taux qui rendent même quelque chose de simple comme l'analyse de vos adeptes assez complexe. Dans l'épisode d'aujourd'hui, je vais vous montrer comment j'ai travaillé avec les limites de taux pour évaluer et construire un tableau de bord de mes suiveurs.

Si vous avez des questions ou des commentaires, postez-les ci-dessous dans les commentaires ou contactez-moi sur Twitter @reifman.

Analyser nos suiveurs de Twitter

Building With the Twitter API Initial scoreboardBuilding With the Twitter API Initial scoreboardBuilding With the Twitter API Initial scoreboard

Juste au-dessus, vous pouvez voir le tableau de bord de base que j'ai créé. L'épisode d'aujourd'hui se concentrera principalement sur l'infrastructure et l'approche que j'ai prises pour créer cela. J'espère avoir la possibilité d'en apprendre davantage sur l'amélioration du mécanisme de notation.

Et oui, comme vous le voyez ci-dessus, le célèbre champion des droits des homosexuels et le conseiller sexuel, Dan Savage, me suit, mais ne retweet jamais tout ce que je partage. S'il y a du temps aujourd'hui, nous analyserons ceci pour répondre à des questions importantes comme: est-il réel, un robot ou simplement me suit pour des conseils sexuels personnels? Que pouvons-nous apprendre de son compte pour déterminer s'il est susceptible d'interagir avec moi sur Twitter ou, d'ailleurs, avec l'un de mes autres suiveurs?

Le code du tableau de bord est principalement un prototype que j'ai construit au-dessus du code Twixxr du dernier épisode, mais ce n'est pas une démonstration en direct pour les utilisateurs. Je partage afin que vous puissiez apprendre de cela et construire vous-même.

Voici les éléments de base du code:

  • Création de la base de données pour stocker mes adeptes et les données connexes.
  • Télécharger mes adeptes dans les pages de 20 abonnés.
  • Suivre les curseurs pour les pages en téléchargeant une fenêtre limitée de 15 pages par taux.
  • Stockage des données collectées sur mes adeptes dans la base de données.
  • Construire un prototype d'algorithme de notation pour marquer tous les adeptes.
  • Création d'une vue pour parcourir le tableau de bord.

Plonger dans le code

Création des migrations de table de base de données

J'ai créé trois tables différentes pour stocker toutes les données et m'aider à travailler avec la limitation du taux d'API de Twitter. Si vous ne connaissez pas les migrations de la base de données Yii, consultez la section Comment programmer avec Yii2: Utilisation de la base de données et de l'enregistrement actif.

Tout d'abord, j'ai étendu la table SocialProfile pour enregistrer beaucoup plus de données sur les comptes du suiveur, qu'il s'agisse de vérification, de localisation et de nombre d'éléments qu'ils ont favorisés:

1
<?php
2
use yii\db\Schema;
3
use yii\db\Migration;
4
5
class m161026_221130_extend_social_profile_table extends Migration
6
{
7
        public function up()
8
        {
9
          $tableOptions = null;
10
          if ($this->db->driverName === 'mysql') {
11
              $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
12
          }
13
          $this->addColumn('{{%social_profile}}','social_id',Schema::TYPE_STRING.' NOT NULL');
14
          $this->addColumn('{{%social_profile}}','name','string NOT NULL');
15
          $this->addColumn('{{%social_profile}}','screen_name',Schema::TYPE_STRING.' NOT NULL');
16
          $this->addColumn('{{%social_profile}}','description',Schema::TYPE_TEXT.' NOT NULL');
17
          $this->addColumn('{{%social_profile}}','url',Schema::TYPE_STRING.' NOT NULL');
18
          $this->addColumn('{{%social_profile}}','protected',Schema::TYPE_SMALLINT. ' NOT NULL DEFAULT 0');
19
          $this->addColumn('{{%social_profile}}','favourites_count',Schema::TYPE_BIGINT. ' NOT NULL DEFAULT 0');
20
          $this->addColumn('{{%social_profile}}','verified',Schema::TYPE_SMALLINT. ' NOT NULL DEFAULT 0');
21
          $this->addColumn('{{%social_profile}}','location',Schema::TYPE_STRING.' NOT NULL');
22
          $this->addColumn('{{%social_profile}}','profile_location',Schema::TYPE_STRING.' NOT NULL');
23
          $this->addColumn('{{%social_profile}}','score',Schema::TYPE_BIGINT. ' NOT NULL DEFAULT 0');
24
        }

Ensuite, j'ai construit une table d'indexation appelée SocialFriend pour suivre les utilisateurs pour des comptes spécifiques. Si je décide de formaliser ce service publiquement, j'aurai besoin de cela. Il associe la table User aux adeptes de l'utilisateur dans la table SocialProfile.

1
<?php
2
  use yii\db\Schema;
3
  use yii\db\Migration;
4
class m161026_233916_create_social_friend_table extends Migration
5
{
6
   public function up()
7
   {
8
       $tableOptions = null;
9
       if ($this->db->driverName === 'mysql') {
10
           $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
11
       }
12
13
       $this->createTable('{{%social_friend}}', [
14
           'id' => Schema::TYPE_PK,
15
           'user_id' => Schema::TYPE_BIGINT.' NOT NULL',
16
           'social_profile_id' => Schema::TYPE_BIGINT.' NOT NULL',
17
       ], $tableOptions);
18
   }

Ensuite, l'API de Twitter exige que vous puissiez passer des demandes de 20 abonnés à la fois. Pour connaître la page suivante, vous devez suivre les curseurs, essentiellement les étiquettes, qui marquent la page suivante à récupérer.

Puisque vous n'êtes autorisé à faire que 15 demandes d'adeptes toutes les 15 minutes, vous devez mémoriser ces curseurs dans la base de données. Le tableau s'appelle SocialCursor:

1
<?php
2
  use yii\db\Schema;
3
  use yii\db\Migration;
4
  class m161027_001026_social_cursor_table extends Migration
5
{
6
   public function up()
7
   {
8
       $tableOptions = null;
9
       if ($this->db->driverName === 'mysql') {
10
           $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
11
       }
12
13
       $this->createTable('{{%social_cursor}}', [
14
           'id' => Schema::TYPE_PK,
15
           'user_id' => Schema::TYPE_BIGINT.' NOT NULL',
16
           'next_cursor' => Schema::TYPE_STRING.' NOT NULL',
17
       ], $tableOptions);
18
   }

Finalement, je vais créer des tâches cronales de fond pour gérer tout cela, mais pour le prototype d'aujourd'hui, je cours ces tâches à la main.

Collecte des utilisateurs et leurs données de compte

Ensuite, j'ai créé une méthode Twitter::getFollowers() pour faire la demande. Voici les bases du code:

1
public function getFollowers($user_id) {
2
  $sp = new SocialProfile();
3
  $next_cursor = SocialCursor::getCursor($user_id);
4
  ...
5
  while ($next_cursor>0) {
6
      $followers = $this->connection->get("followers/list",['cursor'=>$next_cursor]);
7
      if ($this->connection->getLastHttpCode() != 200) {
8
        var_dump($this->connection);
9
        exit;
10
      }
11
        if (isset($followers->users)) {
12
          foreach ($followers->users as $u) {
13
            $n+=1;
14
            $users[]=$u;
15
            $sp->add($user_id,$u);        
16
          }
17
        $next_cursor= $followers->next_cursor;
18
        SocialCursor::refreshCursor($user_id,$next_cursor);
19
        echo $next_cursor.'<br />';
20
        echo '======================================================<br />';
21
      } else {
22
        exit;
23
      }
24
  }

Il obtient le next_cursor et demande à plusieurs reprises des adeptes, $followers = $this->connection->get("followers/list",['curseur'=>$next_cursor]), jusqu'à ce qu'il atteigne les limites de taux.

La sortie ressemble à quelque chose comme cela se produit dans chaque page de 20 résultats:

1
refresh cursor: 1489380833827620370
2
======================================================
3
refresh cursor: 1488086367811119559
4
======================================================
5
refresh cursor: 1486452899268510188
6
======================================================
7
refresh cursor: 1485593015909209633
8
======================================================
9
refresh cursor: 1485330282069552137
10
======================================================
11
refresh cursor: 1485256983607000799
12
======================================================
13
refresh cursor: 1484594012550322889
14
======================================================
15
refresh cursor: 1483359799854574028
16
======================================================
17
refresh cursor: 1481615590678791493
18
======================================================
19
refresh cursor: 1478424827838161031
20
======================================================
21
refresh cursor: 1477449626282716582
22
======================================================
23
refresh cursor: 1475751176809638917
24
======================================================
25
refresh cursor: 1473539961706830585
26
======================================================
27
refresh cursor: 1471375035531579849
28
======================================================

Les données sont stockées par ces $sp->add($ user_id,$u);  méthodes. La méthode SocialProfile::add() est une version différente de la méthode fill() du tutoriel Twixxr. Il stocke plus de données et gère l'indice SocialFriend:

1
public static function add($user_id,$profileObject=null) {
2
      $sp = SocialProfile::find()
3
        ->where(['social_id'=>$profileObject->id_str])
4
        ->one();
5
      if (!isset($profileObject->name) || empty($profileObject->name)) {
6
        $profileObject->name='Nameless';
7
      }
8
      if (!isset($profileObject->url) || empty($profileObject->url)) {
9
        $profileObject->url='';
10
      }
11
      if (!isset($profileObject->screen_name) || empty($profileObject->screen_name)) {
12
        $profileObject->screen_name='error_sn';
13
      }
14
      if (!isset($profileObject->description) || empty($profileObject->description)) {
15
        $profileObject->description='(empty)';
16
      }
17
      if (!isset($profileObject->profile_location) || empty($profileObject->profile_location)) {
18
        $profileObject->profile_location='';
19
      }
20
      if (!isset($profileObject->profile_image_url_https) || empty($profileObject->profile_image_url_https)) {
21
        $profileObject->profile_image_url_https='';
22
      }
23
      if (!is_null($sp)) {
24
        $sp->social_id = $profileObject->id;
25
        $sp->image_url = $profileObject->profile_image_url_https;
26
        $sp->follower_count= $profileObject->followers_count;
27
        $sp->status_count = $profileObject->statuses_count;
28
        $sp->friend_count = $profileObject->friends_count;
29
        $sp->listed_in = $profileObject->listed_count;
30
        $sp->url=$profileObject->url;
31
        if ($profileObject->protected) {
32
            $sp->protected=1;
33
        } else {
34
          $sp->protected=0;
35
        }
36
        if ($profileObject->verified) {
37
            $sp->verified=1;
38
        } else {
39
          $sp->verified=0;
40
        }
41
        $sp->favourites_count=$profileObject->favourites_count;
42
        $sp->location=$profileObject->location;
43
        $sp->profile_location=$profileObject->profile_location;
44
        $sp->name = $profileObject->name;
45
        $sp->description = $profileObject->description;
46
        $sp->image_url = $profileObject->profile_image_url_https;
47
        if ($sp->validate()) {
48
            $sp->update();
49
        } else {
50
          var_dump($sp->getErrors());
51
        }
52
      } else {
53
        $sp = new SocialProfile();
54
        $sp->social_id = $profileObject->id;
55
        $sp->score = 0;
56
        $sp->header_url='';
57
        $sp->url=$profileObject->url;
58
        $sp->favourites_count=$profileObject->favourites_count;
59
        if ($profileObject->protected) {
60
            $sp->protected=1;
61
        } else {
62
          $sp->protected=0;
63
        }
64
        if ($profileObject->verified) {
65
            $sp->verified=1;
66
        } else {
67
          $sp->verified=0;
68
        }     $sp->location=$profileObject->location;
69
        $sp->profile_location=$profileObject->profile_location;
70
        $sp->name = $profileObject->name;
71
        $sp->description = $profileObject->description;
72
        $sp->screen_name = $profileObject->screen_name;
73
        $sp->image_url = $profileObject->profile_image_url_https;
74
        $sp->follower_count= $profileObject->followers_count;
75
        $sp->status_count = $profileObject->statuses_count;
76
        $sp->friend_count = $profileObject->friends_count;
77
        $sp->listed_in = $profileObject->listed_count;
78
        if ($sp->validate()) {
79
            $sp->save();
80
        } else {
81
          var_dump($sp->getErrors());
82
        }
83
      }
84
      $sf = SocialFriend::find()
85
        ->where(['social_profile_id'=>$sp->id])
86
        ->andWhere(['user_id'=>$user_id])
87
        ->one();
88
      if (is_null($sf)) {
89
          $sf = new SocialFriend();
90
          $sf->user_id = $user_id;
91
          $sf->social_profile_id = $sp->id;
92
          $sf->save();
93
      }
94
      return $sp->id;
95
    }

Il est écrit pour enregistrer de nouveaux enregistrements ou mettre à jour les anciens enregistrements afin que vous puissiez suivre à l'avenir vos données suiveuses et les mettre à jour régulièrement, en écrasant les anciennes données.

Cette dernière section à la fin permet de s'assurer qu'il existe un index de l'indice SocialFriend entre la table User et la table SocialProfile.

1
$sf = SocialFriend::find()
2
        ->where(['social_profile_id'=>$sp->id])
3
        ->andWhere(['user_id'=>$user_id])
4
        ->one();
5
      if (is_null($sf)) {
6
          $sf = new SocialFriend();
7
          $sf->user_id = $user_id;
8
          $sf->social_profile_id = $sp->id;
9
          $sf->save();
10
      }

Marquant les adeptes de Twitter

Building With the Twitter API - Accounts with huge numbers of friends unlikely to read my tweetsBuilding With the Twitter API - Accounts with huge numbers of friends unlikely to read my tweetsBuilding With the Twitter API - Accounts with huge numbers of friends unlikely to read my tweets

J'ai eu une poignée de buts pour mes notations Twitter:

  • Éliminez les comptes qui suivent tout le monde qui les suit. Par exemple, ils ont 12 548 abonnés et suivent 12 392 personnes (voir ci-dessus).
  • Éliminez les comptes qui suivent plus que 1 500 comptes qui ne risquent pas de voir ce que je partage. Par exemple, Dan Savage suit 1.536 personnes.
  • Éliminez les comptes qui ont très peu de messages ou très peu de comptes qu'ils suivent, probablement des comptes abandonnés.
  • Éliminez les comptes avec quelques favoris—ce sont probablement des robots, n'utilisant pas vraiment l'application.

De même, je voulais souligner certains aspects positifs:

  • Comptes vérifiés
  • Comptes qui ont beaucoup de suiveurs
  • Les comptes qui ont moins de 1000 personnes qu'ils suivent—un bon endroit pour moi

Voici un code de base brut de SocialProfile::score() qui souligne certains des points positifs:

1
foreach ($all as $sp) {
2
        // score sp

3
        $score =0;
4
        // RULE IN

5
        if ($sp->verified==1) {
6
          $score+=1000;
7
        }
8
        // POSITIVE

9
        if ($sp->protected==1) {
10
          $score+=500;
11
        }
12
        if ($sp->follower_count > 10000) {
13
          $score+=500;
14
        } else if ($sp->follower_count > 3500) {
15
          $score+=750;
16
        } else if ($sp->follower_count > 1100) {
17
          $score+=1000;
18
        }  else if ($sp->follower_count > 1000) {
19
          $score+=250;
20
        } else if ($sp->follower_count> 500) {
21
          $score+=250;
22
        }

Voici un code qui élimine certains des comptes défectueux:

1
  // RULE OUT

2
        // make this a percentage of magnitude

3
        $magnitude = $sp->follower_count/1000;
4
        if ($sp->follower_count> 1000 and abs($sp->follower_count-$sp->friend_count)<$magnitude) {
5
          $score-=2500;
6
        }
7
        if ($sp->friend_count > 7500) {
8
            $score-=10000;
9
        } else
10
        if ($sp->friend_count > 5000) {
11
            $score-=5000;
12
        }
13
         else if ($sp->friend_count > 2500) {
14
            $score-=2500;
15
        }else  if ($sp->friend_count > 2000) {
16
           $score-=2000;
17
       } else if ($sp->friend_count > 1000) {
18
            $score-=250;
19
        } else if ($sp->friend_count > 750) {
20
          $score-=100;
21
        }
22
        if ($sp->follower_count<100) {
23
          $score-=1000;
24
        }
25
        if ($sp->status_count < 35) {
26
          $score-=5000;
27
        }

De toute évidence, il y a beaucoup de choses à jouer ici et diverses façons d'améliorer cela. J'espère avoir une chance de passer plus de temps à ce sujet.

Au fur et à mesure que la méthode fonctionne, cela ressemble à ceci, mais met à jour la table de travail social avec les scores en cours:

1
DJMany -6300
2
gai_ltau -7850
3
Michal92B -900
4
InvestmentAdvsr -2900
5
TSSStweets -7500
6
sandcageapp -1750
7
dominicpouzin 1950
8
daletdykaaolch1 -7850
9
suzamack -8250
10
writingthrulife -7500
11
ryvr -1550
12
RichardAngwin -8300
13
DanielleMorrill -7300
14
ReversaCreates 2750
15
BoKnowsMarkting -7500
16
TheHMProA -8500
17
HouseMgmt101 750
18
itsmeKennethG -1250
19
drbobbiwegner -8500
20
Mizzfit_Bianca -7300
21
wilsonmar 700
22
CoachVibeke -7300
23
jhurwitz 0
24
PiedPiperComms 500
25
Prana2thePeople -1100
26
singlemomspower -2250
27
mouselink -7300
28
MotivatedGenY -7300
29
brett7three -7300
30
JovanWalker 2950
31
ITSPmagazine 450
32
RL_Miller -2250

Affichage du tableau de bord

La grille par défaut de Yii rend très simple d'afficher la table SocialProfile et de personnaliser les colonnes du tableau de bord.

Voici SocialProfileController::actionIndex():

1
/**

2
     * Lists all SocialProfile models.

3
     * @return mixed

4
     */
5
    public function actionIndex()
6
    {
7
        $searchModel = new SocialProfileSearch();
8
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
9
10
        return $this->render('index', [
11
            'searchModel' => $searchModel,
12
            'dataProvider' => $dataProvider,
13
        ]);
14
    }

Et voici la vue de grille personnalisée:

1
<?php
2
use yii\helpers\Html;
3
use yii\grid\GridView;
4
use yii\widgets\Pjax;
5
/* @var $this yii\web\View */
6
/* @var $searchModel frontend\models\SocialProfileSearch */
7
/* @var $dataProvider yii\data\ActiveDataProvider */
8
$this->title = Yii::t('frontend', 'Social Profiles');
9
$this->params['breadcrumbs'][] = $this->title;
10
?>
11
<div class="social-profile-index">
12
    <h1><?= Html::encode($this->title) ?></h1>
13
    <?php // echo $this->render('_search', ['model' => $searchModel]); ?>

14
<?php Pjax::begin(); ?>    <?= GridView::widget([
15
        'dataProvider' => $dataProvider,
16
        'filterModel' => $searchModel,
17
        'columns' => [
18
            ['class' => 'yii\grid\SerialColumn'],
19
            [
20
            'label'=>'Name',
21
              'format' => 'raw',
22
              'value' => function ($model) {
23
                      return '<div><span><strong><a href="http://twitter.com/'.$model->screen_name.'">'.$model->name.'</a></strong><br />'.$model->screen_name.'</span></div>';
24
                  },
25
          ],
26
            'score',
27
            [
28
            'label'=>'Follows',
29
              'format' => 'raw',
30
              'attribute'=>'friend_count',
31
            ],
32
            [
33
            'label'=>'Followers',
34
              'format' => 'raw',
35
              'attribute'=>'follower_count',
36
            ],
37
            [
38
            'label'=>'Tweets',
39
              'format' => 'raw',
40
              'attribute'=>'status_count',
41
            ],
42
            [
43
            'label'=>'Favs',
44
              'format' => 'raw',
45
              'attribute'=>'favourites_count',
46
            ],
47
            [
48
            'label'=>'Listed',
49
              'format' => 'raw',
50
              'attribute'=>'listed_in',
51
            ],
52
            [
53
            'label'=>'P',
54
              'format' => 'raw',
55
              'attribute'=>'protected',
56
            ],
57
          [
58
          'label'=>'V',
59
            'format' => 'raw',
60
            'attribute'=>'verified',
61
        ],
62
63
            // 'location',

64
            // 'profile_location',

65
            [
66
            //'contentOptions' => ['class' => 'col-lg-11 col-xs-10'],

67
            'label'=>'Pic',
68
              'format' => 'raw',
69
              'value' => function ($model) {
70
                      return '<div><span><img src="'.$model->image_url.'"></span></div>';
71
                  },
72
          ],
73
        ],
74
    ]); ?>
75
<?php Pjax::end(); ?></div>

Voici à quoi ressemblent les meilleurs résultats avec mon algorithme initial:

Building With the Twitter API - Top Scoring AccountsBuilding With the Twitter API - Top Scoring AccountsBuilding With the Twitter API - Top Scoring Accounts

Il existe de nombreuses façons d'améliorer et d'accorder la notation. Je suis impatient de jouer avec plus.

Et il y a plus que j'aimerais écrire du code et élargir mon utilisation de l'API, par exemple:

  • Utilisez le genre PHP pour aider à éliminer les entreprises des personnes (les entreprises n'interagissent pas beaucoup).
  • Recherchez la fréquence des publications que les gens ont faites et la dernière fois qu'ils ont utilisé Twitter.
  • Utilisez l'API de recherche de Twitter pour voir quels utilisateurs ont réellement interagi avec mon contenu.
  • Fournissez des commentaires à la notation pour l'accorder.

Regarder vers l'avant

J'espère que l'approche de notation est intrigante. Il y a tellement plus de choses qui peuvent être faites pour améliorer cela. N'hésitez pas à jouer avec vous et à publier vos idées ci-dessous.

Si vous avez des questions ou des suggestions, veuillez les publier dans les commentaires. Si vous souhaitez suivre mes futurs tutoriels Envato Tuts+ et d'autres séries, visitez ma page d'instructeur ou suivez @reifman. Vérifiez définitivement ma série de démarrage et Meeting Planner.

Liens connexes

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.