Github API: 用python爬取相關數據

  • 教科書般的API接口信息
    Github作爲一個出色的代碼託管平臺,也爲開發者們提供了結構非常清晰的API接口信息,瀏覽器安裝json插件後閱讀更佳。
  • 詳細的開發者文檔
    想了解相關參數設置和可爬取的數據,可閱讀Github Developer Guide
  • 爬取目標:
    "digital,library"主題下的開源項目合作情況,包含加權貢獻值commit,additions,deletions.

  • 注意事項:
    Github的關鍵詞檢索功能比較有限,用雙引號和逗號相結合表示AND檢索.

  • 邏輯思路:

    • 先通過repository_search_url 獲取檢索結果下的項目信息
      https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}
  • 再根據項目信息中的{owner}{name}信息傳遞到stats/contributors頁面獲取相關和做貢獻信息
    https://api.github.com/repos/{owner}/{name}/stats/contributors

  • 問題思考:

  1. 使用urlopen()需要導入、安裝什麼包?如何導入urllib包?
    python3.x已經包含urlllib包,無需再安裝,且不同於以往的urllib2urllib分爲urllib.requesturllib.error,導入urlopen的方法
from urllib.request import urlopen
  1. 如何解決API Rate Limiting限制?
    初次爬取到的數據只有200多條記錄,與事先的搜索結果不符,而且返回http error 403 forbidden,訪問請求被禁止,閱讀Github Developer Guide後才發現未經過認證的請求上限是60次/hour,打開api url也會發現該頁只有30條記錄,爲了爬取到較爲完整的數據,需要添加Authentication認證Pagination分頁
  2. 如何實現Authentication認證
    Github提供了三種認證方式,考慮到源碼分享後的賬戶安全問題,用戶+密碼認證方式不太建議使用,另外就是令牌訪問方式。
    首先是如何生成令牌,在你的個人主頁setting/personal access token/ generate new token,把生成的token複製保存下來,後面即將用到
  • 方法一:sent in a header
    一開始我是這麼請求的
headers = {'Authorization':'ef802a122df2e4d29d9b1b868a6fefb14f22b272'}

然後得到了http error 401 unauthorizated,以爲是令牌的問題regenerate了幾次,後來才發現是要加上token前綴,網上很多教程都提到要如何生成tokentoken要加在headers裏,但真的很少提到這點,敲這篇文章的時候發現Github Developer Guide已經寫得很清楚了(閱讀說明文檔很重要-攤手.jpg)

headers = {'User-Agent':'Mozilla/5.0',
               'Authorization': 'token ef802a122df2e4d29d9b1b868a6fefb14f22b272',
               'Content-Type':'application/json',
               'Accept':'application/json'
               }
  • 方法二:sent as a parameter
 https://api.github.com/?access_token=OAUTH-TOKEN
  1. 如何實現對結果分頁?
    Github一頁的上限是100,在後面加上page=1&per_page=100即可,建議加上升序或降序排列,後續處理數據將更加方便,根據star值降序排列採用的是e.g.
https://api.github.com/search/repositories?q={search}&page=4&per_page=100&sort=stars&order=desc
  1. json解析器錯誤
    raise JSONDecodeError("Expecting value", s, err.value) from None json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
    網上很多人都遇到了該問題,起初我也以爲我也是,有點奇怪的是我在爬取第一頁時沒有該報錯,是後面纔有的......
    JSON(JavaScript Object Notation)是一種輕量級的數據交換格式,可以簡單把它理解爲list數組字符串,我們的目的是要將已編碼的 JSON 字符串解碼爲 Python 對象來處理
response = urlopen(req).read()
result = json.loads(response.decode())

注意必須要加上decode(),如果涉及到中文還要加上具體的編碼方式如decode('utf-8'),因爲在這裏response返回的頁面信息是bytes,我們要把他轉爲string
顯然我並不是這類解碼問題,我也嘗試把json的單引號替換成雙引號,然而並沒有什麼用,這個問題真的弄得我寢食難安了一兩天。直到我看到某個論壇裏有人說可能你要解析的object就是空list,這我需要驗證一下了,於是我在代碼中加上了print(item['name']),果然在打印了十幾條後中止了又跳出該報錯,我拿出事先爬取的RepoList比對(這個時候你會發現當初的降序排列是個多麼明智的選擇)
到該停止結點的頁面去看發現果然是空的(由於時間和權限關係,某些repositories已經失效或者沒有contributors信息),只有{},所以需要在代碼中加上判斷條件,但並不是頁面爲空,而是列表爲空,前面提到可以簡單把json理解成list數組,所以判斷條件是len(JSON)是否爲0

完善後成功爬取數據的代碼如下:

from urllib.request import urlopen
from urllib.request import Request
import datetime
import json

def get_statistics(owner,name,headers):
    url = 'https://api.github.com/repos/{owner}/{name}/stats/contributors?page=2&per_page=100'.format(owner=owner, name=name)
    req = Request(url,headers=headers)
    response = urlopen(req).read()
    if len(response)==0:
        return response
    else:
        result = json.loads(response.decode())
        return result

def get_results(search,headers):
    url = 'https://api.github.com/search/repositories?q={search}&page=4&per_page=100&sort=stars&order=desc'.format(search=search)
    req = Request(url,headers=headers)
    response = urlopen(req).read()
    result = json.loads(response.decode())
    return result
    

if __name__ == '__main__':
    # 這裏用不用轉義符沒差別
    search = '\"digital,library\"'
    
    headers = {'User-Agent':'Mozilla/5.0',
               'Authorization': 'token ef802a122df2e4d29d9b1b868a6fefb14f22b272',
               'Content-Type':'application/json',
               'Accept':'application/json'
               }

    results = get_results(search,headers)

    f = open('ContributorsInfo4.txt', 'w')
    for item in results['items']:
        name = item['name']
        star = item['stargazers_count']
        owner = item['owner']['login']
        language = str('0') 
        user = str('0')
        commits = 0
        language = item['language']
        stats = get_statistics(owner,name,headers)
        contributor_list = []
        count = len(stats)
        for i in range(0,count):
            user = stats[i]['author']['login']
            commits = stats[i]['total']
            deletions = 0
            additions = 0
            first_commit = None
            last_commit = None
            for week in stats[i]['weeks']:
                deletions += week['d']
                additions += week['a']
                # assume that weeks are ordered
                if first_commit is None and week['c'] > 0:
                    first_commit = week['w']
                if week['c'] > 0:
                    last_commit = week['w']
            contributor_list.append([name,
                                     owner,
                                     star,
                                     language,
                                     count,
                                     user,
                                     commits,
                                     additions,
                                     deletions,
                                     datetime.datetime.fromtimestamp(first_commit).strftime('%Y-%m-%d'),
                                     datetime.datetime.fromtimestamp(last_commit).strftime('%Y-%m-%d')
                                    ])
        for contributor in contributor_list:
            print(contributor,file = f)

參考項目 Github
詳細接口信息 API接口
請詳細閱讀 Github Developer Guide

原文作者:leonaxiong
原文鏈接:https://www.jianshu.com/p/628a0747c492
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章