Góðir starfshættir

Aðgangslyklar sem lenda aldrei í Git, álagsmörk og kurteisi við þjónustur, og hvernig gögn sótt um API haldast endurtakanleg.

Lyklar fara aldrei í Git

Margar þjónustur krefjast aðgangslykils (e. API key eða token). Lykill er lykilorð: sá sem hefur hann getur gert allt sem þú mátt gera, í þínu nafni.

MikilvægtÞetta eru algengustu alvarlegu mistökin
# ALDREI SVONA
svar <- request("https://api.example.is/gogn") |>
  req_headers(Authorization = "Bearer LYKILLINN-MINN") |>
  req_perform()

Lykillinn er nú í kóðanum. Committirðu skránni er hann í Git-sögunni — og hann er þar áfram þótt þú eyðir línunni síðar, því sagan geymir allar útgáfur. Geymslur eru vaktaðar af sjálfvirkum skanna og lyklar sem lenda í opnum geymslum eru misnotaðir á mínútum.

Rétta leiðin er að geyma lykilinn í umhverfisbreytu utan geymslunnar. Í R er það .Renviron:

# ~/.Renviron  — eða .Renviron í rót verkefnisins
GITHUB_TOKEN=<lykillinn-thinn-hingad>
AthugasemdPlasshaldari, ekki alvöru lykill

Raunverulegur GitHub-lykill byrjar á ghp_ og er 40 stafir. Slíkur strengur er hér vísvitandi ekki notaður, jafnvel sem dæmi: leyndarmálaskannar leita að því sniði og myndu flagga skjölunina. Notaðu sama hátt í þínum eigin dæmum og README-skrám.

Endurræstu R og lestu hann með Sys.getenv():

token <- Sys.getenv("GITHUB_TOKEN")

svar <- request("https://api.github.com/user/repos") |>
  req_auth_bearer_token(token) |>
  req_perform()

Kóðinn ber engan lykil og má fara á GitHub. Sé .Renviron í verkefnismöppunni verður hún að vera í .gitignore:

# .gitignore
.Renviron
.env
ÁbendingHafi lykill lekið

Ekki reyna að fjarlægja hann úr sögunni fyrst. Afturkallaðu hann strax hjá þjónustunni og búðu til nýjan — það gerir gamla lykilinn gagnslausan á augabragði. Að hreinsa Git-söguna er seinna verk og skiptir minna máli.

Sjá einnig öryggi á GitHub.

Dæmi: TMDB

TMDB (The Movie Database) er gott dæmi um þjónustu sem krefst lykils. Lykillinn er ókeypis fyrir persónuleg verkefni — þú sækir um hann, staðfestir að notkunin sé ekki í viðskiptaskyni, og færð hann samstundis.

Sæktu lykilinn á developer.themoviedb.org og settu hann í .Renviron (R) eða .env (Python) — aldrei í kóðann.

library(httr2)

myndir <- request("https://api.themoviedb.org/3/search/movie") |>
  req_url_query(query = "Sódóma Reykjavík") |>
  req_auth_bearer_token(Sys.getenv("TMDB_TOKEN")) |>
  req_perform() |>
  resp_body_json(simplifyVector = TRUE)

myndir$results |>
  dplyr::select(title, release_date, vote_average)
           title release_date vote_average
1 Remote Control   1992-09-09          6.6
Mikilvægt.Renviron er lesin við ræsingu

Tvennt sem eru líklegir villuvaldar:

R les .Renviron aðeins þegar hún ræsist. Bætirðu lykli við skrána meðan R er opin gerist ekkert — þú verður að endurræsa (í RStudio: Session → Restart R).

Skráin verður að vera í þeirri möppu sem R ræsist úr..Renviron í rót verkefnisins finnur R hana aðeins ef R ræsist þar — sem er sjálfgefið þegar unnið er í RStudio-verkefni. Viltu að lykillinn gildi alls staðar seturðu hann í ~/.Renviron í heimamöppunni þinni.

Athugaðu að hann hafi lesist áður en þú sendir beiðni:

nzchar(Sys.getenv("TMDB_TOKEN"))   # TRUE ef lykillinn er kominn inn
[1] TRUE

Skráin þarf líka að enda á línuskilum — annars sleppir R síðustu línunni þegjandi.

import os
import requests
import pandas as pd

svar = requests.get(
    "https://api.themoviedb.org/3/search/movie",
    params={"query": "Sódóma Reykjavík"},
    headers={"Authorization": "Bearer " + os.environ["TMDB_TOKEN"]},
    timeout=30,
)
svar.raise_for_status()

pd.json_normalize(svar.json()["results"])[["title", "release_date", "vote_average"]]
            title release_date  vote_average
0  Remote Control   1992-09-09           6.6

Í Python les python-dotenv skrána .env inn í os.environ:

pip install python-dotenv
from dotenv import load_dotenv
load_dotenv()          # les .env úr vinnumöppunni

Þegar lykilinn vantar

Þetta er fyrsta sem gerist hjá öllum, og málin tvö bregðast gjörólíkt við:

Sys.getenv("TMDB_TOKEN")
VarúðÞessi bútur má aldrei keyra

Hann prentar lykilinn. Væri hann keyrður á vél þar sem lykillinn er til staðar færi hann inn í frystu niðurstöðurnar — og þær eru í opinni geymslu. eval: false er hér öryggisráðstöfun, ekki þægindi.

Sama gildir um þinn eigin kóða: það sem búturinn prentar endar í skjalinu.

[1] ""

Sys.getenv() skilar tómum streng — engin villa, engin viðvörun. Beiðnin fer af stað með hausinn Authorization: Bearer og þjónustan svarar:

401 Invalid API key: You must be granted a valid key.

Villan bendir á lykilinn, en ekki á að hann hafi aldrei verið lesinn. Þess vegna borgar sig að athuga sjálf/ur:

token <- Sys.getenv("TMDB_TOKEN", unset = NA)
stopifnot(!is.na(token), nzchar(token))
import os
os.environ["TMDB_TOKEN"]
KeyError: 'TMDB_TOKEN'

Python stöðvar strax og segir nákvæmlega hvað vantar — engin beiðni fer af stað. Það er betri hegðun.

Í gagnvirkri lotu (REPL) lítur þetta þó verr út en það er. Fyrsta línan fellur, svar verður þar með aldrei til, og hver einasta lína á eftir fellur líka:

>>> svar = requests.get(..., headers={"Authorization": "Bearer " + os.environ["TMDB_TOKEN"]})
KeyError: 'TMDB_TOKEN'

>>> svar.raise_for_status()
NameError: name 'svar' is not defined

>>> pd.json_normalize(svar.json()["results"])
NameError: name 'svar' is not defined

Þrjár villur, eitt vandamál. Lestu alltaf fyrstu villuna — hinar eru bergmál af henni. NameError hér er ekki sjálfstæð villa heldur afleiðing þess að fyrsta skrefið tókst aldrei.

Viltu mýkri villuboð:

token = os.environ.get("TMDB_TOKEN")
if not token:
    raise SystemExit("TMDB_TOKEN vantar — settu hann í .env og keyrðu load_dotenv().")
MikilvægtÞögult tóm gildi er verra en villa

R-hegðunin er dæmi um nákvæmlega það sem varað er við annars staðar í þessum kafla: kóðinn heldur áfram með tómt gildi og villan kemur fram löngu síðar, á stað sem bendir í ranga átt. Athugaðu því alltaf að lykillinn hafi lesist áður en þú sendir beiðnina.

AthugasemdHvernig þetta er keyrt án þess að lykillinn fari á netið

Taflan að ofan er raunveruleg niðurstaða úr TMDB — ekki afrituð með höndunum. En byggingarþjónninn hjá GitHub hefur engan lykil og fær hann ekki.

Lausnin er frysting. Þessi síða er merkt freeze: true, sem þýðir:

  1. Kennarinn keyrir kaflann á sinni vél, þar sem lykillinn er í .Renviron.
  2. Quarto vistar niðurstöðurnar í _freeze/, sem fylgir með í geymsluna.
  3. Þegar bókin er byggð les Quarto frystu niðurstöðurnar í stað þess að senda beiðni.

Lykillinn fer aldrei úr vélinni sem keyrði kóðann. Frystu skrárnar geyma úttakið, ekki umhverfisbreyturnar — þess vegna má enginn bútur hér prenta lykilinn.

Munurinn á þessu og handskrifaðri töflu er sá að frysta útgáfan var raunverulega keyrð, og uppfærist um leið og kaflinn er keyrður aftur. Handskrifuð tafla helst óbreytt þótt kóðinn hætti að virka.

AðvörunSkilmálarnir eru hluti af samningnum

Þegar þú sækir um TMDB-lykil samþykkirðu að nota hann ekki í viðskiptaskyni. Það er raunveruleg kvöð, ekki formsatriði: lykillinn er afturkallaður sé hún brotin. Sama gildir um flestar þjónustur sem gefa ókeypis aðgang — lestu skilmálana áður en þú byggir verkefni á þeim.

TMDB krefst þess líka að getið sé heimildar þegar gögnin eru birt.

Aðgreindu lykla og gefðu lágmarksréttindi

Þegar þú býrð til lykil er oft hægt að velja hvað hann má. Veldu minnstu réttindi sem duga. Lykill sem á bara að lesa opin gögn þarf engin skrifréttindi, og þá er tjónið lítið þótt hann leki. Settu líka gildistíma á hann ef það býðst.

Kurteisi við þjónustuna

Á hinum endanum er tölva sem einhver borgar fyrir. Nokkrar reglur:

Segðu hver þú ert. User-Agent-hausinn á að bera nafn og tilgang. Sumar þjónustur hafna beiðnum án hans, og þeir sem reka þjónustuna geta haft samband ef eitthvað er að.

req_user_agent("IDN302G-verkefni (nafn@hi.is)")

Haltu þig innan marka. Þjónustur setja hámark á beiðnir á tímaeiningu. Farirðu yfir færðu 429 og getur lent í banni. req_throttle() heldur þér réttum megin:

req_throttle(rate = 30 / 60)   # 30 beiðnir á mínútu

Sæktu einu sinni. Ertu að prófa þig áfram? Vistaðu svarið og notaðu skrána á meðan þú þróar kóðann. Það er engin ástæða til að senda nýja beiðni í hvert sinn sem þú lagar eina línu í greiningunni.

Sæktu bara það sem þú þarft. Síaðu í beiðninni með fyrirspurnarfæribreytum, ekki eftir á.

Skilmálar og leyfi

Að gögn séu aðgengileg þýðir ekki að þú megir allt með þau:

  • Skilmálar (e. terms of service) segja hvað er leyfilegt. Sumar þjónustur banna geymslu gagna eða endurbirtingu.
  • Leyfi (e. licence) segir hvernig þú mátt deila gögnunum áfram. Opin gögn krefjast oft þess að heimildar sé getið.
  • Persónugreinanleg gögn eru sérstakt mál. Nöfn, kennitölur og netföng lúta persónuverndarlögum óháð því hvernig þú náðir í þau.
AðvörunÍ verkefnum áfangans

Sæktu ekki persónugreinanleg gögn um fólk sem hefur ekki samþykkt það, og committaðu þeim aldrei í geymsluna þína. Sértu í vafa, spurðu kennara áður en þú sækir.

Endurtakanleiki

Gögn sótt um API eru hreyfanlegt skotmark — þjónustan uppfærist, færslum fjölgar, og greiningin þín skilar annarri niðurstöðu á morgun. Þrennt heldur skýrslunni traustri:

Skráðu hvenær var sótt. Dagsetningin á að vera í skýrslunni sjálfri, ekki í minninu þínu.

sott <- Sys.time()
d <- resp_body_json(svar, simplifyVector = TRUE)
saveRDS(list(gogn = d, sott = sott), "data/raw.rds")

Geymdu hráu gögnin — en ekki í Git. Vistaðu svarið óbreytt og hreinsaðu í sérstöku skrefi. Þá geturðu lagað hreinsunina án þess að sækja aftur. Hráa skráin fer í .gitignore; það sem committast er skriftan sem sækir og strípar, ásamt lítilli CSV-skrá með því sem greiningin raunverulega notar. Sjá hvað á heima í geymslunni.

Aðskildu sókn og greiningu. Eitt skjal sækir og vistar; annað les skrána og greinir. Þá gengur greiningin upp þótt netið liggi niðri eða lykillinn sé útrunninn — sem er einmitt það sem skiptir máli þegar einhver annar les skýrsluna þína eftir hálft ár.

AthugasemdTenging við lotuna um endurtækar skýrslur

Þetta er sama meginregla og í endurtækum skýrslum: allt sem greiningin byggir á á að vera skjalfest og keyranlegt. API breytir ekki reglunni — það gerir hana bara mikilvægari, því gögnin eru ekki lengur í þinni vörslu.

Gátlisti

Áður en þú skilar verkefni sem sækir gögn um API: