这学期选修《社会网络分析》需要爬取些数据,刚接触python对爬虫还不是很熟悉,过程中遇到一些问题,把心得分享给同样学习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}
- 先通过repository_search_url 获取检索结果下的项目信息
-
再根据项目信息中的{owner}{name}信息传递到stats/contributors页面获取相关和做贡献信息
https://api.github.com/repos/{owner}/{name}/stats/contributors
-
问题思考:
- 使用
urlopen()
需要导入、安装什么包?如何导入urllib
包?
python3.x已经包含urlllib
包,无需再安装,且不同于以往的urllib2
,urllib
分为urllib.request
和urllib.error
,导入urlopen
的方法
from urllib.request import urlopen
- 如何解决
API Rate Limiting
限制?
初次爬取到的数据只有200多条记录,与事先的搜索结果不符,而且返回http error 403 forbidden
,访问请求被禁止,阅读Github Developer Guide后才发现未经过认证的请求上限是60次/hour
,打开api url
也会发现该页只有30条记录,为了爬取到较为完整的数据,需要添加Authentication认证和Pagination分页 - 如何实现Authentication认证
Github提供了三种认证方式,考虑到源码分享后的账户安全问题,用户+密码认证方式不太建议使用,另外就是令牌访问方式。
首先是如何生成令牌,在你的个人主页setting/personal access token/ generate new token
,把生成的token复制保存下来,后面即将用到
- 方法一:sent in a header
一开始我是这么请求的
headers = {'Authorization':'ef802a122df2e4d29d9b1b868a6fefb14f22b272'}
然后得到了http error 401 unauthorizated
,以为是令牌的问题regenerate了几次,后来才发现是要加上token
前缀,网上很多教程都提到要如何生成token
,token
要加在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
- 如何实现对结果分页?
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
- 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
网友评论