這學(xué)期選修《社會網(wǎng)絡(luò)分析》需要爬取些數(shù)據(jù)懦鼠,剛接觸python對爬蟲還不是很熟悉,過程中遇到一些問題,把心得分享給同樣學(xué)習(xí)python爬蟲的同學(xué)。
-
教科書般的API接口信息
Github作為一個出色的代碼托管平臺劫侧,也為開發(fā)者們提供了結(jié)構(gòu)非常清晰的API接口信息,瀏覽器安裝json插件后閱讀更佳哨啃。 -
詳細的開發(fā)者文檔
想了解相關(guān)參數(shù)設(shè)置和可爬取的數(shù)據(jù)烧栋,可閱讀Github Developer Guide
爬取目標(biāo):
"digital,library"主題下的開源項目合作情況,包含加權(quán)貢獻值commit,additions,deletions.注意事項:
Github的關(guān)鍵詞檢索功能比較有限棘催,用雙引號和逗號相結(jié)合表示AND檢索.-
邏輯思路:
- 先通過repository_search_url 獲取檢索結(jié)果下的項目信息
https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}
- 先通過repository_search_url 獲取檢索結(jié)果下的項目信息
再根據(jù)項目信息中的{owner}{name}信息傳遞到stats/contributors頁面獲取相關(guān)和做貢獻信息
https://api.github.com/repos/{owner}/{name}/stats/contributors
問題思考:
- 使用
urlopen()
需要導(dǎo)入劲弦、安裝什么包耳标?如何導(dǎo)入urllib
包醇坝?
python3.x已經(jīng)包含urlllib
包,無需再安裝,且不同于以往的urllib2
呼猪,urllib
分為urllib.request
和urllib.error
画畅,導(dǎo)入urlopen
的方法
from urllib.request import urlopen
- 如何解決
API Rate Limiting
限制?
初次爬取到的數(shù)據(jù)只有200多條記錄宋距,與事先的搜索結(jié)果不符轴踱,而且返回http error 403 forbidden
,訪問請求被禁止谚赎,閱讀Github Developer Guide后才發(fā)現(xiàn)未經(jīng)過認證的請求上限是60次/hour
淫僻,打開api url
也會發(fā)現(xiàn)該頁只有30條記錄,為了爬取到較為完整的數(shù)據(jù)壶唤,需要添加Authentication認證和Pagination分頁 - 如何實現(xiàn)Authentication認證
Github提供了三種認證方式雳灵,考慮到源碼分享后的賬戶安全問題,用戶+密碼認證方式不太建議使用闸盔,另外就是令牌訪問方式悯辙。
首先是如何生成令牌,在你的個人主頁setting/personal access token/ generate new token
迎吵,把生成的token復(fù)制保存下來躲撰,后面即將用到
- 方法一:sent in a header
一開始我是這么請求的
headers = {'Authorization':'ef802a122df2e4d29d9b1b868a6fefb14f22b272'}
然后得到了http error 401 unauthorizated
,以為是令牌的問題regenerate了幾次击费,后來才發(fā)現(xiàn)是要加上token
前綴拢蛋,網(wǎng)上很多教程都提到要如何生成token
,token
要加在headers
里蔫巩,但真的很少提到這點瓤狐,敲這篇文章的時候發(fā)現(xiàn)Github Developer Guide已經(jīng)寫得很清楚了(閱讀說明文檔很重要-攤手.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
- 如何實現(xiàn)對結(jié)果分頁?
Github一頁的上限是100,在后面加上page=1&per_page=100即可批幌,建議加上升序或降序排列础锐,后續(xù)處理數(shù)據(jù)將更加方便,根據(jù)star值降序排列采用的是e.g.
https://api.github.com/search/repositories?q={search}&page=4&per_page=100&sort=stars&order=desc
- json解析器錯誤
raise JSONDecodeError("Expecting value", s, err.value) from None json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
網(wǎng)上很多人都遇到了該問題荧缘,起初我也以為我也是皆警,有點奇怪的是我在爬取第一頁時沒有該報錯,是后面才有的......
JSON(JavaScript Object Notation)
是一種輕量級的數(shù)據(jù)交換格式截粗,可以簡單把它理解為list
數(shù)組字符串信姓,我們的目的是要將已編碼的 JSON 字符串解碼為 Python 對象來處理
response = urlopen(req).read()
result = json.loads(response.decode())
注意必須要加上decode()
,如果涉及到中文還要加上具體的編碼方式如decode('utf-8')
绸罗,因為在這里response
返回的頁面信息是bytes
意推,我們要把他轉(zhuǎn)為string
顯然我并不是這類解碼問題,我也嘗試把json的單引號替換成雙引號珊蟀,然而并沒有什么用菊值,這個問題真的弄得我寢食難安了一兩天。直到我看到某個論壇里有人說可能你要解析的object
就是空list
,這我需要驗證一下了腻窒,于是我在代碼中加上了print(item['name'])
昵宇,果然在打印了十幾條后中止了又跳出該報錯,我拿出事先爬取的RepoList
比對(這個時候你會發(fā)現(xiàn)當(dāng)初的降序排列是個多么明智的選擇)
到該停止結(jié)點的頁面去看發(fā)現(xiàn)果然是空的(由于時間和權(quán)限關(guān)系儿子,某些repositories
已經(jīng)失效或者沒有contributors
信息)瓦哎,只有{}
,所以需要在代碼中加上判斷條件柔逼,但并不是頁面為空蒋譬,而是列表為空,前面提到可以簡單把json
理解成list
數(shù)組愉适,所以判斷條件是len(JSON)
是否為0
完善后成功爬取數(shù)據(jù)的代碼如下:
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__':
# 這里用不用轉(zhuǎn)義符沒差別
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