[PYTHON] Kontinuierliche Erfassung durch Twitter API (Tipps)

Wie Sie wissen, ist die Anzahl der Tweets, die Sie in einem bestimmten Zeitraum erhalten, und die Anzahl der Tweets, die Sie gleichzeitig erhalten, begrenzt, wenn Sie Tweets mit der Twitter-API erhalten.

Um mit dieser Einschränkung gut umzugehen, finden Sie hier einige Tipps, wie Sie sie kontinuierlich erhalten können. Bitte beachten Sie, dass dieser Beitrag POST nicht abdeckt. (Obwohl es fast das gleiche ist)

Bitte überprüfen Sie die primären Informationen auf numerische Werte. Der Beispielcode lautet auf Gist veröffentlicht. (Ich habe gehört, dass es ein Twitter-Paket gibt, aber ich benutze es nicht.)

Ratenlimits für Twitter-APIs (Limits für die Anzahl der Akquisitionen innerhalb eines bestimmten Zeitraums)

Die Grenzen sind wie folgt.

GET endpoints

The standard API rate limits described in this table refer to GET (read) endpoints. Note that endpoints not listed in the chart default to 15 requests per allotted user. All request windows are 15 minutes in length.  These rate limits apply to the standard API endpoints only, does not apply to premium APIs.

Auszug aus Ratenlimits - Twitter-Entwickler

Die Anzahl der Akquisitionen in 15-Minuten-Einheiten ist begrenzt. Laut dieser Seite gelten beispielsweise die Einschränkungen für "Suche / Tweets" wie folgt (oh, Standard).

Endpoint Resource family Requests / window (user auth) Requests / window (app auth)
GET search/tweets search 180 450
Auszug aus [Ratenlimits - Twitter-Entwickler](https://developer.twitter.com/de/docs/basics/rate-limits)

Restriktionsstatus abrufen

Den aktuellen Grenzwertstatus als Endpunkt erhalten Sie unter https://api.twitter.com/1.1/application/rate_limit_status.json. Der Parameter "Ressourcen" ist optional und gibt "Ressourcenfamilie" an.

Beispiel für erhaltene Informationen

user auth (OAuth v1.1)


{
  "rate_limit_context": {
    "access_token": "*******************"
  },
  "resources": {
    "search": {
      "/search/tweets": {
        "limit": 180,
        "remaining": 180,
        "reset": 1591016735
      }
    }
  }
}

app auth (OAuth v2.0)


{
  "rate_limit_context": {
    "application": "dummykey"
  },
  "resources": {
    "search": {
      "/search/tweets": {
        "limit": 450,
        "remaining": 450,
        "reset": 1591016736
      }
    }
  }
}

Jede der "Reset" -Nummern ist die Epochenzeit, die die zurückzusetzende Zeit angibt.

Im obigen Beispiel "Benutzerauthentifizierung" (OAuth v1.1) mit Epochenzeit "1591016735" = "2020-06-01 22: 05: 35", "Appauthentifizierung" (OAuth v2.0) mit "1591016736" = Zeigt an, dass es auf "2020-06-01 22: 05: 36" zurückgesetzt wird.

Wenn Sie das Limit überschreiten, lautet die Zahl für "verbleibend" "0".

Elemente organisieren

Die Elemente der erfassten Informationen sind wie folgt. (Beispiel einer Suchfamilie)

Category Family Endpoint Key Value
rate_limit_context access_token (user auth (v1.1)) Inhalt des Zugriffstokens
application (app auth (v2.0)) dummykey (Scheint behoben zu sein)
resources
search
/search/tweets
limit Maximale Anzahl von Malen innerhalb des Zeitlimits
remaining Verbleibende Anzahl von Malen, auf die innerhalb des Zeitlimits zugegriffen werden kann
reset Zeitpunkt, zu dem das Zeitlimit zurückgesetzt wird(epoch time)

Fall, in dem die falsche Ressourcenfamilie angegeben ist

Das folgende Beispiel ist, wenn Sie fälschlicherweise "Benutzer" für die Ressourcenfamilie angeben. (Eigentlich sollten Sie "Benutzer" (mit s) angeben.)

user auth


{
  "rate_limit_context": {
    "access_token": "*******************"
  }
}

app auth


{
  "rate_limit_context": {
    "application": "dummykey"
  }
}

Beide geben " rate_limit_context "zurück, haben aber keine" resources".

Antwort bei Ratenlimitfehler (Erfassungsergebnis)

Wenn ein Ratenlimitfehler auftritt, wird 429 in "res.status_code" (HTTP-Statuscode) zurückgegeben. (420 können zurückgegeben werden [^ 1].)

[^ 1]: Im Allgemeinen wird 429 "Zu viele Anfragen: Wird zurückgegeben, wenn eine Anfrage nicht bearbeitet werden kann, weil das Ratenlimit der App für die Ressource erschöpft ist. "" Wird zurückgegeben, in seltenen Fällen jedoch 420 "Enhance Your Ruhe: Wird zurückgegeben, wenn die Rate einer App für zu viele Anfragen begrenzt ist." Kann zurückgegeben werden. Letzteres kann passieren, wenn Sie versehentlich mehrere Anfragen gleichzeitig stellen (nicht überprüft). → In "Herstellen einer Verbindung zu einem Streaming-Endpunkt - Twitter-Entwickler" wurde eine Erklärung hinzugefügt, die ich dem Text hinzufügte. (2020/06/03).

Code Text Description
420 Enhance Your Calm Returned when an app is being rate limited for making too many requests.
429 Too Many Requests Returned when a request cannot be served due to the app's rate limit having been exhausted for the resource. See Rate Limiting.
Auszug aus [Antwortcodes - Twitter-Entwickler](https://developer.twitter.com/de/docs/basics/response-codes)

[Aktualisiert am 06/03/2020] Es gab eine detaillierte Erklärung von 420 unten.

420 Rate Limited

The client has connected too frequently. For example, an endpoint returns this status if:

  • A client makes too many login attempts in a short period of time.
  • Too many copies of an application attempt to authenticate with the same credentials.
Auszug aus [Verbindung zu einem Streaming-Endpunkt herstellen - Twitter-Entwickler](https://developer.twitter.com/de/docs/tweets/filter-realtime/guides/connecting)

88 wird in den JSON-Fehlercode eingegeben.

{
  "errors": [
    {
      "code": 88,
      "message": "Rate limit exceeded"
    }
  ]
}
Code Text Description
88 Rate limit exceeded Corresponds with HTTP 429. The request limit for this resource has been reached for the current rate limit window.
Auszug aus [Antwortcodes - Twitter-Entwickler](https://developer.twitter.com/de/docs/basics/response-codes)

Ausnahmen wie "Anfragen" finden Sie auf jeder Website.

Prozessablauf

Berücksichtigt der Verarbeitungsablauf das Ratenlimit wie folgt?

while True:
    try:
        res =Anfrage an API erhalten/post
        res.raise_for_status()
    except requests.exceptions.HTTPError:
        #429 wenn das Ratenlimit erreicht ist/420 wird zurückgegeben
        if res.status_code in (420, 429):
Informationen zum Ratenlimit erhalten ← Hier
Warten Sie ruhig bis zum Zurücksetzen
            continue
        420/Andere Ausnahmebehandlung als 429
    except OtherException:
Ausnahmebehandlung

Verarbeitung, wenn es erfolgreich erworben werden kann
brechen oder zurückkehren oder nachgeben etc.

Das Folgende ist eine konkrete Maßnahme für den Teil "Get Rate Limit Information".

Beispiel für das Abrufen von Einschränkungsinformationen

GetTweetStatus-Klasse

Als Beispiel für die Informationserfassung gibt es nicht viel Verdienst, sie in einer Klasse zu implementieren, aber wenn man bedenkt, dass sie tatsächlich in ein Programm integriert wird, ist es meiner Meinung nach besser, sie in einer Form zu schreiben, die einfach zu modularisieren ist. Deshalb habe ich sie zu einer Klasse namens GetTweetStatus gemacht. Es gibt. (Es besteht auch der Wunsch, den Zugriff von außen wie Apikey und Bearer so weit wie möglich zu vermeiden ...)

class GetTweetStatus


    def __init__(self, apikey, apisec, access_token="", access_secret=""):
        self._apikey = apikey
        self._apisec = apisec
        self._access_token = access_token
        self._access_secret = access_secret
        self._access_token_mask = re.compile(r'(?P<access_token>"access_token":)\s*".*"')

Die letzte Zeile, re.compile (), dient zum Maskieren der Anzeige des empfangenen "access_token".

Erwerbsteil

user auth (OAuth v1.1)

GetTweetStatus.get_limit_status_v1(&nbsp;)


    def get_limit_status_v1(self, resource_family="search"):
        """OAuth v1.Erhalten Sie den Status mit 1"""

        #Verwenden Sie OAuth1Session, da OAuth kompliziert ist
        oauth1 = OAuth1Session(self._apikey, self._apisec, self._access_token, self._access_secret)

        params = {
            'resources': resource_family  # help, users, search, statuses etc.
        }

        try:
            res = oauth1.get(STATUS_ENDPOINT, params=params, timeout=5.0)
            res.raise_for_status()
        except (TimeoutError, requests.ConnectionError):
            raise requests.ConnectionError("Cannot get Limit Status")
        except Exception:
            raise Exception("Cannot get Limit Status")
        return res.json()

app auth (OAuth v2.0)

GetTweetStatus.get_limit_status_v2(&nbsp;)


    def get_limit_status_v2(self, resource_family="search"):
        """OAuth v2.0 (Bearer)Status abrufen mit"""
        bearer = self._get_bearer() #Holen Sie sich Träger

        headers = {
            'Authorization':'Bearer {}'.format(bearer),
            'User-Agent': USER_AGENT
        }
        params = {
            'resources': resource_family  # help, users, search, statuses etc.
        }

        try:
            res = requests.get(STATUS_ENDPOINT, headers=headers, params=params, timeout=5.0)
            res.raise_for_status()
        except (TimeoutError, requests.ConnectionError):
            raise requests.ConnectionError("Cannot get Limit Status")
        except Exception:
            raise Exception("Cannot get Limit Status")
        return res.json()
  1. Holen Sie sich den Inhaber-Token am Anfang und
  2. Stellen Sie dies in der Kopfzeile ein.
  3. Legen Sie die Ressourcenfamilie als Parameter fest
  4. Senden an STATUS_ENDPOINT.

Trägergeneration

bearer = self._get_bearer () Dies ist der _get_bearer () Teil, der von #Get Bearer aufgerufen wird.

GetTweetStatus._get_bearer(&nbsp;),&nbsp;_get_credential(&nbsp;)


    def _get_bearer(self):
        """Holen Sie sich Träger"""
        cred = self._get_credential()
        headers = {
            'Authorization': 'Basic ' + cred,
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
            'User-Agent': USER_AGENT
            }
        data = {
            'grant_type': 'client_credentials',
            }

        try:
            res = requests.post(TOKEN_ENDPOINT, data=data, headers=headers, timeout=5.0)
            res.raise_for_status()
        except (TimeoutError, requests.ConnectionError):
            raise Exception("Cannot get Bearer")
        except requests.exceptions.HTTPError:
            if res.status_code == 403:
                raise requests.exceptions.HTTPError("Auth Error")
            raise requests.exceptions.HTTPError("Other Exception")
        except Exception:
            raise Exception("Cannot get Bearer")
        rjson = res.json()
        return rjson['access_token']

    def _get_credential(self):
        """Anmeldeinformationen generieren"""
        pair = self._apikey + ':' + self._apisec
        bcred = b64encode(pair.encode('utf-8'))
        return bcred.decode()
  1. Kombinieren Sie APIKEY und APISEC für die Base64-Codierung
  2. Setzen Sie in der Kopfzeile
  3. Setzen Sie die Nutzdaten auf "grant_type =" client_credentials ""
  4. Anfordern (POST) an den Endpunkt.
  5. Das Bearer Token wird in " access_token "des zurückgegebenen JSON gesetzt, also holen Sie es sich.

Teil anzeigen

Es ist als Methode implementiert. Ist es ein Ort, um etwas zu implementieren, das "reset" zurückgibt, wenn es tatsächlich verwendet wird?

GetTweetStatus.disp_limit_status(&nbsp;)


    def disp_limit_status(self, version=2, resource_family="search"):
        """Anzeigenlimit nach Version"""
        if version == 2:
            resj = self.get_limit_status_v2(resource_family=resource_family)
        elif version == 1:
            resj = self.get_limit_status_v1(resource_family=resource_family)
        else:
            raise Exception("Version error: {version}")

        #JSON anzeigen
        print(self._access_token_mask.sub(r'\g<access_token> "*******************"',
                                          json.dumps(resj, indent=2, ensure_ascii=False)))
        #Demontierte Anzeige(remain/Beispiel für das Zurücksetzen)
        print("resources:")
        if 'resources' in resj:
            resources = resj['resources']
            for family in resources:
                print(f"  family: {family}")
                endpoints = resources[family]
                for endpoint in endpoints:
                    items = endpoints[endpoint]
                    print(f"    endpoint: {endpoint}")
                    limit = items['limit']
                    remaining = items['remaining']
                    reset = items['reset']
                    e2d = epoch2datetime(reset)
                    duration = get_delta(reset)
                    print(f"      limit: {limit}")
                    print(f"      remaining: {remaining}")
                    print(f"      reset: {reset}")         #← Eigentlich ein Formular, das dies zurückgibt
                    print(f"      reset(epoch2datetime): {e2d}")
                    print(f"      duration: {duration} sec")
        else:
            print("  Not Available")

Zeitbezogene Dienstprogramme und Header

Dies ist das Zeitmanipulationsprogramm und der Anfang der Datei.

getTwitterStatus.py


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Beispiel für die Erfassung von Twitter-Ratenlimits"""

import os
import sys
import json
from base64 import b64encode
import datetime
import time
import re
import argparse

#!pip install requests
import requests
#!pip install requests_oauthlib
from requests_oauthlib import OAuth1Session


USER_AGENT = "Get Twitter Staus Application/1.0"
TOKEN_ENDPOINT = 'https://api.twitter.com/oauth2/token'
STATUS_ENDPOINT = 'https://api.twitter.com/1.1/application/rate_limit_status.json'


def epoch2datetime(epoch):
    """Epochenzeit(UNIX-Zeit)Terminzeit(localtime)Konvertieren zu"""
    return datetime.datetime(*(time.localtime(epoch)[:6]))


def datetime2epoch(d_utc):
    """datetime (UTC)Epochenzeit(UNIX-Zeit)Konvertieren zu"""
    #UTC in Ortszeit konvertieren
    date_localtime = \
        d_utc.replace(tzinfo=datetime.tzinfo.tz.tzutc()).astimezone(datetime.tzinfo.tz.tzlocal())
    return int(time.mktime(date_localtime.timetuple()))


def get_delta(target_epoch_time):
    """target_epoch_Gibt die Differenz zwischen der Zeit und der aktuellen Zeit zurück"""
    return target_epoch_time - int(round(time.time(), 0))

Hauptteil

Da es eine große Sache ist, habe ich versucht, die OAuth-Version und die Ressourcenfamilie mit dem Argument der Befehlszeile anzugeben.

main(&nbsp;)


def main():
    """main()"""
    # API_KEY, API_Bestätigung von Umgebungsvariablen wie SEC
    apikey = os.getenv('API_KEY', default="")
    apisec = os.getenv('API_SEC', default="")
    access_token = os.getenv('ACCESS_TOKEN', default="")
    access_secret = os.getenv('ACCESS_SECRET', default="")

    if apikey == "" or apisec == "":    #Wenn die Umgebungsvariable nicht abgerufen werden kann
        print("Umgebungsvariablen-API_KEY und API_Bitte stellen Sie SEC ein.", file=sys.stderr)
        print("OAuth v1.Umgebungsvariable ACCESS bei Verwendung von 1_Token und Zugang_Stellen Sie auch SECRET ein.",
              file=sys.stderr)
        sys.exit(255)

    #Argumenteinstellung
    parser = argparse.ArgumentParser()
    parser.add_argument('-a', '--oauthversion', type=int, default=0,
                        metavar='N', choices=(0, 1, 2),
                        help=u'OAuth-Versionsspezifikation[1|2]')
    parser.add_argument('-f', '--family', type=str, default='search',
                        metavar='Family',
                        help=u'API-Familienspezifikation. Durch Kommas für mehrere getrennt')

    args = parser.parse_args()
    oauthversion = args.oauthversion
    family = args.family

    #GetTweetStatus-Objekt
    gts = GetTweetStatus(apikey, apisec, access_token=access_token, access_secret=access_secret)

    # User Auth (OAuth v1.1)Erfassung und Anzeige von Ratenlimits durch
    if (oauthversion in (0, 1)) and (access_token != "" and access_secret != ""):
        print("<<user auth (OAuth v1)>>")
        gts.disp_limit_status(version=1, resource_family=family)

    # App Auth (OAuth v2.0)Erfassung und Anzeige von Ratenlimits durch
    if oauthversion in (0, 2):
        print("<<app auth (OAuth v2)>>")
        gts.disp_limit_status(version=2, resource_family=family)

if __name__ == "__main__":
    main()

Alle Code getTwitterStatus.py

[^ 2]: Ich habe zum ersten Mal versucht, Gist zu verwenden. Ich mache mir Sorgen, ob die Verwendung korrekt ist.

Ausführungsergebnis

$ python3 getTwitterStatus.py
<<user auth (OAuth v1)>>
{
  "rate_limit_context": {
    "access_token": "*******************"
  },
  "resources": {
    "search": {
      "/search/tweets": {
        "limit": 180,
        "remaining": 180,
        "reset": 1591016735
      }
    }
  }
}
resources:
  family: search
    endpoint: /search/tweets
      limit: 180
      remaining: 180
      reset: 1591016735
      reset(epoch2datetime): 2020-06-01 22:05:35
      duration: 899 sec
<<app auth (OAuth v2)>>
{
  "rate_limit_context": {
    "application": "dummykey"
  },
  "resources": {
    "search": {
      "/search/tweets": {
        "limit": 450,
        "remaining": 450,
        "reset": 1591016736
      }
    }
  }
}
resources:
  family: search
    endpoint: /search/tweets
      limit: 450
      remaining: 450
      reset: 1591016736
      reset(epoch2datetime): 2020-06-01 22:05:36
      duration: 900 sec
$ 

Begrenzung der Anzahl der erworbenen Tweets

Es gibt eine Begrenzung für die Häufigkeit, mit der Sie es gleichzeitig erhalten können, unabhängig von der Begrenzung.

200 ($ count \ leq200 ) für `statuses / user_timeline`, 100 ( count \ leq100 $) für search / tweets. Es gibt verschiedene andere Einschränkungen, aber im Fall von "Suche / Tweets" wird das Element "next_results" in "search_metadata" aufgenommen, damit es kontinuierlich abgerufen werden kann.

Für Suche / Tweets

{
  "statuses": [
  ...
  ],
  "search_metadata": {
    "completed_in": 0.047,
    "max_id": 1125490788736032770,
    "max_id_str": "1125490788736032770",
    "next_results": "?max_id=1124690280777699327&q=from%3Atwitterdev&count=2&include_entities=1&result_type=mixed",
    "query": "from%3Atwitterdev",
    "refresh_url": "?since_id=1125490788736032770&q=from%3Atwitterdev&result_type=mixed&include_entities=1",
    "count": 2,
    "since_id": 0,
    "since_id_str": "0"
  }
}

Es gibt "next_results" in "search_metadata". Wenn Sie dies als neuen Parameter anfordern, können Sie auch den Rest der Suchergebnisse abrufen (in den in count angegebenen Einheiten).

Solange Sie das Zeitlimit nicht erreichen, können Sie sich darauf beziehen und wiederholen, um die Ergebnisse kontinuierlich zu erhalten. Das heißt, Sie können $ count $ (bis zu 100) $ × limit $ (180 für Benutzerauthentifizierung) $ = 18.000 Tweet $ in Rate Limit erhalten.

Im Fall des obigen Beispiels ist $ count = 2 $. Wenn Sie also so weitermachen, wie es ist, können Sie $ count (2) Tweets / Zeit x Limit (180) mal / 15 Minuten = 360 Tweets / 15 Minuten $ erhalten, und Sie sind begrenzt. Erreichen Sie (natürlich, wenn Sie Krebs anfordern).

Wenn alle Suchergebnisse erhalten wurden, verschwindet "next_results" aus "search_metadata".

Wenn Sie es erneut anfordern, wird "next_results" manchmal wiederhergestellt, sodass Sie möglicherweise eine Weile warten und es erneut versuchen möchten.

Wenn keine Metadaten vorhanden sind

Im Fall von "statuses / user_timeline" usw. ist "* _metadata" nicht enthalten. Verwenden Sie daher die Spezifikation von "max_id" und generieren Sie selbst etwas, das "next_results" der Suche entspricht. wird gebraucht. (Eigentlich habe ich es nur für die Suche verwendet, daher bin ich mir nicht sicher, aber ich denke, es ist nicht so weit weg.)

Bei der Suche werden die letzten 7 Tage als Ziel ausgewählt, aber da "user_timeline" die letzten 24 Stunden sind, denke ich, dass der Zweck in erster Linie anders ist ...

Zusammenfassung

Referenz (basierend auf dem, worauf in diesem Artikel verwiesen wurde)

Recommended Posts

Kontinuierliche Erfassung durch Twitter API (Tipps)
Verwenden Sie die Twitter-API (API-Kontoregistrierung und Tweet-Erfassung).
EXE Web API von Python
Verwenden Sie die Twitter-API mit Python
Versuchen Sie es mit der Twitter-API
Versuchen Sie es mit der Twitter-API
Tipps zur Erfassung von Aktienkursdaten
Google Drive API-Tipps (Python)
Erfolgreiches update_with_media mit Twitter API